DoggyZhang před 5 měsíci
revize
238e55aabd
100 změnil soubory, kde provedl 4288 přidání a 0 odebrání
  1. 16 0
      .gitignore
  2. 159 0
      CLAUDE.md
  3. 251 0
      README.md
  4. 1 0
      app/.gitignore
  5. 135 0
      app/build.gradle
  6. 21 0
      app/proguard-rules.pro
  7. 24 0
      app/src/androidTest/java/com/adealink/frame/ExampleInstrumentedTest.kt
  8. 49 0
      app/src/main/AndroidManifest.xml
  9. binární
      app/src/main/assets/audio.zip
  10. binární
      app/src/main/assets/custom_gift_default_effect.svga
  11. binární
      app/src/main/assets/emotion.svga
  12. binární
      app/src/main/assets/svga_with_sound.svga
  13. binární
      app/src/main/assets/vap.mp4
  14. 161 0
      app/src/main/java/com/adealink/frame/App.kt
  15. 121 0
      app/src/main/java/com/adealink/frame/MainActivity.kt
  16. 8 0
      app/src/main/java/com/adealink/frame/cache/UserInfo.kt
  17. 62 0
      app/src/main/java/com/adealink/frame/dot/DotActivity.kt
  18. 82 0
      app/src/main/java/com/adealink/frame/download/DownloadConfig.kt
  19. 15 0
      app/src/main/java/com/adealink/frame/effect/TCEffectConfig.kt
  20. 241 0
      app/src/main/java/com/adealink/frame/effect/TestAnimViewActivity.kt
  21. 125 0
      app/src/main/java/com/adealink/frame/effect/VideoTimeBar.kt
  22. 21 0
      app/src/main/java/com/adealink/frame/file/FilePath.kt
  23. 72 0
      app/src/main/java/com/adealink/frame/gson/GsonActivity.kt
  24. 26 0
      app/src/main/java/com/adealink/frame/image/ImageActivity.kt
  25. 57 0
      app/src/main/java/com/adealink/frame/media/MediaConfig.kt
  26. 140 0
      app/src/main/java/com/adealink/frame/router/TestRouterActivity.kt
  27. 106 0
      app/src/main/java/com/adealink/frame/router/TestRouterFragment.kt
  28. 26 0
      app/src/main/java/com/adealink/frame/sound/Sound.kt
  29. 78 0
      app/src/main/java/com/adealink/frame/sound/SoundActivity.kt
  30. 63 0
      app/src/main/java/com/adealink/frame/sound/SoundPlayerConfig.kt
  31. 11 0
      app/src/main/java/com/adealink/frame/sound/manager/ISoundListener.kt
  32. 31 0
      app/src/main/java/com/adealink/frame/sound/manager/ISoundManager.kt
  33. 81 0
      app/src/main/java/com/adealink/frame/sound/manager/SoundManager.kt
  34. 40 0
      app/src/main/java/com/adealink/frame/stat/StatConfig.kt
  35. 37 0
      app/src/main/java/com/adealink/frame/stat/TestStatActivity.kt
  36. 19 0
      app/src/main/java/com/adealink/frame/stat/TestWeNextStat.kt
  37. 25 0
      app/src/main/java/com/adealink/frame/svga/EmotionEffectEntity.kt
  38. 98 0
      app/src/main/java/com/adealink/frame/svga/EmotionEffectView.kt
  39. 106 0
      app/src/main/java/com/adealink/frame/svga/SVGAEmotionEffectView.kt
  40. 26 0
      app/src/main/java/com/adealink/frame/svga/SVGAEmotionEntity.kt
  41. 40 0
      app/src/main/java/com/adealink/frame/svga/SvgaActivity.kt
  42. 56 0
      app/src/main/java/com/adealink/frame/svga/SvgaEffectActivity.kt
  43. 75 0
      app/src/main/java/com/adealink/frame/svga/recyclerview/SvgaEffectRecyclerViewActivity.kt
  44. 64 0
      app/src/main/java/com/adealink/frame/svga/recyclerview/SvgaRecyclerViewActivity.kt
  45. 60 0
      app/src/main/java/com/adealink/frame/svga/viewpager/RecyclerviewFragment.kt
  46. 72 0
      app/src/main/java/com/adealink/frame/svga/viewpager/SvgaViewpagerActivity.kt
  47. 28 0
      app/src/main/java/com/adealink/frame/vap/VapActivity.kt
  48. 63 0
      app/src/main/java/com/adealink/frame/vap/VapEffectActivity.kt
  49. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  50. binární
      app/src/main/res/drawable-xhdpi/common_red_dot_ic.9.png
  51. binární
      app/src/main/res/drawable-xhdpi/level_glod_num_0.png
  52. binární
      app/src/main/res/drawable-xhdpi/level_glod_num_1.png
  53. 8 0
      app/src/main/res/drawable/common_red_dot_normal_bg.xml
  54. 9 0
      app/src/main/res/drawable/common_red_dot_text_bg.xml
  55. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  56. 8 0
      app/src/main/res/drawable/video_bottom_bar_bg.xml
  57. 30 0
      app/src/main/res/drawable/video_time_bar.xml
  58. 11 0
      app/src/main/res/drawable/video_time_bar_thumb.xml
  59. binární
      app/src/main/res/font/ludo_bold.otf
  60. 65 0
      app/src/main/res/layout/activity_dot.xml
  61. 22 0
      app/src/main/res/layout/activity_image.xml
  62. 179 0
      app/src/main/res/layout/activity_main.xml
  63. 144 0
      app/src/main/res/layout/activity_sound.xml
  64. 34 0
      app/src/main/res/layout/activity_svga.xml
  65. 28 0
      app/src/main/res/layout/activity_svga_effect.xml
  66. 18 0
      app/src/main/res/layout/activity_svga_effect_recyclerview.xml
  67. 18 0
      app/src/main/res/layout/activity_svga_recyclerview.xml
  68. 30 0
      app/src/main/res/layout/activity_svga_tab_view_pager.xml
  69. 99 0
      app/src/main/res/layout/activity_test_animview.xml
  70. 30 0
      app/src/main/res/layout/activity_test_stat.xml
  71. 18 0
      app/src/main/res/layout/activity_vap.xml
  72. 13 0
      app/src/main/res/layout/activity_vap_effect.xml
  73. 12 0
      app/src/main/res/layout/fragment_svga_recyclerview.xml
  74. 38 0
      app/src/main/res/layout/layout_dot_view2.xml
  75. 6 0
      app/src/main/res/layout/layout_svate_effect_item.xml
  76. 6 0
      app/src/main/res/layout/layout_svate_item.xml
  77. 81 0
      app/src/main/res/layout/layout_video_controller.xml
  78. 25 0
      app/src/main/res/layout/layout_video_time_bar.xml
  79. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  80. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  81. binární
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  82. binární
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  83. binární
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  84. binární
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  85. binární
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  86. binární
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  87. binární
      app/src/main/res/mipmap-xhdpi/room_micseat_seat_unlock_ic.webp
  88. binární
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  89. binární
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  90. binární
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  91. binární
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  92. binární
      app/src/main/res/raw/brother.mp4
  93. 16 0
      app/src/main/res/values-night/themes.xml
  94. 10 0
      app/src/main/res/values/colors.xml
  95. 3 0
      app/src/main/res/values/strings.xml
  96. 59 0
      app/src/main/res/values/styles.xml
  97. 20 0
      app/src/main/res/values/themes.xml
  98. 1 0
      app/src/main/resources/META-INF/services/com.adealink.frame.router.IRouterInit
  99. 17 0
      app/src/test/java/com/adealink/frame/ExampleUnitTest.kt
  100. 26 0
      build.gradle

+ 16 - 0
.gitignore

@@ -0,0 +1,16 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+.idea/

+ 159 - 0
CLAUDE.md

@@ -0,0 +1,159 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## CLAUDE.md 自动同步声明
+- 忽略细微修改,对变量名、注释、小范围格式等不影响架构或逻辑的变更,无需同步更新。
+- CLAUDE.md 内容关联同步
+  - 新增对外暴露的接口、模块时,Claude 必须在此文件中添加相应的描述。
+  - 若本文件中描述的内容(如模块职责、接口定义等)在代码中被修改、重命名或移除,Claude 必须同时在此文件中反映这些更改。
+  - 修改内容如在原 CLAUDE.md 中未涉及,则无需添加新内容。
+- 数量限制
+  - 根目录 CLAUDE.md 控制在400行内
+  - 模块内 CLAUDE.md 控制在300行内
+  - 超出行数限制时,优先保留核心架构和规范内容,删除冗余或重复信息。
+
+## 角色
+你是一个资深的 Android 架构专家,尤其擅长Kotlin+Jetpack技术栈。
+
+### 编码风格要求
+- 遵循 Kotlin 官方代码规范
+- 遵循单一职责原则(每个类只负责一件事)
+- 避免冗余注释,保持代码简洁清晰
+
+### 项目约定
+- 异步操作使用协程,通过 frame/coroutine 模块使用
+- 网络请求、文件操作、事件处理返回结果使用 RLT类(result),RLT类封装在 frame/zero 中
+- 日志必须使用 `AppBase.log` 进行输出
+  - `AppBase` 导入方式 `import com.adealink.frame.base.AppBase`
+  - `AppBase.log.d(tag, msg)` - 调试日志(Debug)
+  - `AppBase.log.i(tag, msg)` - 信息日志(Info),会保留到线上,要求重要步骤或重要信息才使用该级别的日志
+  - `AppBase.log.e(tag, msg)` - 错误日志(Error,无异常)
+  - `AppBase.log.e(tag, msg, exception)` - 错误日志(Error,带异常对象)
+- 编写与业务无关的公共功能,优先调用 frame/util 模块中已有的工具类,若不存在对应逻辑,请将该逻辑封装为新的工具方法或类
+
+## 项目架构
+这是一个模块化的Android框架库,采用多模块架构设计。项目分为两大类别:
+- **frame/**: 核心框架模块
+- **external/**: 第三方库集成模块
+
+### frame 代码目录结构
+**基础架构模块(Framework Core):**
+- `frame/base`: 基于 RLT 状态管理的项目基础,所有其他模块的依赖基础
+- `frame/mvvm`: MVVM 架构扩展,提供 ViewModel 和 LiveData 支持
+- `frame/coroutine`: Kotlin 协程工具封装
+- `frame/data`: 自定义数据结构和类型转换
+- `frame/util`: 通用工具类集合
+- `frame/spi`: 服务发现和依赖注入框架(Service Provider Interface)
+- `frame/startup`: 应用启动初始化框架(统一管理模块初始化顺序)
+- `frame/bom`: 统一依赖版本管理(Bill of Materials)
+
+**网络与存储(Network & Storage):**
+- `frame/network`: 基于 Retrofit 的网络请求框架
+- `frame/storage`: 统一的存储管理(文件存储、SharedPreferences)
+- `frame/oss`: 对象存储服务封装(支持阿里云 OSS、腾讯 COS)
+- `frame/cachebean`: 缓存数据模型定义及持久化管理
+- `frame/download`: 下载管理器封装(多线程断点续传、任务调度)
+
+**UI组件(UI & Visual Components):**
+- `frame/effect`: 动画效果播放框架(支持 SVGA、VAP 等格式)
+- `frame/effectpreview`: 动画效果预览与编辑工具
+- `frame/image`: 图片加载框架(基于 Fresco)
+- `frame/commonui`: 公共 UI 组件库
+- `frame/dot`: 红点提醒系统
+- `frame/guide`: 新手引导与引导浮层组件
+- `frame/media`: 音视频媒体播放、录制与处理封装
+
+**实时通信(RTC & 音视频):**
+- `frame/agorartc`: 声网 RTC SDK 封装
+- `frame/trtc`: 腾讯云 RTC SDK 封装
+- `frame/volcrtc`: 火山引擎 RTC SDK 封装
+- `frame/zegortc`: 即构 RTC SDK 封装
+- `frame/room`: 统一的房间信令管理
+- `frame/audio`: 音频播放与录制管理
+- `frame/sound`: 音效与提示音管理模块
+
+**系统与服务(System & Platform):**
+- `frame/router`: 基于注解的页面路由框架
+- `frame/security`: 应用安全检测和防护功能
+- `frame/deviceid`: 设备唯一标识管理
+- `frame/locale`: 多语言本地化支持和位置定位
+- `frame/log`: 统一日志采集与打印框架
+- `frame/crash`: 崩溃捕获与上报
+- `frame/statistics`: 埋点与数据统计上报框架
+- `frame/apm`: 性能监控系统(内存、卡顿、CPU、FPS 等)
+- `frame/tceffect`: 腾讯特效 SDK 封装
+
+**平台集成与外部服务(Integration & Cloud Service):**
+- `frame/googleservice`: Google 服务集成(Firebase、FCM 等)
+- `frame/push`: 多厂商推送统一封装(华为、小米、OPPO、FCM)
+- `frame/share`: 第三方分享与社交平台封装
+- `frame/game`: 游戏 SDK 封装或小游戏容器集成
+- `frame/zero`: 零配置接入工具包(用于快速初始化)
+- `frame/bundletool`: Android App Bundle 构建与动态模块拆分工具
+- `frame/aab`: AAB(Android App Bundle)打包支持工具
+- `frame/asr`: 语音识别(ASR)模块
+
+**开发与测试支持(Dev & Debug):**
+- `frame/debug`: 调试工具(悬浮窗、日志面板、环境切换)
+- `frame/trace`: 性能追踪和调试分析(函数耗时、启动性能)
+
+### external 代码目录结构
+- `external/androidautosize`: AndroidAutoSize 屏幕适配库集成
+- `external/animplayer`: 腾讯 VAP(Video Animation Player),用于播放酷炫动画的实现方案
+- `external/SVGAPlayer`: SVGA 动画播放器集成
+- `external/retrofit`: Retrofit 网络请求库集成
+- `external/drawee`: Fresco DraweeView 图片渲染库集成
+- `external/libcocos2dx`: Cocos2d-x 游戏引擎集成
+- `external/tcturing`: 腾讯图灵盾风险识别
+
+### 开发规范
+1. **模块独立性**: 每个frame模块都应该可以独立编译和使用
+2. **依赖方向**: 框架模块之间避免循环依赖
+3. **base模块**: 是所有其他模块的基础依赖,包含核心的状态管理和通用功能
+
+### 常用代码路径
+- 主要业务逻辑:`frame/*/src/main/java/com/adealink/`
+- 资源文件:`frame/*/src/main/res/`
+- 第三方集成:`external/*/src/main/java/`
+
+## 代码核心架构规范
+
+### 1. 接口-工厂-初始化
+**适用场景:**
+- 模块需要全局访问
+
+```kotlin
+// 1. 定义模块能力的核心接口
+interface IModuleName {
+    fun primaryAction()
+    fun secondaryAction()
+}
+
+// 2. 用于依赖注入的配置接口
+interface IModuleConfig {
+    val requiredDependency: Type
+    val optionalSetting: String
+}
+
+// 3. 创建实例的工厂函数,管理类可以 *Manager 规则命名
+@Synchronized
+fun createModule(config: IModuleConfig): IModuleName {
+    return ModuleImpl(config)
+}
+
+// 4. 模块全局初始化函数
+fun initModule(initiator: (() -> IModuleName)) {
+    ModuleImpl.initiator = initiator
+}
+
+// 5. 内部实现类(带惰性单例)
+internal class ModuleImpl(val config: IModuleConfig) : IModuleName {
+    companion object {
+        var initiator: (() -> IModuleName)? = null
+        var instance: IModuleName? = null
+            get() = field ?: initiator?.invoke()?.also { field = it }
+                ?: throw IllegalArgumentException("Module not initialized")
+    }
+}
+```

+ 251 - 0
README.md

@@ -0,0 +1,251 @@
+## 项目说明
+- base: 全项目基于Rlt状态管理构建的基础
+```shell
+./gradlew :frame:base:clean
+./gradlew :frame:base:publish
+```
+- oss: 对象存储框架
+```shell
+./gradlew :frame:oss:clean
+./gradlew :frame:oss:publish
+```
+- aab: 对google AAB api的封装,实现了AAB管理策略
+```shell
+./gradlew :frame:aab:clean
+./gradlew :frame:aab:publish
+```
+- coroutine: 并发编程基础库
+```shell
+./gradlew :frame:coroutine:clean
+./gradlew :frame:coroutine:publish
+```
+- data: 自定义数据结构
+```shell
+./gradlew :frame:data:clean
+./gradlew :frame:data:publish
+
+- debug: 开发工具
+```shell
+./gradlew :frame:debug:clean
+./gradlew :frame:debug:publish
+
+```
+- deviceid: 设备ID
+```shell
+./gradlew :frame:deviceid:clean
+./gradlew :frame:deviceid:publish
+```
+- download: 下载管理框架
+```shell
+./gradlew :frame:download:clean
+./gradlew :frame:download:publish
+```
+- effect: 动效播放框架
+```shell
+./gradlew :frame:effect:clean
+./gradlew :frame:effect:publish
+```
+- googleservice: google服务基础库
+```shell
+./gradlew :frame:googleservice:clean
+./gradlew :frame:googleservice:publish
+```
+- log: 日志管理
+```shell
+./gradlew :frame:log:clean
+./gradlew :frame:log:publish
+```
+- mvvm: mvvm能力拓展
+```shell
+./gradlew :frame:mvvm:clean
+./gradlew :frame:mvvm:publish
+```
+- network: 网络框架
+```shell
+./gradlew :frame:network:clean
+./gradlew :frame:network:publish
+```
+- security: 应用安全管理
+```shell
+./gradlew :frame:security:clean
+./gradlew :frame:security:publish
+```
+- spi: 服务发现框架
+```shell
+./gradlew :frame:spi:clean
+./gradlew :frame:spi:publish
+```
+- statistics: 统计框架
+```shell
+./gradlew :frame:statistics:clean
+./gradlew :frame:statistics:publish
+```
+- storage: 存储框架(file & sp)
+```shell
+./gradlew :frame:storage:clean
+./gradlew :frame:storage:publish
+```
+- util: 通用工具类
+```shell
+./gradlew :frame:util:clean
+./gradlew :frame:util:publish
+```
+- guide: 用户引导库
+```shell
+./gradlew :frame:guide:clean
+./gradlew :frame:guide:publish
+```
+- sound: 音效播放管理库
+```shell
+./gradlew :frame:sound:clean
+./gradlew :frame:sound:publish
+```
+- animplayer: VAP动效播放库
+```shell
+./gradlew :external:animplayer:clean
+./gradlew :external:animplayer:publish
+```
+- drawee: Fresco drawee
+```shell
+./gradlew :external:drawee:clean
+./gradlew :external:drawee:publish
+```
+- libcocos2dx: Cocos lib
+```shell
+./gradlew :external:libcocos2dx:clean
+./gradlew :external:libcocos2dx:publish
+```
+- qmui: tencent qmui
+```shell
+./gradlew :external:qmui:clean
+./gradlew :external:qmui:publish
+```
+- retrofit: retrofit
+```shell
+./gradlew :external:retrofit:clean
+./gradlew :external:retrofit:publish
+```
+- SVGAPlayer: SVGA播放库
+```shell
+./gradlew :external:SVGAPlayer:clean
+./gradlew :external:SVGAPlayer:publish
+```
+- game: 游戏接入框架
+```shell
+./gradlew :frame:game:clean
+./gradlew :frame:game:publish
+```
+- image: 图片框架
+```shell
+./gradlew :frame:image:clean
+./gradlew :frame:image:publish
+```
+- apm: 性能监控
+```shell
+./gradlew :frame:apm:clean
+./gradlew :frame:apm:publish
+```
+- asr: 语音识别
+```shell
+./gradlew :frame:asr:clean
+./gradlew :frame:asr:publish
+```
+- audio: 音频录制
+```shell
+./gradlew :frame:audio:clean
+./gradlew :frame:audio:publish
+```
+- crash: 崩溃收集
+```shell
+./gradlew :frame:crash:clean
+./gradlew :frame:crash:publish
+```
+- dot: 红点框架
+```shell
+./gradlew :frame:dot:clean
+./gradlew :frame:dot:publish
+```
+- locale: 本地化
+```shell
+./gradlew :frame:locale:clean
+./gradlew :frame:locale:publish
+```
+- push: 推送
+```shell
+./gradlew :frame:push:clean
+./gradlew :frame:push:publish
+```
+- tceffect: 腾讯云动效播放
+```shell
+./gradlew :frame:tceffect:clean
+./gradlew :frame:tceffect:publish
+```
+- media: 媒体服务
+```shell
+./gradlew :frame:media:clean
+./gradlew :frame:media:publish
+```
+- room: 信令房间框架
+```shell
+./gradlew :frame:room:clean
+./gradlew :frame:room:publish
+```
+- router: 路由框架
+```shell
+./gradlew :frame:router:router-api:clean
+./gradlew :frame:router:router-api:publish
+./gradlew :frame:router:router-annotation:clean
+./gradlew :frame:router:router-annotation:publish
+./gradlew :frame:router:router-compiler:clean
+./gradlew :frame:router:router-compiler:publish
+```
+- share: 分享
+```shell
+./gradlew :frame:share:clean
+./gradlew :frame:share:publish
+```
+- startup: 启动框架
+```shell
+./gradlew :frame:startup:clean
+./gradlew :frame:startup:publish
+```
+- AndroidAutoSize: Android屏幕适配
+```shell
+./gradlew :external:AndroidAutoSize:clean
+./gradlew :external:AndroidAutoSize:publish
+```
+- tcturing: 天御风控
+```shell
+./gradlew :external:tcturing:clean
+./gradlew :external:tcturing:publish
+```
+
+- commonui: 公共UI
+```shell
+./gradlew :frame:commonui:clean
+./gradlew :frame:commonui:publish
+```
+
+- effectpreview: 动效预览
+```shell
+./gradlew :frame:effectpreview:clean
+./gradlew :frame:effectpreview:publish
+```
+
+- trtc
+```shell
+./gradlew :frame:trtc:clean
+./gradlew :frame:trtc:publish
+```
+
+- trtcpro
+```shell
+./gradlew :frame:trtcpro:clean
+./gradlew :frame:trtcpro:publish
+```
+
+- Bom: 版本依赖管理
+```shell
+./gradlew :frame:bom:clean
+./gradlew :frame:bom:publish
+```

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 135 - 0
app/build.gradle

@@ -0,0 +1,135 @@
+plugins {
+    id 'com.android.application'
+    id 'org.jetbrains.kotlin.android'
+    id("com.google.devtools.ksp") version "1.9.10-1.0.13"//引入ksp插件
+    id 'org.jetbrains.kotlin.kapt'
+}
+
+android {
+    namespace 'com.adealink.frame'
+    compileSdk libs.versions.compileSdk.get().toInteger()
+
+    defaultConfig {
+//        applicationId "com.partyjoy.weparty"
+        minSdk libs.versions.minSdk.get().toInteger()
+        targetSdk libs.versions.targetSdk.get().toInteger()
+        versionCode 1
+        versionName "1.0"
+        multiDexEnabled true
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+    packagingOptions {
+        pickFirst 'META-INF/INDEX.LIST'
+        pickFirst 'META-INF/DEPENDENCIES'
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        targetCompatibility JavaVersion.VERSION_17
+        sourceCompatibility JavaVersion.VERSION_17
+        coreLibraryDesugaringEnabled true
+    }
+    kotlinOptions {
+        jvmTarget = JavaVersion.VERSION_17.majorVersion
+    }
+    buildFeatures {
+        viewBinding true
+    }
+    lintOptions {
+        abortOnError false
+    }
+    configurations.configureEach {
+        exclude group: 'com.facebook.fresco', module: 'drawee'
+    }
+//    configurations.configureEach {
+//        exclude group: 'com.google.code.gson', module: 'gson'
+//    }
+}
+
+dependencies {
+    api fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
+
+    //kotlin
+    implementation libs.kotlin.stdlib
+    coreLibraryDesugaring libs.desugar.jdk.libs
+
+    //androidx
+    implementation libs.androidx.core
+    implementation libs.androidx.core.ktx
+    implementation libs.androidx.appcompat
+
+    // android
+    implementation libs.android.material
+
+    implementation project(":frame:zero")
+    implementation project(":frame:base")
+    implementation project(":frame:aab")
+    implementation project(":frame:oss")
+    implementation project(":frame:coroutine")
+    implementation project(":frame:debug")
+    implementation project(":frame:deviceid")
+    implementation project(":frame:network")
+    implementation project(":frame:sound")
+    implementation project(":frame:statistics")
+    implementation project(":frame:log")
+    implementation project(":frame:storage")
+    implementation project(":frame:util")
+    implementation project(":frame:download")
+    implementation project(":frame:effect")
+    implementation project(":frame:mvvm")
+    implementation project(":frame:data")
+    implementation project(":frame:googleservice")
+    implementation project(":frame:guide")
+    implementation project(":frame:security")
+    implementation project(":frame:spi")
+    implementation project(":frame:game")
+    implementation project(":frame:image")
+    implementation project(":frame:apm")
+    implementation project(":frame:asr")
+    implementation project(":frame:audio")
+    implementation project(":frame:crash")
+    implementation project(":frame:dot")
+    implementation project(":frame:locale")
+    implementation project(":frame:push")
+    implementation project(":frame:tceffect")
+    implementation project(":frame:media")
+    implementation project(":frame:agorartc")
+    implementation project(":frame:trtc")
+    implementation project(":frame:volcrtc")
+    implementation project(":frame:zegortc")
+    implementation project(":frame:room")
+    implementation project(":frame:share")
+    implementation project(":frame:startup")
+    implementation project(":frame:effectpreview")
+
+    implementation project(':frame:router:router-annotation')
+    implementation project(':frame:router:router-api')
+    kapt project(':frame:router:router-compiler')
+
+    implementation project(':frame:cachebean:cache-bean-annotation')
+    implementation project(':frame:cachebean:cache-bean-api')
+    ksp project(':frame:cachebean:cache-bean-complier')
+
+    implementation libs.androidx.room.runtime
+    implementation libs.androidx.room.ktx
+    kapt libs.androidx.room.compiler
+
+    implementation project(":external:libcocos2dx")
+    implementation project(":external:retrofit")
+    implementation project(":external:SVGAPlayer")
+    implementation project(":external:animplayer")
+    implementation project(":external:drawee")
+    implementation project(":external:AndroidAutoSize")
+    implementation project(":external:tcturing")
+
+    //test
+    testImplementation libs.junit
+    androidTestImplementation libs.androidx.junit
+    androidTestImplementation libs.androidx.espresso.core
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
app/src/androidTest/java/com/adealink/frame/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.adealink.frame
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.adealink.frame", appContext.packageName)
+    }
+}

+ 49 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:name=".App"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.FrameBase"
+        android:usesCleartextTraffic="true"
+        tools:targetApi="31">
+
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="${applicationId}.GO_MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".svga.SvgaActivity" />
+        <activity android:name=".svga.SvgaEffectActivity" />
+        <activity android:name=".svga.recyclerview.SvgaRecyclerViewActivity" />
+        <activity android:name=".svga.recyclerview.SvgaEffectRecyclerViewActivity" />
+        <activity android:name=".svga.viewpager.SvgaViewpagerActivity" />
+        <activity android:name=".vap.VapActivity" />
+        <activity android:name=".vap.VapEffectActivity" />
+        <activity android:name=".stat.TestStatActivity" />
+        <activity android:name=".router.TestRouterActivity" />
+        <activity android:name=".gson.GsonActivity" />
+        <activity android:name=".image.ImageActivity" />
+        <activity
+            android:name=".dot.DotActivity"
+            android:theme="@style/AppTheme" />
+        <activity android:name=".sound.SoundActivity" />
+        <activity android:name=".effect.TestAnimViewActivity" />
+
+    </application>
+
+</manifest>

binární
app/src/main/assets/audio.zip


binární
app/src/main/assets/custom_gift_default_effect.svga


binární
app/src/main/assets/emotion.svga


binární
app/src/main/assets/svga_with_sound.svga


binární
app/src/main/assets/vap.mp4


+ 161 - 0
app/src/main/java/com/adealink/frame/App.kt

@@ -0,0 +1,161 @@
+package com.adealink.frame
+
+import android.app.Application
+import android.content.Context
+import android.util.Log
+import com.adealink.frame.coroutine.dispatcher.Dispatcher
+import com.adealink.frame.download.DownloadConfig
+import com.adealink.frame.download.IDownloadService
+import com.adealink.frame.download.createDownloadService
+import com.adealink.frame.download.initDownloadService
+import com.adealink.frame.effect.IEffect
+import com.adealink.frame.effect.TCEffectConfig
+import com.adealink.frame.effect.config.IEffectConfig
+import com.adealink.frame.effect.createEffect
+import com.adealink.frame.effect.initEffect
+import com.adealink.frame.file.FilePath
+import com.adealink.frame.image.config.IImageConfig
+import com.adealink.frame.image.constant.ImageFetchResultData
+import com.adealink.frame.image.imageService
+import com.adealink.frame.log.XLogHelper
+import com.adealink.frame.network.stat.NetMonitorStatEvent
+import com.adealink.frame.sound.createSoundPlayer
+import com.adealink.frame.sound.soundConfig
+import com.adealink.frame.stat.StatConfig
+import com.adealink.frame.statistics.report.initStat
+import com.adealink.frame.tceffect.TCEffectManager
+import com.adealink.frame.util.AppUtil
+import com.adealink.frame.util.ScreenAutoSizeUtil
+import com.google.androidgamesdk.BuildConfig
+import com.opensource.svgaplayer.control.SVGAManager
+import com.opensource.svgaplayer.control.SvgaConfig
+import com.opensource.svgaplayer.disk.DiskCacheParamsSupplier
+import com.opensource.svgaplayer.disk.DiskTrimStrategy
+import com.opensource.svgaplayer.executor.ExecutorsSupplier
+import com.opensource.svgaplayer.utils.ByteConstants
+import com.tencent.mars.xlog.Xlog
+import okhttp3.OkHttpClient
+import java.io.File
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+
+class App : Application() {
+
+    companion object {
+        lateinit var instance: App
+    }
+
+    val effect: IEffect by lazy {
+        createEffect(object : IEffectConfig {
+
+            override val ctx: Context
+                get() = AppUtil.appContext
+
+            override val videoCachePath: String
+                get() = ""
+
+        })
+    }
+
+    val soundPlayer by lazy { createSoundPlayer(soundConfig) }
+    val downloadService: IDownloadService by lazy { createDownloadService(DownloadConfig()) }
+
+    override fun attachBaseContext(base: Context?) {
+        instance = this
+        super.attachBaseContext(base)
+        AppUtil.init(this)
+        soundPlayer.prepare()
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+        initXLog()
+        initDownloadService { downloadService }
+        initSvga(this)
+        initStat { StatConfig() }
+        initEffect { effect }
+        initTCEffect()
+        imageService.init(object : IImageConfig {
+            override val context: Context
+                get() = AppUtil.appContext
+            override val imageHttpClient: OkHttpClient
+                get() = OkHttpClient.Builder()
+                    .connectTimeout(10_000L, TimeUnit.MILLISECONDS)
+                    .readTimeout(0, TimeUnit.MILLISECONDS)
+                    .writeTimeout(0, TimeUnit.MILLISECONDS)
+                    .build()
+            override val hostToResizeQuery: HashMap<String, String>
+                get() = hashMapOf()
+
+            override fun replaceUrlHost(oldUrl: String): String {
+                return ""
+            }
+
+            override fun report(data: ImageFetchResultData) {
+                val (url, host, size, duration, protocol, tlsVersion, awsXAmzCfId) = data
+                NetMonitorStatEvent(NetMonitorStatEvent.Action.TRAFFIC)
+                    .apply {
+                        uri to url
+                        this.host to host
+                        amount to size
+                        this.duration to duration
+                        this.protocol to protocol
+                        this.tlsVersion to tlsVersion
+                        xAmzCfId to awsXAmzCfId
+                    }
+                    .send()
+            }
+
+        })
+        ScreenAutoSizeUtil.init(this, true)
+    }
+
+    private fun initXLog() {
+        XLogHelper.initXLog(if (BuildConfig.DEBUG) Xlog.LEVEL_ALL else Xlog.LEVEL_INFO,
+            FilePath.logPath,
+            FilePath.logPath,
+            BuildConfig.DEBUG)
+    }
+
+    private fun initSvga(context: Context) {
+        Log.d("SVGA", "init, diskDir:${context.cacheDir}")
+        val config = SvgaConfig(
+            diskCacheParamsSupplier = object : DiskCacheParamsSupplier {
+                override fun getPath(): File = File(context.cacheDir, "svga")
+
+                override fun getDiskTrimStrategy(): DiskTrimStrategy {
+                    return object : DiskTrimStrategy {
+                        override fun maxSize(): Long = ByteConstants.MB * 100L
+                        override fun timeExpired(): Long = TimeUnit.DAYS.toMillis(10)
+                    }
+                }
+
+            },
+            executorsSupplier = object : ExecutorsSupplier {
+
+                override fun getBackgroundExecutor(): Executor {
+                    return Dispatcher.wenextThreadPoolExecutor
+                }
+
+                override fun getIOExecutor(): Executor {
+                    return Dispatcher.wenextThreadPoolExecutor
+                }
+
+                override fun getNetWorkExecutor(): Executor {
+                    return Dispatcher.wenextThreadPoolExecutor
+                }
+
+            },
+            debuggable = true,
+            designWidthPx = 750f,
+            enableSvgaChecker = true,
+            enableShowSvgaCheckResult = true
+        )
+        SVGAManager.init(context, config)
+    }
+
+    private fun initTCEffect() {
+        TCEffectManager.init(TCEffectConfig())
+    }
+
+}

+ 121 - 0
app/src/main/java/com/adealink/frame/MainActivity.kt

@@ -0,0 +1,121 @@
+package com.adealink.frame
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.debug.datasource.performanceDataController
+import com.adealink.frame.debug.floatkit.floatKitManager
+import com.adealink.frame.dot.DotActivity
+import com.adealink.frame.effect.TestAnimViewActivity
+import com.adealink.frame.gson.GsonActivity
+import com.adealink.frame.image.ImageActivity
+import com.adealink.frame.router.Router
+import com.adealink.frame.sound.SoundActivity
+import com.adealink.frame.stat.TestStatActivity
+import com.adealink.frame.svga.SvgaActivity
+import com.adealink.frame.svga.SvgaEffectActivity
+import com.adealink.frame.svga.recyclerview.SvgaEffectRecyclerViewActivity
+import com.adealink.frame.svga.recyclerview.SvgaRecyclerViewActivity
+import com.adealink.frame.svga.viewpager.SvgaViewpagerActivity
+import com.adealink.frame.vap.VapActivity
+import com.adealink.frame.vap.VapEffectActivity
+import com.wenext.frame.effectpreview.EffectPreviewSettingActivity
+
+class MainActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        findViewById<View>(R.id.tv_svga_test).setOnClickListener {
+            startActivity(Intent(this, SvgaActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_svga_effect_test).setOnClickListener {
+            startActivity(Intent(this, SvgaEffectActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_svga_recycler_view_test).setOnClickListener {
+            startActivity(Intent(this, SvgaRecyclerViewActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_svga_effect_recycler_view_test).setOnClickListener {
+            startActivity(Intent(this, SvgaEffectRecyclerViewActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_svga_viewpager_test).setOnClickListener {
+            startActivity(Intent(this, SvgaViewpagerActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_vap_test).setOnClickListener {
+            startActivity(Intent(this, VapActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_vap_effect_test).setOnClickListener {
+            startActivity(Intent(this, VapEffectActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_stat_test).setOnClickListener {
+            startActivity(Intent(this, TestStatActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_router_test).setOnClickListener {
+            Router.build(this, "TestRouterActivity")
+                .putExtra("extra_char_sequence", "test")
+                .putExtra("extra_char_sequence_null", "test")
+                .putExtra("extra_boolean", "test")
+                .putExtra("extra_boolean_null", "test")
+                .putExtra("extra_byte", "test")
+                .putExtra("extra_byte_null", "test")
+                .putExtra("extra_short", "test")
+                .putExtra("extra_short_null", "test")
+                .putExtra("extra_int", "test")
+                .putExtra("extra_int_null", "test")
+                .putExtra("extra_long", "test")
+                .putExtra("extra_long_null", "test")
+                .putExtra("extra_char", "test")
+                .putExtra("extra_char_null", "test")
+                .putExtra("extra_float", "test")
+                .putExtra("extra_float_null", "test")
+                .putExtra("extra_double", "test")
+                .putExtra("extra_double_null", "test")
+                .putExtra("extra_string", "test")
+                .putExtra("extra_string_null", "test")
+                .start()
+        }
+
+        findViewById<View>(R.id.tv_gson_test).setOnClickListener {
+            startActivity(Intent(this, GsonActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_image_test).setOnClickListener {
+            startActivity(Intent(this, ImageActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_dot_test).setOnClickListener {
+            startActivity(Intent(this, DotActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_sound_test).setOnClickListener {
+            startActivity(Intent(this, SoundActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_anim_view_test).setOnClickListener {
+            startActivity(Intent(this, TestAnimViewActivity::class.java))
+        }
+
+        findViewById<View>(R.id.tv_effect_preview_test).setOnClickListener {
+            EffectPreviewSettingActivity.start(this)
+        }
+
+        floatKitManager.install(application, true)
+        floatKitManager.onMainIconDoubleClick = {
+            Toast.makeText(application, "double click", Toast.LENGTH_SHORT).show()
+        }
+        performanceDataController.start()
+
+
+    }
+
+}

+ 8 - 0
app/src/main/java/com/adealink/frame/cache/UserInfo.kt

@@ -0,0 +1,8 @@
+package com.adealink.frame.cache
+
+/**
+ * Created by XiaoDongLin.
+ * Date: 2024/10/21
+ */
+class UserInfo {
+}

+ 62 - 0
app/src/main/java/com/adealink/frame/dot/DotActivity.kt

@@ -0,0 +1,62 @@
+package com.adealink.frame.dot
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.R
+import com.adealink.frame.image.view.NetworkImageView
+import com.facebook.drawee.drawable.ScalingUtils
+
+class DotActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_dot)
+
+        val normalDot = Dot(arrayListOf(), "normal")
+        normalDot.show(NormalDot(true))
+
+        val numDot = Dot(arrayListOf(), "num")
+        numDot.show(NumDot(1000))
+
+        val numDot2 = Dot(arrayListOf(), "num2")
+        numDot2.show(NumDot(9))
+
+        val textDot = Dot(arrayListOf(), "text")
+        textDot.show(TextDot("文本adb1234,.?", true))
+
+        // =================
+        findViewById<DotView>(R.id.normal_dot)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, normalDot)
+        }
+
+        findViewById<DotView>(R.id.num_dot)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, numDot)
+        }
+
+        findViewById<DotView>(R.id.num_dot2)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, numDot2)
+        }
+
+        findViewById<DotView>(R.id.text_dot)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, textDot)
+        }
+
+        // =================
+        findViewById<DotView>(R.id.small_normal_dot)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, normalDot)
+        }
+
+        findViewById<DotView>(R.id.small_num_dot)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, numDot)
+        }
+
+        findViewById<DotView>(R.id.small_num_dot2)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, numDot2)
+        }
+
+        findViewById<DotView>(R.id.small_text_dot)?.let { dotView ->
+            dotView.observeDot(this@DotActivity, textDot)
+        }
+
+    }
+}

+ 82 - 0
app/src/main/java/com/adealink/frame/download/DownloadConfig.kt

@@ -0,0 +1,82 @@
+package com.adealink.frame.download
+
+import com.adealink.frame.base.IError
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.base.WriterCallback
+import com.adealink.frame.download.config.IDownloadConfig
+import com.adealink.frame.download.constant.DownloadResultData
+import com.adealink.frame.network.stat.NetMonitorStatEvent
+import com.adealink.frame.oss.IOssService
+import com.adealink.frame.oss.data.OssResultData
+import com.adealink.frame.oss.data.UploadFile
+import com.adealink.frame.oss.listener.IOssListener
+import com.adealink.frame.storageService
+import okhttp3.OkHttpClient
+import java.util.concurrent.TimeUnit
+
+/**
+ * Created by sunxiaodong on 2021/10/10.
+ */
+class DownloadConfig : IDownloadConfig {
+    override val ossBucket: String
+        get() = ""
+    override val ossService: IOssService
+        get() = object: IOssService {
+            override val bucket: String
+                get() = ""
+
+            override suspend fun suspendUploadFile(
+                file: UploadFile,
+                l: IOssListener?
+            ): Rlt<String> {
+                return Rlt.Failed(IError())
+            }
+
+            override suspend fun suspendDownloadFile(
+                obj: String,
+                filePath: String,
+                l: IOssListener?
+            ): Rlt<OssResultData> {
+                return Rlt.Failed(IError())
+            }
+
+            override fun getUrlByPath(path: String): String {
+                return ""
+            }
+
+        }
+    override val downloadHttpClient: OkHttpClient
+        get() = OkHttpClient.Builder()
+        .connectTimeout(10_000L, TimeUnit.MILLISECONDS)
+        .readTimeout(0, TimeUnit.MILLISECONDS)
+        .writeTimeout(0, TimeUnit.MILLISECONDS)
+        .build()
+
+    override fun report(data: DownloadResultData) {
+        val (url, host, size, duration, protocol, tlsVersion, awsXAmzCfId) = data
+        NetMonitorStatEvent(NetMonitorStatEvent.Action.TRAFFIC)
+            .apply {
+                uri to url
+                this.host to host
+                amount to size
+                this.duration to duration
+                this.protocol to protocol
+                this.tlsVersion to tlsVersion
+                xAmzCfId to awsXAmzCfId
+            }
+            .send()
+    }
+
+    override fun getCacheRootDir(): String {
+        return storageService.file.getCacheRootDir()
+    }
+
+    override fun getTempUsingDir(): String {
+        return storageService.file.getTempUsingDir()
+    }
+
+    override suspend fun saveFile(savePath: String, callback: WriterCallback): Rlt<Any> {
+        return storageService.file.saveFile(savePath, callback)
+    }
+
+}

+ 15 - 0
app/src/main/java/com/adealink/frame/effect/TCEffectConfig.kt

@@ -0,0 +1,15 @@
+package com.adealink.frame.effect
+
+import com.adealink.frame.tceffect.config.ITCEffectConfig
+
+class TCEffectConfig : ITCEffectConfig {
+    override val licenceUrls: List<String> = listOf(
+        "https://license.vod2.myqcloud.com/license/v2/1314119829_1/v_cube.license",
+    )
+    override val oldLicence: String = "edbd15d01777ad43b422347e94e90757"
+    override val licence: String = "db3ac8d5972ef28cb62c9b7eabe76b39"
+    override val logEnable: Boolean = true
+    override fun replaceUrlHost(oldUrl: String): String {
+        return oldUrl
+    }
+}

+ 241 - 0
app/src/main/java/com/adealink/frame/effect/TestAnimViewActivity.kt

@@ -0,0 +1,241 @@
+package com.adealink.frame.effect
+
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup.LayoutParams
+import android.widget.ImageView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.lifecycle.lifecycleScope
+import com.adealink.frame.R
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.base.WriterCallback
+import com.adealink.frame.download.manager.downloadManager
+import com.adealink.frame.effect.data.AnimExtraConfig
+import com.adealink.frame.effect.data.DynamicAnimImage
+import com.adealink.frame.effect.data.DynamicAnimText
+import com.adealink.frame.effect.data.IAnimDynamicProvider
+import com.adealink.frame.effect.video.WeVideoView
+import com.adealink.frame.effect.view.EffectView
+import com.adealink.frame.effect.view.IWeAnimPlayListener
+import com.adealink.frame.effect.view.WeAnimView
+import com.adealink.frame.effect.view.WeEffectEntity
+import com.adealink.frame.ext.toRawUrl
+import com.adealink.frame.storage.constant.StorageFileWriteError
+import com.adealink.frame.storage.file.getCacheFilePathByUrl
+import com.adealink.frame.storageService
+import com.adealink.frame.util.AppUtil
+import com.adealink.frame.util.writeStreamToFile
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+class TestAnimViewActivity : AppCompatActivity() {
+    private lateinit var root: ConstraintLayout
+    private lateinit var animView: WeAnimView
+    private lateinit var videoView: WeVideoView
+    private lateinit var effectView: EffectView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_test_animview)
+        root = findViewById(R.id.root)
+        animView = findViewById(R.id.anim_view)
+        videoView = findViewById(R.id.video_view)
+        effectView = findViewById(R.id.effect_view)
+        findViewById<View>(R.id.btn_start_queue).setOnClickListener {
+            startEffectQueue()
+        }
+        findViewById<View>(R.id.btn_set).setOnClickListener {
+            setAnimViewConfig()
+        }
+        findViewById<View>(R.id.btn_start).setOnClickListener {
+            start()
+        }
+        findViewById<View>(R.id.btn_pause).setOnClickListener {
+            pause()
+        }
+        findViewById<View>(R.id.btn_stop).setOnClickListener {
+            stop()
+        }
+        findViewById<View>(R.id.btn_remove).setOnClickListener {
+            remove()
+        }
+    }
+
+    private fun startEffectQueue() {
+        lifecycleScope.launch {
+            val vapUrls = listOf("http://yoki-resource-cf.wenext.media/goods/car/1757477355740163651.mp4")
+            val giftUrls = listOf(
+                "https://wenext-lama.oss-accelerate.aliyuncs.com/video/100809698/1758070196066.mp4",
+                "https://wenext-lama.oss-accelerate.aliyuncs.com/video/102490307/1758053421631.mp4",
+                "https://wenext-lama.oss-accelerate.aliyuncs.com/video/102586390/1758053184385.mp4"
+            )
+            
+            val downloadTasks = giftUrls.map { url ->
+                async {
+                    downloadManager.downloadResource(url)
+                }
+            }
+            
+            val downloadResults = downloadTasks.map { it.await() }
+            
+            downloadResults.forEach { result ->
+                if (result is Rlt.Success) {
+                    val filePath = result.data.use()
+                    if (!filePath.isNullOrEmpty()) {
+                        effectView.add(WeEffectEntity(
+                            path = filePath,
+                            height = LayoutParams.WRAP_CONTENT,
+                            scaleType = ImageView.ScaleType.CENTER_CROP
+                        ))
+                        Log.d(TAG, "添加效果到队列: $filePath")
+                    }
+                } else {
+                    Log.e(TAG, "下载失败: ${result}")
+                }
+            }
+        }
+    }
+
+    private fun setAnimViewConfig() {
+        animView.setConfig(AnimExtraConfig(loop = -1, autoPlay = true, mute = true))
+        val svgaUrl =
+            "https://inchat-resource.wenext.media/activity/user_level/avatar_frame/lv_30_39.svga"
+        val vapUrl = "https://lama-cf.wenext-resource.chat/car/1685115483042941662.mp4"
+        val tcUrl =
+            "https://inchat-resource.wenext.media/background/goods/avatar/svip3_frame.mp4"
+        val mp4Url = "https://cdn.wenext.love/gift/brother.mp4"
+        val dynamicProvider = object : IAnimDynamicProvider {
+            override fun provideText(callback: (Map<String, DynamicAnimText>?) -> Unit) {
+                callback(
+                    hashMapOf(
+                        "lvavatar" to DynamicAnimText(
+                            "Lv.120",
+                            color = "#FFEBBD",
+                            fontSizePx = 36
+                        ),
+                        "username" to DynamicAnimText(
+                            "1-\uD83D\uDC47\uD83D\uDE01",
+                            color = "#FFEBBD",
+                            fontSizePx = 36
+                        )
+                    )
+                )
+            }
+
+            override fun provideImage(callback: (Map<String, DynamicAnimImage>?) -> Unit) {
+                callback(hashMapOf(
+                    "text1" to DynamicAnimImage {
+                        BitmapFactory.decodeResource(
+                            AppUtil.appContext.resources,
+                            R.drawable.level_glod_num_1
+                        )
+                    },
+                    "text2" to DynamicAnimImage {
+                        BitmapFactory.decodeResource(
+                            AppUtil.appContext.resources,
+                            R.drawable.level_glod_num_0
+                        )
+                    },
+                    "number" to DynamicAnimImage {
+                        BitmapFactory.decodeResource(
+                            AppUtil.appContext.resources,
+                            R.drawable.level_glod_num_0
+                        )
+                    }
+                ))
+            }
+        }
+        animView.setListener(object : IWeAnimPlayListener {
+            override fun onPlayStart() {
+                super.onPlayStart()
+                Log.d(TAG, "onPlayStart")
+            }
+
+            override fun onPlayEnd() {
+                super.onPlayEnd()
+                Log.d(TAG, "onPlayEnd")
+            }
+
+            override fun onPlayError(errorCode: String) {
+                super.onPlayError(errorCode)
+                Log.d(TAG, "onPlayError,${errorCode}")
+            }
+        })
+        animView.setUrl(tcUrl, dynamicProvider)
+    }
+
+    private fun setVideoViewConfig() {
+        if (videoView.parent == null) {
+            root.addView(videoView)
+        }
+        videoView.setAutoPlay(true)
+        videoView.setLoopCount(-1)
+        videoView.setShowControllerBar(false)
+        videoView.setUri(R.raw.brother.toRawUrl())
+    }
+
+    private fun start() {
+        animView.startPlay()
+        videoView.startPlay()
+    }
+
+    private fun pause() {
+        animView.pausePlay()
+        videoView.pausePlay()
+    }
+
+    private fun stop() {
+        animView.stopPlay()
+        videoView.stopPlay()
+    }
+
+    private fun remove() {
+        root.removeView(animView)
+        root.removeView(videoView)
+    }
+
+    private suspend fun copyAssetToCache(assetName: String): String {
+        return suspendCancellableCoroutine { continuation ->
+            val assetUri = buildAssetUri(assetName)
+            val filePath = getCacheFilePathByUrl(assetUri.toString())
+            val file = storageService.file.createWeFile(filePath)
+
+            val resultPath = if (file.exists()) {
+                filePath
+            } else {
+                val result = file.save(object : WriterCallback {
+                    override fun syncWriteToFile(savePath: String): Rlt<Any> {
+                        return if (writeStreamToFile(
+                                AppUtil.appContext.assets.open(assetName),
+                                savePath
+                            )
+                        ) {
+                            Rlt.Success(savePath)
+                        } else {
+                            Rlt.Failed(StorageFileWriteError(""))
+                        }
+                    }
+                })
+                if (result is Rlt.Success) result.data as String else ""
+            }
+
+            if (continuation.isActive) {
+                continuation.resume(resultPath)
+            }
+        }
+    }
+
+    private fun buildAssetUri(assetName: String): Uri {
+        return Uri.parse("file:///android_asset/$assetName")
+    }
+
+    companion object {
+        const val TAG = "TestAnimViewActivity"
+    }
+}

+ 125 - 0
app/src/main/java/com/adealink/frame/effect/VideoTimeBar.kt

@@ -0,0 +1,125 @@
+package com.adealink.frame.effect
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.ViewParent
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.ui.TimeBar
+import com.adealink.frame.databinding.LayoutVideoTimeBarBinding
+import com.adealink.frame.log.Log
+import com.adealink.frame.util.DisplayUtil
+import java.util.concurrent.CopyOnWriteArraySet
+
+@UnstableApi
+class VideoTimeBar @JvmOverloads constructor(
+    context: Context, attrs: AttributeSet? = null
+) : ConstraintLayout(context, attrs), TimeBar {
+
+    private val binding = LayoutVideoTimeBarBinding.inflate(LayoutInflater.from(context), this)
+
+    private val listeners: CopyOnWriteArraySet<TimeBar.OnScrubListener> = CopyOnWriteArraySet()
+    private var position: Long = 0
+    private var bufferedPosition: Long = 0
+    private var duration: Long = 0
+
+    init {
+        binding.seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
+            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
+                if (fromUser) {
+
+                }
+            }
+
+            override fun onStartTrackingTouch(seekBar: SeekBar?) {
+//                seekBar?.progress?.let {
+//                    startScrubbing(it.toLong())
+//                }
+            }
+
+            override fun onStopTrackingTouch(seekBar: SeekBar?) {
+                seekBar?.progress?.let {
+                    stopScrubbing(it.toLong())
+                }
+            }
+        })
+    }
+
+    private fun stopScrubbing(position: Long) {
+        val parent: ViewParent? = parent
+        parent?.requestDisallowInterceptTouchEvent(false)
+        invalidate()
+        for (listener: TimeBar.OnScrubListener in listeners) {
+            listener.onScrubStop(this, position, false)
+        }
+    }
+
+    override fun addListener(listener: TimeBar.OnScrubListener) {
+        listeners.add(listener)
+    }
+
+    override fun removeListener(listener: TimeBar.OnScrubListener) {
+        listeners.remove(listener)
+    }
+
+    override fun setKeyTimeIncrement(time: Long) {
+        //Ntd.
+    }
+
+    override fun setKeyCountIncrement(count: Int) {
+        //Ntd.
+    }
+
+    override fun setPosition(position: Long) {
+        Log.d(TAG, "setPosition:$position")
+        if (this.position == position) {
+            return
+        }
+        this.position = position
+        update()
+    }
+
+    override fun setBufferedPosition(bufferedPosition: Long) {
+        Log.d(TAG, "setBufferedPosition:$bufferedPosition")
+        if (this.bufferedPosition == bufferedPosition) {
+            return
+        }
+        this.bufferedPosition = bufferedPosition
+        update()
+    }
+
+    override fun setDuration(duration: Long) {
+        Log.d(TAG, "setDuration:$duration")
+        if (this.duration == duration) {
+            return
+        }
+        this.duration = duration
+        update()
+    }
+
+    override fun getPreferredUpdateDelay(): Long {
+        val timeBarWidthDp: Int = DisplayUtil.px2dp(binding.seekBar.width.toFloat())
+        return if ((timeBarWidthDp == 0) || (duration == 0L) || (duration == androidx.media3.common.C.TIME_UNSET)) {
+            Long.Companion.MAX_VALUE
+        } else {
+            duration / timeBarWidthDp
+        }
+    }
+
+    override fun setAdGroupTimesMs(adGroupTimesMs: LongArray?, playedAdGroups: BooleanArray?, adGroupCount: Int) {
+        //Ntd.
+    }
+
+    private fun update() {
+        binding.seekBar.max = duration.toInt()
+        binding.seekBar.progress = position.toInt()
+        binding.seekBar.secondaryProgress = bufferedPosition.toInt()
+    }
+
+    companion object {
+        const val TAG = "JackarooVideoTimeBar"
+    }
+}

+ 21 - 0
app/src/main/java/com/adealink/frame/file/FilePath.kt

@@ -0,0 +1,21 @@
+package com.adealink.frame.file
+
+import com.adealink.frame.storage.file.Path
+import com.adealink.frame.storage.file.privateFilesPath
+import com.adealink.frame.storage.file.publicFilesPath
+import com.adealink.frame.util.AppUtil
+import com.adealink.frame.util.isExternalStorageAvailable
+
+/**
+ * Created by sunxiaodong on 2021/4/8.
+ */
+object FilePath {
+
+    val spPath by lazy { Path("sp", privateFilesPath).absolutePath }
+
+    val logPath by lazy { Path("xlog", privateFilesPath).absolutePath }
+
+    val xlogZipPath by lazy { Path("xlogz", publicFilesPath).absolutePath }
+
+    val videoPath by lazy { Path("video", publicFilesPath).absolutePath }
+}

+ 72 - 0
app/src/main/java/com/adealink/frame/gson/GsonActivity.kt

@@ -0,0 +1,72 @@
+package com.adealink.frame.gson
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.data.json.froJsonErrorNull
+import com.adealink.frame.log.Log
+import com.google.gson.annotations.GsonNullable
+import com.google.gson.annotations.Must
+import com.google.gson.annotations.SerializedName
+
+/**
+ * Created By: duxuefu
+ * Creation Date: 2025/5/26 14:04
+ * Description:
+ */
+class GsonActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        Log.setLevel(0, false)
+
+        val jsonStr = """{"test":"1122"}"""
+        try {
+            val obj = froJsonErrorNull(jsonStr, TestBean::class.java)
+            obj?.let {
+                Log.d("dxf", "test: ${obj.test}")
+                Log.d("dxf", "value: ${obj.value}")
+                Log.d("dxf", "progress: ${obj.progress}")
+                Log.d("dxf", "type: ${obj.type}")
+                Log.d("dxf", "list: ${obj.list}")
+                Log.d("dxf", "userBean: ${obj.userBean}")
+            }
+        } catch (e: Exception) {
+            Log.e("dxf", "异常, e:$e")
+        }
+
+//        try {
+//            val obj = TestBean(value = 111, userBean = UserBean())
+//            val jsonStr1 = toJsonErrorNull(obj)
+//            Log.d("dxf", "toJsonErrorNull jsonStr: $jsonStr1")
+//        } catch (e: Exception) {
+//            Log.e("dxf", "异常, e:$e")
+//        }
+
+    }
+
+}
+
+data class TestBean(
+    @Must
+    @SerializedName("test")
+    val test: String,
+    val value: Int,
+    val progress: Int = 100,
+    @SerializedName("type")
+    val type: Type,
+    @GsonNullable
+    @SerializedName("list")
+    val list: List<Long>,
+    @SerializedName("userBean")
+    val userBean: UserBean,
+)
+
+data class UserBean(
+    @SerializedName("name")
+    val test: String = "Joy",
+)
+
+enum class Type {
+    DEFAULT,
+}

+ 26 - 0
app/src/main/java/com/adealink/frame/image/ImageActivity.kt

@@ -0,0 +1,26 @@
+package com.adealink.frame.image
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.R
+import com.adealink.frame.image.view.NetworkImageView
+import com.facebook.drawee.drawable.ScalingUtils
+
+class ImageActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_image)
+
+        findViewById<NetworkImageView>(R.id.v_image).apply {
+            setIsAsCircle(true)
+            hierarchy.actualImageScaleType = ScalingUtils.ScaleType.FIT_CENTER
+            setDefaultImageResId(R.mipmap.room_micseat_seat_unlock_ic)
+        }
+
+//        findViewById<NetworkImageView>(R.id.v_image2).apply {
+//            hierarchy.actualImageScaleType = ScalingUtils.ScaleType.FIT_CENTER
+//            setDefaultImageResId(R.mipmap.room_micseat_seat_unlock_ic)
+//        }
+    }
+}

+ 57 - 0
app/src/main/java/com/adealink/frame/media/MediaConfig.kt

@@ -0,0 +1,57 @@
+package com.adealink.frame.media
+
+import android.content.Context
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.media.config.IMediaConfig
+import com.adealink.frame.media.data.RtcType
+import com.adealink.frame.media.rtc.IRtcCallback
+import com.adealink.frame.media.rtc.IRtcEngine
+import com.adealink.frame.media.rtc.RtcConfig
+import com.adealink.frame.util.AppUtil
+import com.wenext.agorartc.AgoraRtcEngine
+import com.wenext.trtc.TRtcEngine
+import com.wenext.volcrtc.VolcRtcEngine
+import com.wenext.zegortc.ZegoRtcEngine
+
+/**
+ * Created by sunxiaodong on 2025/4/18.
+ */
+class MediaConfig : IMediaConfig {
+
+    override val context: Context
+        get() = AppUtil.appContext
+    override val lowBitrate: Boolean = false
+
+    override fun createRtcEngine(rtcType: RtcType, callback: IRtcCallback): IRtcEngine? {
+        return when (rtcType) {
+            RtcType.AGORA_RTC -> {
+                AgoraRtcEngine(RtcConfig("", AppUtil.appContext, ""), callback)
+            }
+
+            RtcType.T_RTC -> {
+                TRtcEngine(RtcConfig("", AppUtil.appContext, ""), callback)
+            }
+
+            RtcType.VOLC_RTC -> {
+                VolcRtcEngine(RtcConfig("", AppUtil.appContext, ""), callback)
+            }
+
+            RtcType.ZEGO_RTC -> {
+                ZegoRtcEngine(RtcConfig("", AppUtil.appContext, ""), callback)
+            }
+        }
+    }
+
+    override suspend fun getChannelToken(channel: String, rtcType: RtcType): Rlt<String> {
+        TODO("Not yet implemented")
+    }
+
+    override fun onRTCQualityGood() {
+
+    }
+
+    override fun onRTCQualityBad() {
+
+    }
+
+}

+ 140 - 0
app/src/main/java/com/adealink/frame/router/TestRouterActivity.kt

@@ -0,0 +1,140 @@
+package com.adealink.frame.router
+
+import android.os.Bundle
+import android.util.Log
+import android.widget.FrameLayout
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.router.annotation.BindExtra
+import com.adealink.frame.router.annotation.RouterUri
+
+@RouterUri(
+    path = ["TestRouterActivity"],
+    desc = "测试RouterActivity"
+)
+class TestRouterActivity : AppCompatActivity() {
+
+    @BindExtra(name = "extra_char_sequence")
+    var charSequenceVar: CharSequence = "123456"
+
+    @BindExtra(name = "extra_char_sequence_null")
+    var charSequenceVarNull: CharSequence? = "123456"
+
+    @BindExtra(name = "extra_boolean")
+    var booleanVar: Boolean = true
+
+    @BindExtra(name = "extra_boolean_null")
+    var booleanVarNull: Boolean? = true
+
+    @BindExtra(name = "extra_byte")
+    var byteVar: Byte = 123
+
+    @BindExtra(name = "extra_byte_null")
+    var byteVarNull: Byte? = 123
+
+    @BindExtra(name = "extra_short")
+    var shortVar: Short = 123
+
+    @BindExtra(name = "extra_short_null")
+    var shortVarNull: Short? = 123
+
+    @BindExtra(name = "extra_int")
+    var intVar: Int = 123
+
+    @BindExtra(name = "extra_int_null")
+    var intVarNull: Int? = 123
+
+    @BindExtra(name = "extra_long")
+    var longVar: Long = 123
+
+    @BindExtra(name = "extra_long_null")
+    var longVarNull: Long? = 123
+
+    @BindExtra(name = "extra_char")
+    var charVar: Char = '1'
+
+    @BindExtra(name = "extra_char_null")
+    var charVarNull: Char? = '1'
+
+    @BindExtra(name = "extra_float")
+    var floatVar: Float = 123f
+
+    @BindExtra(name = "extra_float_null")
+    var floatVarNull: Float? = 123f
+
+    @BindExtra(name = "extra_double")
+    var doubleVar: Double = 123.0
+
+    @BindExtra(name = "extra_double_null")
+    var doubleVarNull: Double? = 123.0
+
+    @BindExtra(name = "extra_string")
+    var stringVar: String = "123"
+
+    @BindExtra(name = "extra_string_null")
+    var stringVarNull: String? = "123"
+
+    @BindExtra(name = "extra_bundle")
+    var bundleVar: Bundle? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        Router.bind(this)
+        val fl = FrameLayout(this).apply {
+            id = com.adealink.frame.R.id.fl_content
+        }
+        setContentView(fl)
+
+        Log.d("Router-Activity", "charSequenceVar: $charSequenceVar")
+        Log.d("Router-Activity", "charSequenceVarNull: $charSequenceVarNull")
+        Log.d("Router-Activity", "booleanVar: $booleanVar")
+        Log.d("Router-Activity", "booleanVarNull: $booleanVarNull")
+        Log.d("Router-Activity", "byteVar: $byteVar")
+        Log.d("Router-Activity", "byteVarNull: $byteVarNull")
+        Log.d("Router-Activity", "shortVar: $shortVar")
+        Log.d("Router-Activity", "shortVarNull: $shortVarNull")
+        Log.d("Router-Activity", "intVar: $intVar")
+        Log.d("Router-Activity", "intVarNull: $intVarNull")
+        Log.d("Router-Activity", "longVar: $longVar")
+        Log.d("Router-Activity", "longVarNull: $longVarNull")
+        Log.d("Router-Activity", "charVar: $charVar")
+        Log.d("Router-Activity", "charVarNull: $charVarNull")
+        Log.d("Router-Activity", "floatVar: $floatVar")
+        Log.d("Router-Activity", "floatVarNull: $floatVarNull")
+        Log.d("Router-Activity", "doubleVar: $doubleVar")
+        Log.d("Router-Activity", "doubleVarNull: $doubleVarNull")
+        Log.d("Router-Activity", "stringVar: $stringVar")
+        Log.d("Router-Activity", "stringVarNull: $stringVarNull")
+        Log.d("Router-Activity", "stringVar: $stringVar")
+        Log.d("Router-Activity", "bundleVar: $bundleVar")
+
+
+        Router.getRouterInstance<TestRouterFragment>("TestRouterFragment")?.let {
+            it.arguments = Bundle().apply {
+                putString("extra_char_sequence", "test")
+                putString("extra_char_sequence_null", "test")
+                putString("extra_boolean", "test")
+                putString("extra_boolean_null", "test")
+                putString("extra_byte", "test")
+                putString("extra_byte_null", "test")
+                putString("extra_short", "test")
+                putString("extra_short_null", "test")
+                putString("extra_int", "test")
+                putString("extra_int_null", "test")
+                putString("extra_long", "test")
+                putString("extra_long_null", "test")
+                putString("extra_char", "test")
+                putString("extra_char_null", "test")
+                putString("extra_float", "test")
+                putString("extra_float_null", "test")
+                putString("extra_double", "test")
+                putString("extra_double_null", "test")
+                putString("extra_string", "test")
+                putString("extra_string_null", "test")
+            }
+            supportFragmentManager
+                .beginTransaction()
+                .add(com.adealink.frame.R.id.fl_content, it, "TestRouterFragment")
+                .commitAllowingStateLoss()
+        }
+    }
+}

+ 106 - 0
app/src/main/java/com/adealink/frame/router/TestRouterFragment.kt

@@ -0,0 +1,106 @@
+package com.adealink.frame.router
+
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import com.adealink.frame.router.annotation.BindExtra
+import com.adealink.frame.router.annotation.RouterUri
+
+@RouterUri(
+    path = ["TestRouterFragment"],
+    desc = "测试RouterFragment"
+)
+class TestRouterFragment : Fragment() {
+
+    @BindExtra(name = "extra_char_sequence")
+    var charSequenceVar: CharSequence = "123456"
+
+    @BindExtra(name = "extra_char_sequence_null")
+    var charSequenceVarNull: CharSequence? = "123456"
+
+    @BindExtra(name = "extra_boolean")
+    var booleanVar: Boolean = true
+
+    @BindExtra(name = "extra_boolean_null")
+    var booleanVarNull: Boolean? = true
+
+    @BindExtra(name = "extra_byte")
+    var byteVar: Byte = 123
+
+    @BindExtra(name = "extra_byte_null")
+    var byteVarNull: Byte? = 123
+
+    @BindExtra(name = "extra_short")
+    var shortVar: Short = 123
+
+    @BindExtra(name = "extra_short_null")
+    var shortVarNull: Short? = 123
+
+    @BindExtra(name = "extra_int")
+    var intVar: Int = 123
+
+    @BindExtra(name = "extra_int_null")
+    var intVarNull: Int? = 123
+
+    @BindExtra(name = "extra_long")
+    var longVar: Long = 123
+
+    @BindExtra(name = "extra_long_null")
+    var longVarNull: Long? = 123
+
+    @BindExtra(name = "extra_char")
+    var charVar: Char = '1'
+
+    @BindExtra(name = "extra_char_null")
+    var charVarNull: Char? = '1'
+
+    @BindExtra(name = "extra_float")
+    var floatVar: Float = 123f
+
+    @BindExtra(name = "extra_float_null")
+    var floatVarNull: Float? = 123f
+
+    @BindExtra(name = "extra_double")
+    var doubleVar: Double = 123.0
+
+    @BindExtra(name = "extra_double_null")
+    var doubleVarNull: Double? = 123.0
+
+    @BindExtra(name = "extra_string")
+    var stringVar: String = "123"
+
+    @BindExtra(name = "extra_string_null")
+    var stringVarNull: String? = "123"
+
+    @BindExtra(name = "extra_bundle")
+    var bundleVar: Bundle? = null
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        Router.bind(this)
+
+        Log.d("Router-Fragment", "charSequenceVar: $charSequenceVar")
+        Log.d("Router-Fragment", "charSequenceVarNull: $charSequenceVarNull")
+        Log.d("Router-Fragment", "booleanVar: $booleanVar")
+        Log.d("Router-Fragment", "booleanVarNull: $booleanVarNull")
+        Log.d("Router-Fragment", "byteVar: $byteVar")
+        Log.d("Router-Fragment", "byteVarNull: $byteVarNull")
+        Log.d("Router-Fragment", "shortVar: $shortVar")
+        Log.d("Router-Fragment", "shortVarNull: $shortVarNull")
+        Log.d("Router-Fragment", "intVar: $intVar")
+        Log.d("Router-Fragment", "intVarNull: $intVarNull")
+        Log.d("Router-Fragment", "longVar: $longVar")
+        Log.d("Router-Fragment", "longVarNull: $longVarNull")
+        Log.d("Router-Fragment", "charVar: $charVar")
+        Log.d("Router-Fragment", "charVarNull: $charVarNull")
+        Log.d("Router-Fragment", "floatVar: $floatVar")
+        Log.d("Router-Fragment", "floatVarNull: $floatVarNull")
+        Log.d("Router-Fragment", "doubleVar: $doubleVar")
+        Log.d("Router-Fragment", "doubleVarNull: $doubleVarNull")
+        Log.d("Router-Fragment", "stringVar: $stringVar")
+        Log.d("Router-Fragment", "stringVarNull: $stringVarNull")
+        Log.d("Router-Fragment", "stringVar: $stringVar")
+        Log.d("Router-Fragment", "bundleVar: $bundleVar")
+    }
+}

+ 26 - 0
app/src/main/java/com/adealink/frame/sound/Sound.kt

@@ -0,0 +1,26 @@
+package com.adealink.frame.sound
+
+enum class Sound(val soundName: String) {
+    LOVE_STORE("love_store.mp3"),
+    BACKGROUND_MUSIC("backgroundMusic.mp3"),
+    BUTTON("btn.mp3"),
+    TIP("tip.mp3"),
+    WIN("win.mp3"),
+    LOST("lost.mp3"),
+    COINS_COLLECT("coinsCollect.mp3"),
+    TREASURE_CHEAT_OPEN_FULL("treasureCheatOpenFull.mp3"),
+    TREASURE_CHEAT_OPEN_EMPTY("treasureCheatOpenEmpty.mp3"),
+    SHOP_OPEN("shopOpen.mp3"),
+    READY_GO("readyGo.mp3"),
+    GET_TASK_REWARD("getTaskReward.mp3"),
+    GET_LEVEL_REWARD("getLevelReward.mp3"),
+    LEVEL_UPGRADE("levelUpgrade.mp3"),
+    BUY_SUCCESS("buy_success.mp3"),
+    SWITCH_PREVIEW("switch_preview.mp3"),
+    DICE_6("dice_6.mp3"),
+    DICE_ROLL("roll.mp3"),
+    COIN("coin.mp3"),
+    GAME_RESULT_LIKE("gameResultLike.mp3"),
+    COUPLE_PROPOSAL("coupleProposal.mp3"),
+    OPEN_TREASURE_CHEST("openTreasureChest.mp3"), //打开宝箱页
+}

+ 78 - 0
app/src/main/java/com/adealink/frame/sound/SoundActivity.kt

@@ -0,0 +1,78 @@
+package com.adealink.frame.sound
+
+import android.os.Bundle
+import android.widget.Button
+import android.widget.RadioGroup
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.App
+import com.adealink.frame.R
+import com.adealink.frame.sound.data.PlayerType
+import com.adealink.frame.sound.data.SOUND_LOOP_INFINITE
+
+class SoundActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_sound)
+
+        var soundType = PlayerType.SOUND_POOL
+
+        findViewById<RadioGroup>(R.id.rg_type).apply {
+            check(R.id.rb_sound_pool) // 默认选中SoundPool
+            setOnCheckedChangeListener { _, checkedId ->
+                when (checkedId) {
+                    R.id.rb_media_sdk -> {
+                        soundType = PlayerType.MEDIA_SDK
+                    }
+
+                    R.id.rb_sound_pool -> {
+                        soundType = PlayerType.SOUND_POOL
+                    }
+
+                    R.id.rb_system_media -> {
+                        soundType = PlayerType.SYSTEM_MEDIA
+                    }
+                }
+            }
+        }
+
+        // 按照音效名字播放
+        findViewById<Button>(R.id.btn_play).apply {
+            setOnClickListener {
+                App.instance.soundPlayer.play(Sound.BACKGROUND_MUSIC.soundName, loop = SOUND_LOOP_INFINITE, soundType)
+            }
+        }
+
+        findViewById<Button>(R.id.btn_pause).apply {
+            setOnClickListener {
+                App.instance.soundPlayer.pause(Sound.BACKGROUND_MUSIC.soundName)
+            }
+        }
+
+        findViewById<Button>(R.id.btn_stop).apply {
+            setOnClickListener {
+                App.instance.soundPlayer.stop(Sound.BACKGROUND_MUSIC.soundName)
+            }
+        }
+
+        // 按照音效路径播放
+        val soundPath = soundConfig.audioPath.plus(Sound.COINS_COLLECT.soundName)
+        findViewById<Button>(R.id.btn_play_path).apply {
+            setOnClickListener {
+                App.instance.soundPlayer.playPath(soundPath, loop = SOUND_LOOP_INFINITE, soundType)
+            }
+        }
+
+        findViewById<Button>(R.id.btn_pause_path).apply {
+            setOnClickListener {
+                App.instance.soundPlayer.pause(soundPath)
+            }
+        }
+
+        findViewById<Button>(R.id.btn_stop_path).apply {
+            setOnClickListener {
+                App.instance.soundPlayer.stop(soundPath)
+            }
+        }
+    }
+}

+ 63 - 0
app/src/main/java/com/adealink/frame/sound/SoundPlayerConfig.kt

@@ -0,0 +1,63 @@
+package com.adealink.frame.sound
+
+import com.adealink.frame.sound.config.ISoundPlayerConfig
+import com.adealink.frame.sound.manager.soundManager
+import com.adealink.frame.sound.mediasdk.IMediaSdkSoundEffect
+import com.adealink.frame.util.AppUtil
+import java.io.File
+
+val soundConfig by lazy { SoundPlayerConfig() }
+
+class SoundPlayerConfig : ISoundPlayerConfig, IMediaSdkSoundEffect {
+
+    override val mediaSdkSoundEffect: IMediaSdkSoundEffect
+        get() = this
+    override val audioPath: String
+        get() = "${AppUtil.appContext.filesDir}${File.separator}audio${File.separator}"
+    override val audioZipFileName: String
+        get() = "audio.zip"
+    override val defaultVolume: Int
+        get() = 60
+
+    override fun isUseMediaSdk(): Boolean {
+        return false
+    }
+
+    override fun isCanPlaySound(): Boolean {
+        return soundManager.isSoundOpen()
+    }
+
+    override fun isCanPlayMusic(): Boolean {
+        return soundManager.isMusicOpen()
+    }
+
+    override fun isInForeGround(): Boolean {
+        return !AppUtil.background
+    }
+
+    override fun setSoundEffectVolume(soundId: Int, volume: Int) {
+//        App.instance.mediaService.setSoundEffectVolume(soundId, volume)
+    }
+
+    override fun setSoundEffectRate(soundId: Int, rate: Float) {
+
+    }
+
+    override fun playSoundEffect(filePath: String, loopCount: Int): Int {
+        //return App.instance.mediaService.playSoundEffect(filePath, loopCount)
+        return 0
+    }
+
+    override fun pauseSoundEffect(effectId: Int) {
+//        App.instance.mediaService.pauseSoundEffect(effectId)
+    }
+
+    override fun resumeSoundEffect(effectId: Int) {
+//        App.instance.mediaService.resumeSoundEffect(effectId)
+    }
+
+    override fun stopSoundEffect(effectId: Int) {
+//        App.instance.mediaService.stopSoundEffect(effectId)
+    }
+
+}

+ 11 - 0
app/src/main/java/com/adealink/frame/sound/manager/ISoundListener.kt

@@ -0,0 +1,11 @@
+package com.adealink.frame.sound.manager
+
+interface ISoundListener {
+
+    fun onMusicOpenChanged(open: Boolean)
+
+    fun onSoundOpenChanged(open: Boolean)
+
+    fun onVibrationOpenChanged(open: Boolean)
+
+}

+ 31 - 0
app/src/main/java/com/adealink/frame/sound/manager/ISoundManager.kt

@@ -0,0 +1,31 @@
+package com.adealink.frame.sound.manager
+
+import com.adealink.frame.sound.Sound
+
+interface ISoundManager {
+
+    fun setMusicOpen(open: Boolean)
+
+    fun switchMusic()
+
+    fun isMusicOpen(): Boolean
+
+    fun setSoundOpen(open: Boolean)
+
+    fun switchSound()
+
+    fun isSoundOpen(): Boolean
+
+    fun setVibration(open: Boolean)
+
+    fun switchVibration()
+
+    fun isVibrationOpen(): Boolean
+
+    fun playSound(sound: Sound)
+
+    fun addListener(listener: ISoundListener)
+
+    fun removeListener(listener: ISoundListener)
+
+}

+ 81 - 0
app/src/main/java/com/adealink/frame/sound/manager/SoundManager.kt

@@ -0,0 +1,81 @@
+package com.adealink.frame.sound.manager
+
+import com.adealink.frame.App
+import com.adealink.frame.data.collections.ConcurrentList
+import com.adealink.frame.sound.Sound
+
+
+val soundManager: ISoundManager by lazy { SoundManager() }
+
+class SoundManager : ISoundManager {
+
+    private var musicOpen = true
+    private var soundOpen = true
+    private var vibrationOpen = true
+    private val listeners = ConcurrentList<ISoundListener>()
+
+
+    override fun setMusicOpen(open: Boolean) {
+        musicOpen = open
+        listeners.dispatch { it.onMusicOpenChanged(musicOpen) }
+    }
+
+    override fun switchMusic() {
+        setMusicOpen(!musicOpen)
+    }
+
+    override fun isMusicOpen(): Boolean {
+        return musicOpen
+    }
+
+    override fun setSoundOpen(open: Boolean) {
+        soundOpen = open
+        if(!open){
+            //关闭音效
+            closeAllSound(false)
+        }
+        listeners.dispatch { it.onSoundOpenChanged(soundOpen) }
+    }
+
+    private fun closeAllSound(closeBgMusic: Boolean) {
+        if (closeBgMusic) {
+            App.instance.soundPlayer.stopAll()
+        } else {
+            App.instance.soundPlayer.stopAll(Sound.BACKGROUND_MUSIC.soundName)
+        }
+    }
+
+    override fun switchSound() {
+        setSoundOpen(!soundOpen)
+    }
+
+    override fun isSoundOpen(): Boolean {
+        return soundOpen
+    }
+
+    override fun setVibration(open: Boolean) {
+        vibrationOpen = open
+        listeners.dispatch { it.onVibrationOpenChanged(vibrationOpen) }
+    }
+
+    override fun switchVibration() {
+        setVibration(!vibrationOpen)
+    }
+
+    override fun isVibrationOpen(): Boolean {
+        return vibrationOpen
+    }
+
+    override fun playSound(sound: Sound) {
+        App.instance.soundPlayer.play(sound.soundName)
+    }
+
+    override fun addListener(listener: ISoundListener) {
+        listeners.add(listener)
+    }
+
+    override fun removeListener(listener: ISoundListener) {
+        listeners.remove(listener)
+    }
+
+}

+ 40 - 0
app/src/main/java/com/adealink/frame/stat/StatConfig.kt

@@ -0,0 +1,40 @@
+package com.adealink.frame.stat
+
+import android.content.Context
+import com.adealink.frame.statistics.IStatConfig
+import com.adealink.frame.statistics.ReportType
+import com.adealink.frame.statistics.StatClientError
+import com.adealink.frame.util.AppUtil
+import okhttp3.OkHttpClient
+import java.util.concurrent.TimeUnit
+
+/**
+ * Created by sunxiaodong on 2024/12/4.
+ */
+class StatConfig : IStatConfig {
+
+    override val ctx: Context = AppUtil.appContext
+    override val app: String = "weparty"
+    override val uid: Long = 1001
+    override val deviceId: String = "test1001"
+    override val region: String = "EG"
+    override val countryCode: String = "EG"
+    override val languageCode: String = "ar"
+    override val appChannel: String = "gp"
+    override val organicInstall: Boolean = false
+    override val roomId: Long? = null
+    override val roomType: Int? = null
+    override val gender: Int? = null
+    override val statHtpClient: OkHttpClient = OkHttpClient.Builder()
+        .connectTimeout(10_000L, TimeUnit.MILLISECONDS)
+        .readTimeout(20_000L, TimeUnit.MILLISECONDS)
+        .writeTimeout(20_000L, TimeUnit.MILLISECONDS)
+        .build()
+    override val reportUrl: String = "http://api-log-upload.wenext.technology/api/log"
+    override val defaultReportChannels = listOf(ReportType.WENEXT)
+    
+    override fun reportClientError(statClientError: StatClientError?) {
+
+    }
+
+}

+ 37 - 0
app/src/main/java/com/adealink/frame/stat/TestStatActivity.kt

@@ -0,0 +1,37 @@
+package com.adealink.frame.stat
+
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.R
+
+/**
+ * Created by sunxiaodong on 2024/12/4.
+ */
+class TestStatActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_test_stat)
+        findViewById<View>(R.id.tv_immediate_report).setOnClickListener {
+            TestWeNextStat(TestWeNextStat.Action.BTN)
+                .apply {
+                    page to "TestStatActivity"
+                }
+                .send()
+        }
+        findViewById<View>(R.id.tv_delay_report).setOnClickListener {
+            TestWeNextStat(TestWeNextStat.Action.BTN)
+                .apply {
+                    page to "TestStatActivity"
+                }
+                .send()
+        }
+        TestWeNextStat(TestWeNextStat.Action.PAGE)
+            .apply {
+                page to "TestStatActivity"
+            }
+            .send()
+    }
+
+}

+ 19 - 0
app/src/main/java/com/adealink/frame/stat/TestWeNextStat.kt

@@ -0,0 +1,19 @@
+package com.adealink.frame.stat
+
+import com.adealink.frame.statistics.BaseStatEvent
+import com.adealink.frame.statistics.CommonEventKey
+import com.adealink.frame.statistics.IEventValue
+
+/**
+ * Created by sunxiaodong on 2024/12/4.
+ */
+class TestWeNextStat(override val action: IEventValue) : BaseStatEvent("test") {
+
+    enum class Action(override val value: String, override val desc: String) : IEventValue {
+        PAGE("page", "页面"),
+        BTN("btn", "按钮"),
+    }
+
+    val page = Param(CommonEventKey.PAGE)
+
+}

+ 25 - 0
app/src/main/java/com/adealink/frame/svga/EmotionEffectEntity.kt

@@ -0,0 +1,25 @@
+package com.adealink.frame.svga
+
+import android.content.Context
+import android.util.AttributeSet
+import com.adealink.frame.effect.data.IEffectEntity
+import com.adealink.frame.effect.data.defaultPriority
+import com.adealink.frame.effect.listener.IPlayListener
+
+class EmotionEffectEntity(
+    override val path: String,
+    override val priority: String = defaultPriority,
+    override val loop: Int = 0, //循环次数
+    val showResultOnly: Boolean = false,//是否只显示结果
+    override val playListener: IPlayListener? = null,
+) : IEffectEntity<EmotionEffectView>() {
+
+    override fun createEffectView(
+        ctx: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+    ): EmotionEffectView {
+        return EmotionEffectView(ctx, attrs, defStyleAttr)
+    }
+
+}

+ 98 - 0
app/src/main/java/com/adealink/frame/svga/EmotionEffectView.kt

@@ -0,0 +1,98 @@
+package com.adealink.frame.svga
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.effect.data.ERROR_ENTITY_NULL
+import com.adealink.frame.effect.data.IEffectEntity
+import com.adealink.frame.effect.listener.IPlayListener
+import com.adealink.frame.effect.view.EffectView
+import com.adealink.frame.effect.view.IEffectView
+import com.adealink.frame.log.Log
+
+class EmotionEffectView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr), IEffectView {
+    companion object {
+        private const val TAG = "EmotionEffectView"
+    }
+
+    private val effectView by fastLazy { EffectView(context) }
+
+    init {
+        addView(effectView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
+    }
+
+    override fun addToParent(parent: ViewGroup, params: ViewGroup.LayoutParams) {
+        parent.addView(this, params)
+    }
+
+    override fun removeFromParent(parent: ViewGroup) {
+        parent.removeView(this)
+    }
+
+    override fun setVisibility(visibility: Boolean) {
+        this.visibility = if (visibility) View.VISIBLE else View.GONE
+    }
+
+    override fun isVisibility(): Boolean {
+        return this.visibility == View.VISIBLE
+    }
+
+    override fun play(entity: IEffectEntity<out IEffectView>?, l: IPlayListener?) {
+        Log.d(TAG, "play, $entity")
+        val emotionEntity = entity as? EmotionEffectEntity
+        if (emotionEntity == null) {
+            l?.onError(ERROR_ENTITY_NULL)
+            return
+        }
+        effectView.scaleX = 1.2f
+        effectView.scaleY = 1.2f
+        val playListener: IPlayListener = object : IPlayListener {
+            override fun onComplete() {
+                Log.d(TAG, "play, $entity, onComplete")
+                emotionEntity.playListener?.onComplete()
+                l?.onComplete()
+            }
+
+            override fun onError(errCode: Int) {
+                Log.d(TAG, "play, $entity, onError:$errCode")
+                emotionEntity.playListener?.onError(errCode)
+                l?.onError(errCode)
+            }
+
+            override fun onInfiniteLoop() {
+                super.onInfiniteLoop()
+                Log.d(TAG, "play, $entity, onInfiniteLoop")
+                emotionEntity.playListener?.onInfiniteLoop()
+                l?.onInfiniteLoop()
+            }
+        }
+        Log.d(TAG, "play, play()")
+        effectView.play()
+        Log.d(TAG, "play, add()")
+        effectView.add(
+            SVGAEmotionEntity(
+                path = emotionEntity.path,
+                loop = emotionEntity.loop,
+                showResultOnly = emotionEntity.showResultOnly,
+                playListener = playListener
+            )
+        )
+    }
+
+    override fun pause() {
+        Log.d(TAG, "play, pause")
+        effectView.pause()
+    }
+
+    override fun stop() {
+        Log.d(TAG, "play, stop")
+        effectView.stop()
+    }
+}

+ 106 - 0
app/src/main/java/com/adealink/frame/svga/SVGAEmotionEffectView.kt

@@ -0,0 +1,106 @@
+package com.adealink.frame.svga
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.effect.data.ERROR_ENTITY_NULL
+import com.adealink.frame.effect.data.IEffectEntity
+import com.adealink.frame.effect.data.INFINITE_LOOP
+import com.adealink.frame.effect.listener.IPlayListener
+import com.adealink.frame.effect.svga.SVGAEffectView
+import com.adealink.frame.effect.svga.data.PathType
+import com.adealink.frame.effect.svga.data.SVGAEffectEntity
+import com.adealink.frame.effect.view.IEffectView
+import com.adealink.frame.log.Log
+import com.opensource.svgaplayer.FillMode
+import com.opensource.svgaplayer.SVGAImageView
+
+class SVGAEmotionEffectView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr), IEffectView {
+
+    companion object {
+        private const val TAG = "SVGAEmotionEffectView"
+    }
+
+    private val svgaEffectView: SVGAEffectView by fastLazy {
+        SVGAEffectView(context)
+    }
+
+    init {
+        addView(svgaEffectView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
+    }
+
+    override fun addToParent(parent: ViewGroup, params: ViewGroup.LayoutParams) {
+        parent.addView(this, params)
+    }
+
+    override fun removeFromParent(parent: ViewGroup) {
+        parent.removeView(this)
+    }
+
+    override fun setVisibility(visibility: Boolean) {
+        this.visibility = if (visibility) View.VISIBLE else View.GONE
+    }
+
+    override fun isVisibility(): Boolean {
+        return this.visibility == View.VISIBLE
+    }
+
+    override fun play(entity: IEffectEntity<out IEffectView>?, l: IPlayListener?) {
+        Log.d(TAG, "play, $entity")
+        val emotionEntity = entity as? SVGAEmotionEntity
+        if (emotionEntity == null) {
+            l?.onError(ERROR_ENTITY_NULL)
+            return
+        }
+        svgaEffectView.fillMode = FillMode.Forward
+        val svgaLoopCount = emotionEntity.loop
+        val isInfiniteLoop = emotionEntity.loop == INFINITE_LOOP
+        svgaEffectView.play(
+            SVGAEffectEntity(path = emotionEntity.path, PathType.ASSERT, loop = svgaLoopCount),
+            object : IPlayListener {
+                override fun onComplete() {
+                    Log.d(TAG, "play, $entity, onComplete")
+                    endPlay(emotionEntity, l, isInfiniteLoop)
+                }
+
+                override fun onError(errCode: Int) {
+                    Log.d(TAG, "play, $entity, onError")
+                    l?.onError(errCode)
+                }
+
+                override fun onInfiniteLoop() {
+                    super.onInfiniteLoop()
+                    Log.d(TAG, "play, $entity, onInfiniteLoop")
+                    emotionEntity.playListener?.onInfiniteLoop()
+                    l?.onInfiniteLoop()
+                }
+            })
+    }
+
+    private fun endPlay(emotionEntity: SVGAEmotionEntity, l: IPlayListener?, isInfiniteLoop: Boolean) {
+        Log.d(TAG, "endPlay")
+        if (isInfiniteLoop) {
+            l?.onInfiniteLoop()
+            return
+        }
+        svgaEffectView.stop()
+        l?.onComplete()
+    }
+
+    override fun pause() {
+        Log.d(TAG, "pause")
+        svgaEffectView.pause()
+    }
+
+    override fun stop() {
+        Log.d(TAG, "stop")
+        svgaEffectView.stop()
+    }
+}

+ 26 - 0
app/src/main/java/com/adealink/frame/svga/SVGAEmotionEntity.kt

@@ -0,0 +1,26 @@
+package com.adealink.frame.svga
+
+import android.content.Context
+import android.util.AttributeSet
+import com.adealink.frame.effect.data.IEffectEntity
+import com.adealink.frame.effect.data.defaultPriority
+import com.adealink.frame.effect.listener.IPlayListener
+
+class SVGAEmotionEntity(
+    override val path: String,
+    override val priority: String = defaultPriority,
+    val resultIndex: Int = 0,
+    override val loop: Int = 0, //循环次数
+    val showResultOnly: Boolean = false,//是否只显示结果
+    override val playListener: IPlayListener? = null
+) : IEffectEntity<SVGAEmotionEffectView>() {
+
+    override fun createEffectView(
+        ctx: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+    ): SVGAEmotionEffectView {
+        return SVGAEmotionEffectView(ctx, attrs, defStyleAttr)
+    }
+
+}

+ 40 - 0
app/src/main/java/com/adealink/frame/svga/SvgaActivity.kt

@@ -0,0 +1,40 @@
+package com.adealink.frame.svga
+
+import android.os.Bundle
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.R
+import com.opensource.svgaplayer.WenextSvgaView
+
+class SvgaActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_svga)
+
+        var volume = 0.5f
+
+        val svgaView = findViewById<WenextSvgaView>(R.id.iv_svga)
+        svgaView.setUrl("https://ludo-system-1314119829.cos.eu-frankfurt.myqcloud.com/gift/1691862091847739209.svga")
+        svgaView.volume = volume
+
+        val sbVolume = findViewById<SeekBar>(R.id.sb_volume)
+        sbVolume.progress = 50
+        sbVolume.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
+            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
+                svgaView.volume = progress * 1f / 100
+            }
+
+            override fun onStartTrackingTouch(seekBar: SeekBar?) {
+            }
+
+            override fun onStopTrackingTouch(seekBar: SeekBar?) {
+            }
+        })
+
+//        findViewById<View>(R.id.iv_svga).setOnClickListener {
+//            Log.d("SvgaActivity", "son, onClick")
+//        }
+    }
+}

+ 56 - 0
app/src/main/java/com/adealink/frame/svga/SvgaEffectActivity.kt

@@ -0,0 +1,56 @@
+package com.adealink.frame.svga
+
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.R
+import com.adealink.frame.effect.listener.IPlayListener
+import com.adealink.frame.effect.svga.data.PathType
+import com.adealink.frame.effect.svga.data.SVGAEffectEntity
+import com.adealink.frame.effect.view.EffectView
+
+class SvgaEffectActivity : AppCompatActivity() {
+
+    private lateinit var effectView: EffectView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_svga_effect)
+        effectView = findViewById(R.id.iv_effect_view)
+        findViewById<View>(R.id.btn_play).setOnClickListener {
+            playSvga()
+        }
+    }
+
+    private fun playSvga() {
+        effectView.visibility = View.VISIBLE
+//        effectView.add(
+//            EmotionEffectEntity(
+//                path = "emotion.svga",
+//                showResultOnly = false,
+//                playListener = object : IPlayListener {
+//
+//                    override fun onComplete() {
+//                        effectView.visibility = View.GONE
+//                    }
+//
+//                    override fun onError(errCode: Int) {
+//                        effectView.visibility = View.GONE
+//                    }
+//
+//                }
+//            )
+//        )
+                effectView.add(
+                    SVGAEffectEntity(
+                        path = "custom_gift_default_effect.svga",
+                        PathType.ASSERT,
+                        loop = 0,
+                        timeoutMS = 10_000L,
+                        scaleType = ImageView.ScaleType.CENTER_CROP
+                    )
+                )
+    }
+
+}

+ 75 - 0
app/src/main/java/com/adealink/frame/svga/recyclerview/SvgaEffectRecyclerViewActivity.kt

@@ -0,0 +1,75 @@
+package com.adealink.frame.svga.recyclerview
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.MainActivity
+import com.adealink.frame.R
+import com.adealink.frame.effect.data.INFINITE_LOOP
+import com.adealink.frame.effect.view.EffectView
+import com.adealink.frame.svga.EmotionEffectEntity
+
+class SvgaEffectRecyclerViewActivity : AppCompatActivity() {
+
+    private val items = mutableListOf<String>()
+
+    private val itemsAdapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+            val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_svate_effect_item, parent, false)
+            return SvgaViewHolder(view)
+        }
+
+        override fun getItemCount(): Int {
+            return items.size
+        }
+
+        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+            if (holder is SvgaViewHolder) {
+                holder.effectView?.add(
+                    EmotionEffectEntity(
+                        path = items[position],
+                        loop = INFINITE_LOOP,
+                        showResultOnly = false
+                    )
+                )
+            }
+        }
+    }
+
+    class SvgaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+        var effectView: EffectView? = null
+
+        init {
+            effectView = itemView.findViewById(R.id.iv_svga_effect)
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_svga_effect_recyclerview)
+
+        findViewById<View>(R.id.btn_next).setOnClickListener {
+            startActivity(Intent(this, MainActivity::class.java))
+        }
+
+        findViewById<RecyclerView>(R.id.rv_svga).apply {
+            adapter = this@SvgaEffectRecyclerViewActivity.itemsAdapter
+            layoutManager = LinearLayoutManager(this@SvgaEffectRecyclerViewActivity, RecyclerView.VERTICAL, false)
+        }
+        items.addAll(
+            listOf(
+                "emotion.svga",
+                "emotion.svga",
+                "emotion.svga",
+                "emotion.svga"
+            )
+        )
+        itemsAdapter.notifyDataSetChanged()
+    }
+}

+ 64 - 0
app/src/main/java/com/adealink/frame/svga/recyclerview/SvgaRecyclerViewActivity.kt

@@ -0,0 +1,64 @@
+package com.adealink.frame.svga.recyclerview
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.R
+import com.adealink.frame.svga.SvgaActivity
+import com.opensource.svgaplayer.WenextSvgaView
+
+class SvgaRecyclerViewActivity : AppCompatActivity() {
+
+    private val items = mutableListOf<String>()
+
+    private val itemsAdapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+            val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_svate_item, parent, false)
+            return SvgaViewHolder(view)
+        }
+
+        override fun getItemCount(): Int {
+            return items.size
+        }
+
+        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+            if (holder is SvgaViewHolder) {
+                holder.svgaView?.setAsset(items[position])
+            }
+        }
+    }
+
+    class SvgaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+        var svgaView: WenextSvgaView? = null
+
+        init {
+            svgaView = itemView.findViewById(R.id.iv_svga)
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_svga_recyclerview)
+
+        findViewById<View>(R.id.btn_next).setOnClickListener {
+            startActivity(Intent(this, SvgaActivity::class.java))
+        }
+
+        findViewById<RecyclerView>(R.id.rv_svga).apply {
+            adapter = this@SvgaRecyclerViewActivity.itemsAdapter
+            layoutManager = LinearLayoutManager(this@SvgaRecyclerViewActivity, RecyclerView.VERTICAL, false)
+        }
+        items.addAll(
+            listOf(
+                "emotion.svga", "emotion.svga", "emotion.svga",
+            )
+        )
+        itemsAdapter.notifyDataSetChanged()
+    }
+}

+ 60 - 0
app/src/main/java/com/adealink/frame/svga/viewpager/RecyclerviewFragment.kt

@@ -0,0 +1,60 @@
+package com.adealink.frame.svga.viewpager
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.adealink.frame.R
+import com.opensource.svgaplayer.WenextSvgaView
+
+class RecyclerviewFragment : Fragment(R.layout.fragment_svga_recyclerview) {
+
+    private val items = mutableListOf<String>()
+
+    private val itemsAdapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+            val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_svate_item, parent, false)
+            return SvgaViewHolder(view)
+        }
+
+        override fun getItemCount(): Int {
+            return items.size
+        }
+
+        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+            if (holder is SvgaViewHolder) {
+                Log.d("RecyclerviewFragment", "setAsset")
+                holder.svgaView?.setAsset(items[position])
+            }
+        }
+    }
+
+    class SvgaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+        var svgaView: WenextSvgaView? = null
+
+        init {
+            svgaView = itemView.findViewById(R.id.iv_svga)
+        }
+    }
+
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        view.findViewById<RecyclerView>(R.id.rv).apply {
+            adapter = itemsAdapter
+            layoutManager = LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
+        }
+        items.addAll(
+            listOf(
+                "emotion.svga", //"emotion.svga", "emotion.svga",
+            )
+        )
+        itemsAdapter.notifyDataSetChanged()
+    }
+
+}

+ 72 - 0
app/src/main/java/com/adealink/frame/svga/viewpager/SvgaViewpagerActivity.kt

@@ -0,0 +1,72 @@
+package com.adealink.frame.svga.viewpager
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import androidx.viewpager2.widget.ViewPager2
+import com.adealink.frame.R
+import com.adealink.frame.base.fastLazy
+import com.google.android.material.tabs.TabLayout
+import com.google.android.material.tabs.TabLayoutMediator
+
+class SvgaViewpagerActivity : AppCompatActivity() {
+
+    private val tabAdapter by fastLazy { TabAdapter() }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_svga_tab_view_pager)
+
+        val tabLayout = findViewById<TabLayout>(R.id.tab_layout)
+        val vp = findViewById<ViewPager2>(R.id.vp)
+
+
+
+        vp.isSaveEnabled = false
+        vp.isUserInputEnabled = false
+        (vp.getChildAt(0) as? RecyclerView)?.setItemViewCacheSize(tabAdapter.itemCount)
+        vp.adapter = tabAdapter
+        tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+            override fun onTabSelected(tab: TabLayout.Tab?) {
+
+            }
+
+            override fun onTabUnselected(tab: TabLayout.Tab?) {
+
+            }
+
+            override fun onTabReselected(tab: TabLayout.Tab?) {
+            }
+        })
+        TabLayoutMediator(
+            tabLayout, vp
+        ) { tab: TabLayout.Tab, position: Int ->
+            run {
+                tab.text = position.toString()
+            }
+        }.attach()
+
+    }
+
+    inner class TabAdapter : FragmentStateAdapter(this) {
+
+        private val tabs = listOf(
+            0, 1, 2
+        )
+
+        override fun getItemCount(): Int {
+            return tabs.size
+        }
+
+        override fun createFragment(position: Int): Fragment {
+            return if (position == 0) {
+                RecyclerviewFragment()
+            } else {
+                Fragment()
+            }
+        }
+
+    }
+}

+ 28 - 0
app/src/main/java/com/adealink/frame/vap/VapActivity.kt

@@ -0,0 +1,28 @@
+package com.adealink.frame.vap
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.R
+import com.opensource.svgaplayer.WenextSvgaView
+import com.tencent.qgame.animplayer.AnimView
+
+class VapActivity : AppCompatActivity() {
+
+    private var isMute = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_vap)
+
+        findViewById<AnimView>(R.id.v_vap).apply {
+            setOnClickListener {
+                isMute = !isMute
+                setMute(isMute)
+            }
+            setLoop(10)
+            startPlay(assetManager = resources.assets, "vap.mp4")
+        }
+    }
+}

+ 63 - 0
app/src/main/java/com/adealink/frame/vap/VapEffectActivity.kt

@@ -0,0 +1,63 @@
+package com.adealink.frame.vap
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import com.adealink.frame.R
+import com.adealink.frame.effect.video.data.VideoEffectEntity
+import com.adealink.frame.effect.view.EffectView
+import com.adealink.frame.util.copy
+import com.opensource.svgaplayer.WenextSvgaView
+import com.tencent.qgame.animplayer.AnimView
+import java.io.File
+
+class VapEffectActivity : AppCompatActivity() {
+
+    private var isMute = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_vap_effect)
+
+        val path = "${cacheDir}${File.separator}vap.mp4"
+        copyToSDCard(path)
+
+        findViewById<EffectView>(R.id.v_vap_effect).apply {
+            setOnClickListener {
+                isMute = !isMute
+
+                playVapEffect(path)
+            }
+        }
+    }
+
+    private fun copyToSDCard(path: String) {
+        val target = File(path)
+        if (target.exists()) {
+            return
+        }
+        target.createNewFile()
+        copy(resources.assets.open("vap.mp4"), target)
+    }
+
+    private fun playVapEffect(path: String) {
+        Toast.makeText(
+            this, if (isMute) {
+                "静音播放"
+            } else {
+                "播放声音"
+            }, Toast.LENGTH_SHORT
+        ).show()
+        findViewById<EffectView>(R.id.v_vap_effect).apply {
+            add(
+                VideoEffectEntity(
+                    path,
+                    mute = isMute,
+                    timeoutMS = 15_000,
+                )
+            )
+        }
+    }
+}

+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

binární
app/src/main/res/drawable-xhdpi/common_red_dot_ic.9.png


binární
app/src/main/res/drawable-xhdpi/level_glod_num_0.png


binární
app/src/main/res/drawable-xhdpi/level_glod_num_1.png


+ 8 - 0
app/src/main/res/drawable/common_red_dot_normal_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <stroke
+        android:width="1dp"
+        android:color="@color/white" />
+    <solid android:color="#FF6150" />
+</shape>

+ 9 - 0
app/src/main/res/drawable/common_red_dot_text_bg.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <stroke
+        android:width="1dp"
+        android:color="@color/white" />
+    <corners android:radius="16dp" />
+    <solid android:color="#FF6150" />
+</shape>

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 8 - 0
app/src/main/res/drawable/video_bottom_bar_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <corners android:radius="16dp" />
+    <solid android:color="#26000000" />
+
+</shape>

+ 30 - 0
app/src/main/res/drawable/video_time_bar.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!--定义滑动条的底色-->
+    <item android:id="@android:id/background">
+        <shape>
+            <corners android:radius="2dp" />
+            <solid android:color="#80FFFFFF" />
+        </shape>
+    </item>
+
+    <!-- 定义缓存颜色 -->
+    <item android:id="@android:id/secondaryProgress">
+        <clip>
+            <shape>
+                <corners android:radius="2dp" />
+                <solid android:color="#CCFFFFFF" />
+            </shape>
+        </clip>
+    </item>
+
+    <!--定义seekbar滑动条进度颜色-->
+    <item android:id="@android:id/progress">
+        <clip>
+            <shape>
+                <corners android:radius="2dp" />
+                <solid android:color="#FF00CA51" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>

+ 11 - 0
app/src/main/res/drawable/video_time_bar_thumb.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="9dp"
+        android:height="9dp" />
+    <solid android:color="@color/white" />
+    <stroke
+        android:width="1dp"
+        android:color="@color/white" />
+</shape>

binární
app/src/main/res/font/ludo_bold.otf


+ 65 - 0
app/src/main/res/layout/activity_dot.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/normal_dot"
+            style="@style/CommonRedDot" />
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/num_dot"
+            style="@style/CommonRedDot"
+            android:layout_marginTop="20dp" />
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/num_dot2"
+            style="@style/CommonRedDot"
+            android:layout_marginTop="20dp" />
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/text_dot"
+            style="@style/CommonRedDot"
+            android:layout_marginTop="20dp" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/small_normal_dot"
+            style="@style/CommonRedDot2" />
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/small_num_dot"
+            style="@style/CommonRedDot2"
+            android:layout_marginTop="20dp" />
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/small_num_dot2"
+            style="@style/CommonRedDot2"
+            android:layout_marginTop="20dp" />
+
+        <com.adealink.frame.dot.DotView
+            android:id="@+id/small_text_dot"
+            style="@style/CommonRedDot2"
+            android:layout_marginTop="20dp" />
+
+    </LinearLayout>
+
+
+</LinearLayout>

+ 22 - 0
app/src/main/res/layout/activity_image.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <com.adealink.frame.image.view.NetworkImageView
+        android:id="@+id/v_image"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        android:scaleType="fitCenter" />
+
+
+<!--    <com.adealink.frame.image.view.NetworkImageView-->
+<!--        android:id="@+id/v_image2"-->
+<!--        android:layout_width="200dp"-->
+<!--        android:layout_height="200dp"-->
+<!--        android:scaleType="fitCenter" />-->
+
+
+</LinearLayout>

+ 179 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.appcompat.widget.LinearLayoutCompat
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_svga_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="SVGA测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_svga_effect_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="SVGA Effect测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_svga_recycler_view_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="SVGA RecyclerView测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_svga_effect_recycler_view_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="SVGA Effect RecyclerView测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_svga_viewpager_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="SVGA ViewPager测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_vap_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="VAP Test"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_vap_effect_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="VAP Effect测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_stat_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="统计测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_router_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="Router测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_gson_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="Gson测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_image_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="Image测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_dot_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="Dot测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_sound_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="音效播放测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_anim_view_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="WeAnimView测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_effect_preview_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="动效预览测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
+    </androidx.appcompat.widget.LinearLayoutCompat>
+</ScrollView>

+ 144 - 0
app/src/main/res/layout/activity_sound.xml

@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <RadioGroup
+        android:id="@+id/rg_type"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <RadioButton
+            android:id="@+id/rb_media_sdk"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:enabled="false"
+            android:text="MEDIA_SDK" />
+
+        <RadioButton
+            android:id="@+id/rb_sound_pool"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="SOUND_POOL" />
+
+        <RadioButton
+            android:id="@+id/rb_system_media"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="SYSTEM_MEDIA" />
+
+    </RadioGroup>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_play_file_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        app:layout_constraintTop_toBottomOf="@id/rg_type">
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/black"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/tv_file_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="按照音效名播放"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <Button
+            android:id="@+id/btn_play"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="播放"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <Button
+            android:id="@+id/btn_pause"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="10dp"
+            android:text="暂停"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/btn_play" />
+
+        <Button
+            android:id="@+id/btn_stop"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="10dp"
+            android:text="停止"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/btn_pause" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_play_file_path"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        app:layout_constraintTop_toBottomOf="@id/cl_play_file_name">
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/black"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/tv_file_path"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="按照音效路径播放"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <Button
+            android:id="@+id/btn_play_path"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="播放"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <Button
+            android:id="@+id/btn_pause_path"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="10dp"
+            android:text="暂停"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/btn_play_path" />
+
+        <Button
+            android:id="@+id/btn_stop_path"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="10dp"
+            android:text="停止"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/btn_pause_path" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 34 - 0
app/src/main/res/layout/activity_svga.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.appcompat.widget.LinearLayoutCompat
+        android:id="@+id/fl_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.opensource.svgaplayer.WenextSvgaView
+            android:id="@+id/iv_svga"
+            android:layout_width="200dp"
+            android:layout_height="200dp" />
+
+        <SeekBar
+            android:id="@+id/sb_volume"
+            android:layout_width="match_parent"
+            android:layout_height="20dp"
+            android:layout_marginTop="20dp"
+            android:max="100"
+            android:progress="50" />
+
+    </androidx.appcompat.widget.LinearLayoutCompat>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 28 - 0
app/src/main/res/layout/activity_svga_effect.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <com.adealink.frame.effect.view.EffectView
+        android:id="@+id/iv_effect_view"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_play"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="播放SVGA动效"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/iv_effect_view"
+        tools:ignore="HardcodedText" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 18 - 0
app/src/main/res/layout/activity_svga_effect_recyclerview.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_next"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="下一个页面" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_svga"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</androidx.appcompat.widget.LinearLayoutCompat>

+ 18 - 0
app/src/main/res/layout/activity_svga_recyclerview.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_next"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="下一个页面" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_svga"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</androidx.appcompat.widget.LinearLayoutCompat>

+ 30 - 0
app/src/main/res/layout/activity_svga_tab_view_pager.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tab_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="6dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:tabBackground="@null"
+        app:tabIndicatorHeight="0dp"
+        app:tabMode="fixed"
+        app:tabPaddingBottom="0dp"
+        app:tabPaddingEnd="3dp"
+        app:tabPaddingStart="3dp"
+        app:tabPaddingTop="0dp"
+        app:tabRippleColor="@null" />
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/vp"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginTop="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tab_layout" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 99 - 0
app/src/main/res/layout/activity_test_animview.xml

@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <com.adealink.frame.effect.view.WeAnimView
+        android:id="@+id/anim_view"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+    <com.adealink.frame.effect.video.WeVideoView
+        android:id="@+id/video_view"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        app:controller_layout_id="@layout/layout_video_controller"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+    <com.adealink.frame.effect.view.EffectView
+        android:id="@+id/effect_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_start_queue"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="开始队列播放"
+        android:layout_marginTop="20dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/anim_view"
+        tools:ignore="HardcodedText" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_set"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="设置动效"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_start_queue"
+        tools:ignore="HardcodedText" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_start"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="开始播放"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_set"
+        tools:ignore="HardcodedText" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_pause"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="暂停播放"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_start"
+        tools:ignore="HardcodedText" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_stop"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="停止播放"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_pause"
+        tools:ignore="HardcodedText" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_remove"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="移除view"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_stop"
+        tools:ignore="HardcodedText" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 30 - 0
app/src/main/res/layout/activity_test_stat.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/tv_immediate_report"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="12dp"
+        android:paddingVertical="8dp"
+        android:text="立即测试"
+        android:textSize="16sp"
+        android:gravity="center"
+        tools:ignore="HardcodedText" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/tv_delay_report"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="12dp"
+        android:paddingVertical="8dp"
+        android:text="延迟测试"
+        android:textSize="16sp"
+        android:gravity="center"
+        tools:ignore="HardcodedText" />
+
+</androidx.appcompat.widget.LinearLayoutCompat>

+ 18 - 0
app/src/main/res/layout/activity_vap.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <com.tencent.qgame.animplayer.AnimView
+        android:id="@+id/v_vap"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 13 - 0
app/src/main/res/layout/activity_vap_effect.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <com.adealink.frame.effect.view.EffectView
+        android:id="@+id/v_vap_effect"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 12 - 0
app/src/main/res/layout/fragment_svga_recyclerview.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 38 - 0
app/src/main/res/layout/layout_dot_view2.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/black">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_dot"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:lineSpacingExtra="0dp"
+            app:layout_constrainedHeight="true"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+
+            tools:background="@drawable/common_red_dot_ic"
+            tools:layout_constraintWidth_min="24dp"
+            tools:layout_height="24dp"
+            tools:minWidth="24dp"
+            tools:paddingHorizontal="3dp"
+            tools:paddingVertical="3dp"
+            tools:text="9"
+            tools:textColor="@color/white" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout>

+ 6 - 0
app/src/main/res/layout/layout_svate_effect_item.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.adealink.frame.effect.view.EffectView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/iv_svga_effect"
+    android:layout_width="200dp"
+    android:layout_height="200dp" />

+ 6 - 0
app/src/main/res/layout/layout_svate_item.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.opensource.svgaplayer.WenextSvgaView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/iv_svga"
+    android:layout_width="200dp"
+    android:layout_height="200dp" />

+ 81 - 0
app/src/main/res/layout/layout_video_controller.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:parentTag="android.widget.FrameLayout"
+    tools:viewBindingIgnore="true">
+
+    <!-- ExoPlayer自定义操作控件, StyledPlayerControlView, exo_styled_player_control_view.xml -->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10dp"
+        android:layout_gravity="bottom">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@id/exo_bottom_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="8dp"
+            android:layout_marginBottom="6dp"
+            android:background="@drawable/video_bottom_bar_bg"
+            android:paddingHorizontal="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent">
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@id/exo_play_pause"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@id/exo_position"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="4dp"
+                android:includeFontPadding="false"
+                android:textColor="@color/white"
+                android:textSize="12sp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toEndOf="@id/exo_play_pause"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:minWidth="50dp" />
+
+            <com.adealink.frame.effect.VideoTimeBar
+                android:id="@id/exo_progress"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_marginHorizontal="4dp"
+                app:bar_height="8dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@id/exo_duration"
+                app:layout_constraintStart_toEndOf="@id/exo_position"
+                app:layout_constraintTop_toTopOf="parent"
+                app:played_color="#FF00CA51"
+                app:scrubber_drawable="@drawable/video_time_bar_thumb"
+                app:unplayed_color="#80FFFFFF" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@id/exo_duration"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:includeFontPadding="false"
+                android:textColor="@color/white"
+                android:textSize="12sp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:minWidth="50dp" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</merge>

+ 25 - 0
app/src/main/res/layout/layout_video_time_bar.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
+
+    <androidx.appcompat.widget.AppCompatSeekBar
+        android:id="@+id/seek_bar"
+        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="10dp"
+        android:clipChildren="false"
+        android:max="100"
+        android:paddingStart="4.5dp"
+        android:paddingEnd="4.5dp"
+        android:maxHeight="4dp"
+        tools:progress="20"
+        android:progressDrawable="@drawable/video_time_bar"
+        android:thumb="@drawable/video_time_bar_thumb"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</merge>

+ 6 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 6 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

binární
app/src/main/res/mipmap-hdpi/ic_launcher.webp


binární
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


binární
app/src/main/res/mipmap-mdpi/ic_launcher.webp


binární
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


binární
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


binární
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


binární
app/src/main/res/mipmap-xhdpi/room_micseat_seat_unlock_ic.webp


binární
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


binární
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


binární
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


binární
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


binární
app/src/main/res/raw/brother.mp4


+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.FrameBase" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 10 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">WeNextFrame</string>
+</resources>

+ 59 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!--红点样式-->
+    <style name="CommonRedDot">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+
+        <item name="dot_padding_top">0dp</item>
+        <item name="dot_padding_bottom">0dp</item>
+        <item name="dot_padding_start">3dp</item>
+        <item name="dot_padding_end">3dp</item>
+
+        <!-- NormalDot -->
+        <item name="dot_normal_width">12dp</item>
+        <item name="dot_normal_bg">@drawable/common_red_dot_ic</item>
+
+        <!-- NumDot -->
+        <item name="dot_num_width">15dp</item>
+        <item name="dot_num_bg">@drawable/common_red_dot_ic</item>
+        <item name="dot_num_text_font_family">@font/ludo_bold</item>
+        <item name="dot_num_text_size">8dp</item>
+        <item name="dot_num_text_color">@color/white</item>
+
+        <!-- TextDot -->
+        <item name="dot_text_width">15dp</item>
+        <item name="dot_text_bg">@drawable/common_red_dot_ic</item>
+        <item name="dot_text_text_font_family">@font/ludo_bold</item>
+        <item name="dot_text_text_size">8dp</item>
+        <item name="dot_text_text_color">@color/white</item>
+    </style>
+
+    <style name="CommonRedDot2">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+
+        <item name="dot_padding_top">1.2dp</item>
+        <item name="dot_padding_bottom">1.2dp</item>
+        <item name="dot_padding_start">1.2dp</item>
+        <item name="dot_padding_end">1.2dp</item>
+
+        <!-- NormalDot -->
+        <item name="dot_normal_width">12dp</item>
+        <item name="dot_normal_bg">@drawable/common_red_dot_normal_bg</item>
+
+        <!-- NumDot -->
+        <item name="dot_num_width">16dp</item>
+        <item name="dot_num_bg">@drawable/common_red_dot_text_bg</item>
+        <item name="dot_num_text_size">10sp</item>
+        <item name="dot_num_text_color">@color/white</item>
+
+        <!-- TextDot -->
+        <item name="dot_text_width">16dp</item>
+        <item name="dot_text_bg">@drawable/common_red_dot_text_bg</item>
+        <item name="dot_text_text_size">10sp</item>
+        <item name="dot_text_text_color">@color/white</item>
+    </style>
+
+</resources>

+ 20 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,20 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.FrameBase" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+
+    <style name="AppTheme" parent="Theme.AppCompat">
+        <item name="default_dot_style">@style/CommonRedDot</item>
+    </style>
+</resources>

+ 1 - 0
app/src/main/resources/META-INF/services/com.adealink.frame.router.IRouterInit

@@ -0,0 +1 @@
+com.adealink.frame.router.RouterInit_app

+ 17 - 0
app/src/test/java/com/adealink/frame/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.adealink.frame
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}

+ 26 - 0
build.gradle

@@ -0,0 +1,26 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+        maven { url 'https://jitpack.io' }
+        maven {
+            url'http://8.134.23.107:8088/repository/wenext-android/'
+            credentials {
+                username 'wenext-android'
+                password 'wenext123456'
+            }
+            allowInsecureProtocol = true//允许 Gradle 使用不安全的协议
+        }
+    }
+    dependencies {
+        classpath libs.android.build.gradle
+        classpath libs.kotlin.gradle.plugin
+
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů