Explorar o código

feat: 移除atomic_x模块

DoggyZhang hai 5 días
pai
achega
01beda5083
Modificáronse 100 ficheiros con 0 adicións e 10934 borrados
  1. 0 16
      frame/atomic_x/.gitignore
  2. 0 60
      frame/atomic_x/build.gradle
  3. 0 21
      frame/atomic_x/proguard-rules.pro
  4. 0 19
      frame/atomic_x/src/main/AndroidManifest.xml
  5. 0 7
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/core/CallViewFunction.kt
  6. 0 33
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/core/common/widget/ControlButton.kt
  7. 0 189
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/aisubtitle/AISubtitle.kt
  8. 0 43
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/AudioAndVideoCalleeWaitingView.kt
  9. 0 124
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/AudioCallerWaitingAndAcceptedView.kt
  10. 0 76
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/MultiCallControlsView.kt
  11. 0 79
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/SingleCallControlsView.kt
  12. 0 378
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/VideoCallerAndCalleeAcceptedView.kt
  13. 0 150
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/VideoCallerWaitingView.kt
  14. 0 34
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/service/MusicServiceFactory.kt
  15. 0 25
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/CallbacksDefine.kt
  16. 0 1205
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/KaraokeStore.kt
  17. 0 7
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/MusicCatalogService.kt
  18. 0 21
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/utils/KaraokeTypes.kt
  19. 0 89
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/utils/LyricsFileReader.kt
  20. 0 443
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/KaraokeControlView.kt
  21. 0 405
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/KaraokeFloatingView.kt
  22. 0 145
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/KaraokeSettingPanel.kt
  23. 0 220
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/LyricView.kt
  24. 0 345
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/PitchView.kt
  25. 0 196
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/SongRequestPanel.kt
  26. 0 165
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/adapter/KaraokeOrderedListAdapter.kt
  27. 0 107
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/adapter/KaraokeSongListAdapter.kt
  28. 0 24
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/pictureinpicture/PictureInPictureStore.kt
  29. 0 145
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/ThemeStore.kt
  30. 0 678
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/ColorTokens.kt
  31. 0 34
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/DesignTokenSet.kt
  32. 0 36
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/FontTokens.kt
  33. 0 41
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/ShadowTokens.kt
  34. 0 258
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/utils/ColorAlgorithm.kt
  35. 0 65
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/utils/ThemePersistUtil.kt
  36. 0 481
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/alertdialog/AtomicAlertDialog.kt
  37. 0 124
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/alertdialog/README.md
  38. 0 483
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/avatar/AtomicAvatar.kt
  39. 0 460
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/avatar/README.md
  40. 0 579
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/button/AtomicButton.kt
  41. 0 178
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/button/README.md
  42. 0 242
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/label/AtomicLabel.kt
  43. 0 523
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/label/README.md
  44. 0 441
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/popover/AtomicPopover.kt
  45. 0 94
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/popover/README.md
  46. 0 190
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/toast/AtomicToast.kt
  47. 0 237
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/toast/README.md
  48. 0 337
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/BlurUtils.kt
  49. 0 22
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/DisplayUtil.kt
  50. 0 185
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/ImageLoader.kt
  51. 0 69
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/ImageOptions.kt
  52. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_check_box_group_selected.png
  53. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_group_select_disable.png
  54. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_hangup_loading.gif
  55. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_add_user_black.png
  56. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_audio_input.png
  57. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_audio_route_picker.png
  58. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_avatar.png
  59. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_back.png
  60. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_blur_background_accept.png
  61. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_blur_disable.png
  62. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_blur_enable.png
  63. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_disable.png
  64. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_enable.png
  65. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_dialing.png
  66. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_dialing_pressed.png
  67. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_float.png
  68. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_float_button.png
  69. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_disable.png
  70. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_enable.png
  71. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_hangup.png
  72. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_hangup_pressed.png
  73. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_loading.gif
  74. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_mute.png
  75. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_unmute.png
  76. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_network_bad.png
  77. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_self_mute.png
  78. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_switch_camera.png
  79. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_switch_camera_group.png
  80. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_view_expand.png
  81. 0 5
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_aisubtitle.xml
  82. 0 5
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_audio_device.xml
  83. 0 5
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_blur_background.xml
  84. 0 5
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_camera.xml
  85. 0 5
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_dialing.xml
  86. 0 8
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_group_call_bottom.xml
  87. 0 5
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_hangup.xml
  88. 0 5
      frame/atomic_x/src/main/res-callview/drawable/callview_bg_mute_mic.xml
  89. BIN=BIN
      frame/atomic_x/src/main/res-callview/drawable/callview_check_box_group_unselected.png
  90. 0 9
      frame/atomic_x/src/main/res-callview/drawable/callview_group_checkbox_selector.xml
  91. 0 11
      frame/atomic_x/src/main/res-callview/drawable/callview_ic_bluetooth.xml
  92. 0 10
      frame/atomic_x/src/main/res-callview/drawable/callview_ic_earpiece.xml
  93. 0 11
      frame/atomic_x/src/main/res-callview/drawable/callview_ic_item_checked.xml
  94. 0 10
      frame/atomic_x/src/main/res-callview/drawable/callview_ic_speaker.xml
  95. 0 9
      frame/atomic_x/src/main/res-callview/drawable/callview_popup_background.xml
  96. 0 44
      frame/atomic_x/src/main/res-callview/layout/callview_function_view_audio.xml
  97. 0 64
      frame/atomic_x/src/main/res-callview/layout/callview_function_view_invited_waiting.xml
  98. 0 102
      frame/atomic_x/src/main/res-callview/layout/callview_function_view_video.xml
  99. 0 64
      frame/atomic_x/src/main/res-callview/layout/callview_function_view_video_inviting.xml
  100. 0 29
      frame/atomic_x/src/main/res-callview/layout/callview_function_view_video_item.xml

+ 0 - 16
frame/atomic_x/.gitignore

@@ -1,16 +0,0 @@
-*.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
-.kotlin

+ 0 - 60
frame/atomic_x/build.gradle

@@ -1,60 +0,0 @@
-plugins {
-    id 'com.android.library'
-    id 'org.jetbrains.kotlin.android'
-}
-
-
-android {
-    namespace 'io.trtc.tuikit.atomicx'
-    compileSdk libs.versions.compileSdk.get().toInteger()
-
-    defaultConfig {
-        minSdk libs.versions.minSdk.get().toInteger()
-        targetSdk libs.versions.targetSdk.get().toInteger()
-        resConfigs "zh", "en", "in"
-
-        versionName "1.0"
-        versionCode 1
-    }
-
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_17
-        targetCompatibility JavaVersion.VERSION_17
-    }
-    kotlinOptions {
-        jvmTarget = JavaVersion.VERSION_17.majorVersion
-    }
-
-    sourceSets {
-        main {
-            res.srcDirs = [
-                    'src/main/res',
-                    'src/main/res-karaoke',
-                    'src/main/res-callview',
-            ]
-        }
-    }
-}
-
-dependencies {
-    implementation libs.androidx.appcompat
-    implementation libs.androidx.constraint.layout
-    implementation libs.androidx.recyclerview
-
-    api libs.android.material
-    api libs.androidx.core.ktx
-    api libs.kotlinx.coroutines.android
-
-    api libs.gson
-    api libs.glide
-
-    api libs.tencent.atomic.x
-    api libs.tencent.uikit.common
-    api libs.tencent.tui.core
-}

+ 0 - 21
frame/atomic_x/proguard-rules.pro

@@ -1,21 +0,0 @@
-# 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

+ 0 - 19
frame/atomic_x/src/main/AndroidManifest.xml

@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
-
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission
-        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
-        android:maxSdkVersion="28" />
-    <uses-permission
-        android:name="android.permission.READ_EXTERNAL_STORAGE"
-        android:maxSdkVersion="32" />
-    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
-    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
-    <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
-
-</manifest>

+ 0 - 7
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/core/CallViewFunction.kt

@@ -1,7 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.core
-
-import io.trtc.tuikit.atomicxcore.api.view.CallLayoutTemplate
-
-interface CallViewFunction {
-    fun setLayoutTemplate(template: CallLayoutTemplate)
-}

+ 0 - 33
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/core/common/widget/ControlButton.kt

@@ -1,33 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.core.common.widget
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.constraintlayout.utils.widget.ImageFilterView
-import io.trtc.tuikit.atomicx.R
-
-class ControlButton @JvmOverloads constructor(
-    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
-) : LinearLayout(context, attrs, defStyleAttr) {
-    val imageView: ImageFilterView
-    val textView: TextView
-
-    init {
-        LayoutInflater.from(context).inflate(R.layout.callview_function_view_video_item, this, true)
-        imageView = findViewById(R.id.iv_function)
-        textView = findViewById(R.id.tv_function)
-
-        attrs?.let {
-            val typedArray = context.obtainStyledAttributes(it, R.styleable.ControlButton, 0, 0)
-            val iconRes = typedArray.getResourceId(R.styleable.ControlButton_cbIcon, 0)
-            val text = typedArray.getString(R.styleable.ControlButton_cbText)
-            if (iconRes != 0) {
-                imageView.setImageResource(iconRes)
-            }
-            textView.text = text ?: ""
-            typedArray.recycle()
-        }
-    }
-}

+ 0 - 189
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/aisubtitle/AISubtitle.kt

@@ -1,189 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.aisubtitle
-
-import android.content.Context
-import android.graphics.Typeface
-import android.os.Handler
-import android.os.Looper
-import android.text.SpannableString
-import android.text.SpannableStringBuilder
-import android.text.Spanned
-import android.text.style.ForegroundColorSpan
-import android.text.style.StyleSpan
-import android.util.AttributeSet
-import android.widget.ScrollView
-import androidx.appcompat.widget.AppCompatTextView
-import com.tencent.cloud.tuikit.engine.call.TUICallEngine
-import com.tencent.trtc.TRTCCloudListener
-import io.trtc.tuikit.atomicx.callview.core.common.Constants.AI_TRANSLATION_ROBOT
-import io.trtc.tuikit.atomicx.callview.core.common.utils.Logger
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import org.json.JSONObject
-import java.util.Timer
-import java.util.TimerTask
-
-class AISubtitle(context: Context, attrs: AttributeSet?) : ScrollView(context, attrs) {
-    private var hideTimer: Timer? = null
-    private val mainHandler = Handler(Looper.getMainLooper())
-    private val translationInfo = mutableListOf<TranslationInfo>()
-    private lateinit var textView: AppCompatTextView
-
-    init {
-        initView()
-    }
-
-    private fun initView() {
-        isVerticalScrollBarEnabled = true
-        isScrollbarFadingEnabled = false
-        textView = AppCompatTextView(context).apply {
-            layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
-            setPadding(16, 16, 16, 16)
-            textSize = 14f
-            setTextColor(0xFFFFFFFF.toInt())
-        }
-        addView(textView)
-    }
-
-    private val trtcCloudListener = object : TRTCCloudListener() {
-        override fun onRecvCustomCmdMsg(userId: String?, cmdID: Int, seq: Int, message: ByteArray?) {
-            super.onRecvCustomCmdMsg(userId, cmdID, seq, message)
-            if (userId == null || message == null) return
-
-            try {
-                val messageString = String(message)
-                val jsonObject = JSONObject(messageString)
-                val type = jsonObject.optInt("type")
-
-                if (type == AI_MESSAGE_TYPE) {
-                    val sender = jsonObject.optString("sender")
-                    val payload = jsonObject.optJSONObject("payload")
-                    val text = payload?.optString("text") ?: ""
-                    val translationText = payload?.optString("translation_text") ?: ""
-                    val roundId = payload?.optString("roundid") ?: ""
-                    val translationLanguage = payload?.optString("translation_language") ?: ""
-                    if (roundId.isNotEmpty()) {
-                        val index = translationInfo.indexOfFirst { it.roundId == roundId }
-                        if (index != -1) {
-                            translationInfo[index].sender =
-                                if (sender.contains(AI_TRANSLATION_ROBOT)) translationInfo[index].sender else sender
-                            translationInfo[index].text = text.ifEmpty {
-                                translationInfo[index].text
-                            }
-                            translationInfo[index].translation[translationLanguage] =
-                                (translationText.ifEmpty { translationInfo[index].translation[translationLanguage] }) as String
-                        } else {
-                            val translationInfoItem = TranslationInfo()
-                            translationInfoItem.roundId = roundId
-                            translationInfoItem.sender =
-                                if (sender.contains(AI_TRANSLATION_ROBOT)) "" else sender
-                            translationInfoItem.text = text
-                            if (translationLanguage.isNotEmpty()) {
-                                translationInfoItem.translation[translationLanguage] = translationText
-                            }
-                            translationInfo.add(translationInfoItem)
-                        }
-                        updateView()
-                    }
-                }
-            } catch (e: Exception) {
-                Logger.e("translation fail, e=${e.message}")
-            }
-        }
-    }
-
-    private fun updateView() {
-        val spannableBuilder = SpannableStringBuilder()
-        for ((index, message) in translationInfo.withIndex()) {
-            val translationText = sortLanguageType(message)
-            val displayName = getUserDisplayName(message.sender)
-            val fullText = "$displayName:\n${message.text}\n$translationText"
-            formatAISubtitleText(spannableBuilder, displayName, fullText)
-            if (index < translationInfo.size - 1) {
-                spannableBuilder.append("\n")
-            }
-        }
-        hideAISubtitleAfterDelay(spannableBuilder)
-    }
-
-    private fun sortLanguageType(message: TranslationInfo): StringBuilder {
-        val translationText = StringBuilder()
-        val languageOrder =
-            listOf("zh", "en", "es", "pt", "fr", "de", "ru", "ar", "ja", "ko", "vi", "ms", "id", "it", "th")
-        for (language in languageOrder) {
-            message.translation[language]?.let { translation ->
-                translationText.append("[$language]: $translation\n")
-            }
-        }
-        return translationText
-    }
-
-    private fun formatAISubtitleText(
-        spannableBuilder: SpannableStringBuilder,
-        displayName: String,
-        fullText: String
-    ) {
-        val spannableString = SpannableString(fullText)
-        val nameStart = 0
-        val nameEnd = displayName.length + 1
-        spannableString.setSpan(ForegroundColorSpan(0xFFD9CC66.toInt()),
-            nameStart,
-            nameEnd,
-            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
-        )
-        spannableString.setSpan(
-            StyleSpan(Typeface.BOLD),
-            nameStart,
-            nameEnd,
-            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
-        )
-        spannableBuilder.append(spannableString)
-    }
-
-    private fun hideAISubtitleAfterDelay(textBuilder: SpannableStringBuilder) {
-        mainHandler.post {
-            textView.text = textBuilder
-            visibility = VISIBLE
-            post {
-                fullScroll(ScrollView.FOCUS_DOWN)
-            }
-            hideTimer?.cancel()
-            hideTimer = Timer().apply {
-                schedule(object : TimerTask() {
-                    override fun run() {
-                        mainHandler.post {
-                            visibility = GONE
-                        }
-                    }
-                }, SHOW_DURATION * 1000)
-            }
-        }
-    }
-
-    private fun getUserDisplayName(userId: String): String {
-        val participant = CallStore.shared.observerState.allParticipants.value.find { it.id == userId }
-        return participant?.name?.takeIf { it.isNotEmpty() } ?: userId
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        TUICallEngine.createInstance(context).trtcCloudInstance.addListener(trtcCloudListener)
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        TUICallEngine.createInstance(context).trtcCloudInstance.removeListener(trtcCloudListener)
-        hideTimer?.cancel()
-    }
-
-    private class TranslationInfo {
-        var roundId: String = ""
-        var sender: String = ""
-        var text: String = ""
-        var translation: MutableMap<String, String> = mutableMapOf()
-    }
-
-    companion object {
-        private const val TAG = "AISubtitle"
-        private const val AI_MESSAGE_TYPE = 10000
-        private const val SHOW_DURATION: Long = 8
-    }
-}

+ 0 - 43
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/AudioAndVideoCalleeWaitingView.kt

@@ -1,43 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.controls
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.widget.LinearLayout
-import android.widget.RelativeLayout
-import androidx.constraintlayout.utils.widget.ImageFilterView
-import androidx.core.content.ContextCompat
-import com.trtc.tuikit.common.imageloader.ImageLoader
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-
-class AudioAndVideoCalleeWaitingView(context: Context) : RelativeLayout(context) {
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        this.layoutParams?.width = LayoutParams.MATCH_PARENT
-        this.layoutParams?.height = LayoutParams.MATCH_PARENT
-        initView()
-    }
-
-    private fun initView() {
-        LayoutInflater.from(context).inflate(R.layout.callview_function_view_invited_waiting, this)
-        val layoutReject: LinearLayout = findViewById(R.id.ll_reject)
-        val layoutDialing: LinearLayout = findViewById(R.id.ll_answer)
-        val imageViewReject: ImageFilterView = findViewById(R.id.img_reject)
-        layoutDialing.isEnabled = true
-
-        layoutReject.setOnClickListener {
-            imageViewReject.roundPercent = 1.0f
-            imageViewReject.setBackgroundColor(ContextCompat.getColor(context, R.color.callview_button_bg_red))
-            ImageLoader.loadGif(context, imageViewReject, R.drawable.callview_hangup_loading)
-            layoutDialing.isEnabled = false
-            layoutDialing.alpha = 0.8f
-            CallStore.shared.reject(null)
-        }
-        layoutDialing.setOnClickListener {
-            if (!layoutDialing.isEnabled) {
-                return@setOnClickListener
-            }
-            CallStore.shared.accept(null)
-        }
-    }
-}

+ 0 - 124
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/AudioCallerWaitingAndAcceptedView.kt

@@ -1,124 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.controls
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.widget.RelativeLayout
-import androidx.core.content.ContextCompat
-import com.trtc.tuikit.common.imageloader.ImageLoader
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import io.trtc.tuikit.atomicxcore.api.device.AudioRoute
-import io.trtc.tuikit.atomicxcore.api.device.DeviceStatus
-import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-class AudioCallerWaitingAndAcceptedView(context: Context) : RelativeLayout(context) {
-    private var subscribeStateJob: Job? = null
-
-    private lateinit var buttonMicrophone: ControlButton
-    private lateinit var buttonAudioDevice: ControlButton
-    private lateinit var buttonHangup: ControlButton
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        this.layoutParams?.width = LayoutParams.MATCH_PARENT
-        this.layoutParams?.height = LayoutParams.MATCH_PARENT
-        initView()
-        registerObserver()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        subscribeStateJob?.cancel()
-    }
-
-    private fun registerObserver() {
-        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
-            launch { observeDeviceStatus() }
-            launch { observeAudioRoute() }
-        }
-    }
-
-    private suspend fun observeDeviceStatus() {
-        DeviceStore.shared().deviceState.microphoneStatus.collect {
-            updateMicrophoneButtonView()
-        }
-    }
-
-    private suspend fun observeAudioRoute() {
-        DeviceStore.shared().deviceState.currentAudioRoute.collect { audioRoute ->
-            updateAudioRouteButton(audioRoute)
-        }
-    }
-
-    private fun initView() {
-        LayoutInflater.from(context).inflate(R.layout.callview_function_view_audio, this)
-        buttonMicrophone = findViewById(R.id.cb_mic)
-        buttonHangup = findViewById(R.id.cb_hangup)
-        buttonAudioDevice = findViewById(R.id.cb_audio_device)
-        buttonMicrophone.visibility = VISIBLE
-        buttonAudioDevice.visibility = VISIBLE
-        updateMicrophoneButtonView()
-
-        val currentAudioRoute = DeviceStore.shared().deviceState.currentAudioRoute.value
-        updateAudioRouteButton(currentAudioRoute)
-        initViewListener()
-    }
-
-    private fun updateAudioRouteButton(audioRoute: AudioRoute) {
-        val isSpeaker = audioRoute == AudioRoute.SPEAKERPHONE
-        val resId = if (isSpeaker) R.string.callview_text_speaker else R.string.callview_text_use_earpiece
-        buttonAudioDevice.textView.text = context.getString(resId)
-        buttonAudioDevice.imageView.isActivated = isSpeaker
-        buttonAudioDevice.imageView.setImageResource(R.drawable.callview_bg_audio_device)
-    }
-
-    private fun initViewListener() {
-        buttonMicrophone.setOnClickListener {
-            if (!buttonMicrophone.isEnabled) {
-                return@setOnClickListener
-            }
-            val currentMicrophoneStatus = DeviceStore.shared().deviceState.microphoneStatus.value
-            if (currentMicrophoneStatus == DeviceStatus.ON) {
-                DeviceStore.shared().closeLocalMicrophone()
-            } else {
-                DeviceStore.shared().openLocalMicrophone(null)
-            }
-        }
-        buttonHangup.setOnClickListener {
-            buttonHangup.imageView.roundPercent = 1.0f
-            buttonHangup.imageView.setBackgroundColor(ContextCompat.getColor(context, R.color.callview_button_bg_red))
-            ImageLoader.loadGif(context, buttonHangup.imageView, R.drawable.callview_hangup_loading)
-            disableButton(buttonMicrophone)
-            disableButton(buttonAudioDevice)
-            CallStore.shared.hangup(null)
-        }
-        buttonAudioDevice.setOnClickListener {
-            val currentAudioRoute = DeviceStore.shared().deviceState.currentAudioRoute.value
-            if (currentAudioRoute == AudioRoute.SPEAKERPHONE) {
-                DeviceStore.shared().setAudioRoute(AudioRoute.EARPIECE)
-            } else {
-                DeviceStore.shared().setAudioRoute(AudioRoute.SPEAKERPHONE)
-            }
-        }
-    }
-
-    private fun disableButton(button: ControlButton) {
-        button.isEnabled = false
-        button.alpha = 0.8f
-    }
-
-    private fun updateMicrophoneButtonView() {
-        val microphoneStatus = DeviceStore.shared().deviceState.microphoneStatus.value
-        buttonMicrophone.imageView.isActivated = (microphoneStatus == DeviceStatus.OFF)
-        buttonMicrophone.textView.text = if (microphoneStatus == DeviceStatus.OFF) {
-            context.getString(R.string.callview_toast_enable_mute)
-        } else {
-            context.getString(R.string.callview_toast_disable_mute)
-        }
-    }
-}

+ 0 - 76
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/MultiCallControlsView.kt

@@ -1,76 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.controls
-
-import android.content.Context
-import android.widget.RelativeLayout
-import androidx.constraintlayout.widget.ConstraintLayout
-import io.trtc.tuikit.atomicx.callview.core.common.utils.CallUtils
-import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.supervisorScope
-
-class MultiCallControlsView(context: Context) : ConstraintLayout(context) {
-    private var subscribeStateJob: Job? = null
-    private var functionLayout: RelativeLayout? = null
-    private var callStatus = CallParticipantStatus.None
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        updateLayout()
-        registerObserver()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        subscribeStateJob?.cancel()
-    }
-
-    private fun registerObserver() {
-        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
-            supervisorScope {
-                launch { observeSelfInfo() }
-                launch { observeAllParticipants() }
-            }
-        }
-    }
-
-    private suspend fun observeSelfInfo() {
-        CallStore.shared.observerState.selfInfo.collect { selfInfo ->
-            if (callStatus != selfInfo.status && selfInfo.status == CallParticipantStatus.Accept) {
-                callStatus = selfInfo.status
-                updateLayout()
-            }
-        }
-    }
-
-    private suspend fun observeAllParticipants() {
-        CallStore.shared.observerState.allParticipants.collect { participants ->
-            if (participants.size > 2) {
-                if (functionLayout is VideoCallerAndCalleeAcceptedView) {
-                    return@collect
-                }
-                updateLayout()
-            }
-        }
-    }
-
-    private fun updateLayout() {
-        val selfInfo = CallStore.shared.observerState.selfInfo.value
-        val newLayout = if (selfInfo.status == CallParticipantStatus.Waiting && !CallUtils.isCaller(selfInfo.id)) {
-                AudioAndVideoCalleeWaitingView(context)
-            } else {
-                VideoCallerAndCalleeAcceptedView(context)
-            }
-
-        functionLayout?.takeIf { it.javaClass == newLayout.javaClass }?.let {
-            return
-        }
-
-        functionLayout = newLayout
-        removeAllViews()
-        addView(functionLayout)
-    }
-}

+ 0 - 79
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/SingleCallControlsView.kt

@@ -1,79 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.controls
-
-import android.content.Context
-import android.widget.RelativeLayout
-import androidx.constraintlayout.widget.ConstraintLayout
-import io.trtc.tuikit.atomicx.callview.core.common.utils.Logger
-import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
-import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-class SingleCallControlsView(context: Context) : ConstraintLayout(context) {
-    private var subscribeStateJob: Job? = null
-    private var functionLayout: RelativeLayout? = null
-    private var callStatus = CallParticipantStatus.None
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        updateLayout()
-        registerSelfObserver()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        subscribeStateJob?.cancel()
-    }
-
-    private fun registerSelfObserver() {
-        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
-            CallStore.shared.observerState.selfInfo.collect { selfInfo ->
-                if (callStatus != selfInfo.status && selfInfo.status == CallParticipantStatus.Accept) {
-                    callStatus = selfInfo.status
-                    updateLayout()
-                }
-            }
-        }
-    }
-
-    private fun updateLayout() {
-        val mediaType = CallStore.shared.observerState.activeCall.value.mediaType
-        val selfStatus = CallStore.shared.observerState.selfInfo.value.status
-
-        when {
-            selfStatus == CallParticipantStatus.Waiting && !selfIsCaller() -> {
-                functionLayout = AudioAndVideoCalleeWaitingView(context)
-            }
-
-            selfStatus == CallParticipantStatus.Waiting && selfIsCaller() -> {
-                functionLayout = when (mediaType) {
-                    CallMediaType.Video -> VideoCallerWaitingView(context)
-                    else -> AudioCallerWaitingAndAcceptedView(context)
-                }
-            }
-
-            selfStatus == CallParticipantStatus.Accept -> {
-                functionLayout = when (mediaType) {
-                    CallMediaType.Video -> VideoCallerAndCalleeAcceptedView(context)
-                    else -> AudioCallerWaitingAndAcceptedView(context)
-                }
-            }
-        }
-
-        if (functionLayout == null) {
-            Logger.e("functionLayout == null")
-        } else {
-            removeAllViews()
-            addView(functionLayout)
-        }
-    }
-
-    private fun selfIsCaller(): Boolean {
-        val selfId = CallStore.shared.observerState.selfInfo.value.id
-        val callerId = CallStore.shared.observerState.activeCall.value.inviterId
-        return selfId == callerId
-    }
-}

+ 0 - 378
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/VideoCallerAndCalleeAcceptedView.kt

@@ -1,378 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.controls
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import android.view.LayoutInflater
-import android.view.MotionEvent
-import android.view.View
-import android.widget.ImageView
-import android.widget.RelativeLayout
-import androidx.constraintlayout.utils.widget.ImageFilterView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.core.content.ContextCompat
-import androidx.transition.ChangeBounds
-import androidx.transition.TransitionManager
-import com.tencent.cloud.tuikit.engine.call.TUICallEngine
-import com.tencent.cloud.tuikit.engine.common.TUICommonDefine
-import com.tencent.qcloud.tuicore.permission.PermissionCallback
-import com.trtc.tuikit.common.imageloader.ImageLoader
-import com.trtc.tuikit.common.util.ScreenUtil
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.callview.core.common.Constants
-import io.trtc.tuikit.atomicx.callview.core.common.Constants.BLUR_LEVEL_CLOSE
-import io.trtc.tuikit.atomicx.callview.core.common.Constants.BLUR_LEVEL_HIGH
-import io.trtc.tuikit.atomicx.callview.core.common.utils.Logger
-import io.trtc.tuikit.atomicx.callview.core.common.utils.PermissionRequest
-import io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import io.trtc.tuikit.atomicxcore.api.device.AudioRoute
-import io.trtc.tuikit.atomicxcore.api.device.DeviceStatus
-import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.supervisorScope
-import java.util.concurrent.CopyOnWriteArraySet
-
-class VideoCallerAndCalleeAcceptedView(context: Context) : RelativeLayout(context) {
-    private var subscribeStateJob: Job? = null
-
-    private lateinit var rootView: ConstraintLayout
-    private lateinit var imageHangup: ImageFilterView
-    private lateinit var imageSwitchCamera: ImageView
-    private lateinit var imageExpandView: ImageView
-    private lateinit var imageBlurBackground: ImageView
-
-    private lateinit var buttonMicrophone: ControlButton
-    private lateinit var buttonAudioDevice: ControlButton
-    private lateinit var buttonCamera: ControlButton
-
-    private var defaultAudioButtonDrawable: Drawable? = null
-
-    private var isBottomViewExpand: Boolean = false
-    private var enableTransition: Boolean = false
-    private val originalSet = ConstraintSet()
-    private val rowSet = ConstraintSet()
-
-    private var hasTriggeredSlideAnimation: Boolean = false
-    private var isEnableBlurBackground:Boolean = false
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        this.layoutParams?.width = LayoutParams.MATCH_PARENT
-        this.layoutParams?.height = LayoutParams.MATCH_PARENT
-        enableTransition = true
-        initView()
-        registerObserver()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        subscribeStateJob?.cancel()
-    }
-
-    private fun registerObserver() {
-        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
-            supervisorScope {
-                launch { observeCameraStatus() }
-                launch { observeMicrophoneStatus() }
-                launch { observeAudioRoute() }
-            }
-        }
-    }
-
-    private suspend fun observeCameraStatus() {
-        DeviceStore.shared().deviceState.cameraStatus.collect { cameraStatus ->
-            val cameraIsOpened = (cameraStatus == DeviceStatus.ON)
-            buttonCamera.imageView.isActivated = cameraIsOpened
-            buttonCamera.textView.text = when {
-                cameraIsOpened -> context.getString(R.string.callview_toast_enable_camera)
-                else -> context.getString(R.string.callview_toast_disable_camera)
-            }
-            updateSwitchCameraAndBlurBackgroundButton(cameraIsOpened)
-        }
-    }
-
-    private suspend fun observeMicrophoneStatus() {
-        DeviceStore.shared().deviceState.microphoneStatus.collect { microphoneStatus ->
-            val isMute = microphoneStatus == DeviceStatus.OFF
-            val resId = if (isMute) {
-                R.string.callview_toast_enable_mute
-            } else {
-                R.string.callview_toast_disable_mute
-            }
-            buttonMicrophone.textView.text = context.getString(resId)
-            buttonMicrophone.imageView.isActivated = isMute
-        }
-    }
-
-    private suspend fun observeAudioRoute() {
-        DeviceStore.shared().deviceState.currentAudioRoute.collect { audioRoute ->
-            updateAudioRouteButton(audioRoute)
-        }
-    }
-
-    private fun initView() {
-        LayoutInflater.from(context).inflate(R.layout.callview_function_view_video, this)
-        rootView = findViewById(R.id.cl_view_video)
-        buttonMicrophone = findViewById(R.id.cb_microphone)
-        buttonAudioDevice = findViewById(R.id.cb_speaker)
-        buttonCamera = findViewById(R.id.cb_open_camera)
-
-        imageHangup = findViewById(R.id.iv_hang_up)
-        imageSwitchCamera = findViewById(R.id.iv_function_switch_camera)
-        imageBlurBackground = findViewById(R.id.img_blur_background)
-        imageExpandView = findViewById(R.id.iv_expanded)
-
-        val isMute = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.OFF
-        buttonMicrophone.imageView.isActivated = isMute
-        val micResId = if (isMute) R.string.callview_toast_disable_mute else R.string.callview_toast_enable_mute
-        buttonMicrophone.textView.text = context.getString(micResId)
-        val currentRoute = DeviceStore.shared().deviceState.currentAudioRoute.value
-        updateAudioRouteButton(currentRoute)
-        defaultAudioButtonDrawable = buttonAudioDevice.imageView.drawable
-        buttonMicrophone.visibility = VISIBLE
-        buttonAudioDevice.visibility = VISIBLE
-        buttonCamera.visibility = VISIBLE
-        imageExpandView.visibility = if (isMultiCall()) View.VISIBLE else View.GONE
-
-        val isCameraOpened = (DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON)
-        updateSwitchCameraAndBlurBackgroundButton(isCameraOpened)
-        originalSet.clone(rootView)
-        rowSet.clone(rootView)
-        buildRowConstraint(rowSet)
-        startAnimation(true)
-
-        initViewListener()
-    }
-
-    private fun updateAudioRouteButton(audioRoute: AudioRoute) {
-        val isSpeaker = audioRoute == AudioRoute.SPEAKERPHONE
-        val resId = if (isSpeaker) R.string.callview_text_speaker else R.string.callview_text_use_earpiece
-        buttonAudioDevice.textView.text = context.getString(resId)
-        buttonAudioDevice.imageView.isActivated = isSpeaker
-        buttonAudioDevice.imageView.setImageResource(R.drawable.callview_bg_audio_device)
-    }
-
-    private fun initViewListener() {
-        buttonMicrophone.setOnClickListener {
-            if (!buttonMicrophone.isEnabled) {
-                return@setOnClickListener
-            }
-            val isMicrophoneOpen = (DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ON)
-            if (isMicrophoneOpen) {
-                DeviceStore.shared().closeLocalMicrophone()
-            } else {
-                DeviceStore.shared().openLocalMicrophone(null)
-            }
-        }
-        buttonAudioDevice.setOnClickListener {
-            val currentAudioRoute = DeviceStore.shared().deviceState.currentAudioRoute.value
-            if (currentAudioRoute == AudioRoute.SPEAKERPHONE) {
-                DeviceStore.shared().setAudioRoute(AudioRoute.EARPIECE)
-            } else {
-                DeviceStore.shared().setAudioRoute(AudioRoute.SPEAKERPHONE)
-            }
-        }
-        buttonCamera.setOnClickListener {
-            if (!buttonCamera.isEnabled) {
-                return@setOnClickListener
-            }
-            if (DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON) {
-                DeviceStore.shared().closeLocalCamera()
-            } else {
-                openLocalCamera()
-            }
-        }
-
-        imageHangup.setOnClickListener {
-            imageHangup.roundPercent = 1.0f
-            imageHangup.setBackgroundColor(ContextCompat.getColor(context, R.color.callview_button_bg_red))
-            ImageLoader.loadGif(context, imageHangup, R.drawable.callview_hangup_loading)
-
-            disableButton(buttonMicrophone)
-            disableButton(buttonAudioDevice)
-            disableButton(buttonCamera)
-            disableButton(imageSwitchCamera)
-            disableButton(imageBlurBackground)
-
-            CallStore.shared.hangup(null)
-        }
-
-        imageExpandView.setOnClickListener() {
-            startAnimation(!isBottomViewExpand)
-        }
-
-        imageBlurBackground.setOnClickListener {
-            if (!imageBlurBackground.isEnabled) {
-                return@setOnClickListener
-            }
-            enableBlurBackground(!isEnableBlurBackground)
-        }
-
-        imageSwitchCamera.setOnClickListener() {
-            if (!imageSwitchCamera.isEnabled) {
-                return@setOnClickListener
-            }
-            val isFront = DeviceStore.shared().deviceState.isFrontCamera.value
-            DeviceStore.shared().switchCamera(!isFront)
-        }
-    }
-
-    private fun disableButton(button: View) {
-        button.isEnabled = false
-        button.alpha = 0.8f
-    }
-
-    private fun startAnimation(isExpand: Boolean) {
-        if (!isMultiCall()) {
-            return
-        }
-        if (!enableTransition) {
-            return
-        }
-        if (isExpand == isBottomViewExpand) {
-            return
-        }
-        rootView.background = ContextCompat.getDrawable(context, R.drawable.callview_bg_group_call_bottom)
-        isBottomViewExpand = isExpand
-
-        val transition = ChangeBounds().apply { duration = 300 }
-        TransitionManager.beginDelayedTransition(rootView, transition)
-        updateButtonSize(isExpand)
-        if (isExpand) {
-            originalSet.applyTo(rootView)
-        } else {
-            rowSet.applyTo(rootView)
-            imageExpandView.rotation = 180f
-        }
-        val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON
-        updateSwitchCameraAndBlurBackgroundButton(isCameraOpen)
-        imageExpandView.visibility = if (enableTransition) View.VISIBLE else View.GONE
-        setControlButtonTextVisible(isExpand)
-    }
-
-    private fun setControlButtonTextVisible(visible: Boolean) {
-        buttonMicrophone.textView.visibility = if (visible) View.VISIBLE else View.GONE
-        buttonAudioDevice.textView.visibility = if (visible) View.VISIBLE else View.GONE
-        buttonCamera.textView.visibility = if (visible) View.VISIBLE else View.GONE
-    }
-
-    private fun updateButtonSize(isExpand: Boolean) {
-        val size = if (isExpand) ScreenUtil.dip2px(60f) else ScreenUtil.dip2px(48f)
-        buttonMicrophone.imageView.layoutParams?.let { it.width = size; it.height = size }
-        buttonAudioDevice.imageView.layoutParams?.let { it.width = size; it.height = size }
-        buttonCamera.imageView.layoutParams?.let { it.width = size; it.height = size }
-    }
-
-    private fun buildRowConstraint(set: ConstraintSet) {
-        val disableButtonSet: MutableSet<Constants.ControlButton> = CopyOnWriteArraySet()
-
-        val buttonIds = mutableListOf(imageExpandView.id).apply {
-            if (!disableButtonSet.contains(Constants.ControlButton.Microphone)) {
-                add(buttonMicrophone.id)
-            }
-            if (!disableButtonSet.contains(Constants.ControlButton.AudioPlaybackDevice)) {
-                add(buttonAudioDevice.id)
-            }
-            if (!disableButtonSet.contains(Constants.ControlButton.Camera)) {
-                add(buttonCamera.id)
-            }
-            add(imageHangup.id)
-        }
-
-        buttonIds.forEach {
-            set.clear(it)
-            set.setVisibility(it, View.VISIBLE)
-            val size = if (it == imageHangup.id) ScreenUtil.dip2px(48f) else ConstraintSet.WRAP_CONTENT
-            set.constrainWidth(it, size)
-            set.constrainHeight(it, size)
-        }
-        set.createHorizontalChainRtl(
-            ConstraintSet.PARENT_ID, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.END,
-            buttonIds.toIntArray(), null, ConstraintSet.CHAIN_SPREAD
-        )
-        val margin = ScreenUtil.dip2px(20f)
-        buttonIds.forEach {
-            set.connect(it, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, margin)
-            set.connect(it, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, margin)
-        }
-        set.setMargin(imageExpandView.id, ConstraintSet.START, margin)
-        set.setMargin(imageHangup.id, ConstraintSet.END, margin)
-    }
-
-    override fun onTouchEvent(event: MotionEvent): Boolean {
-        when (event.action) {
-            MotionEvent.ACTION_DOWN -> {
-                touchStartY = event.y
-                hasTriggeredSlideAnimation = false
-            }
-
-            MotionEvent.ACTION_MOVE -> {
-                val selfUser = CallStore.shared.observerState.selfInfo.value.copy()
-                if (selfUser.status != CallParticipantStatus.Accept) {
-                    return true
-                }
-                if (!enableTransition || hasTriggeredSlideAnimation) {
-                    return true
-                }
-                val deltaY = event.y - touchStartY
-                val threshold = 50
-                if (deltaY < -threshold && !isBottomViewExpand) {
-                    startAnimation(true)
-                    hasTriggeredSlideAnimation = true
-                } else if (deltaY > threshold && isBottomViewExpand) {
-                    startAnimation(false)
-                    hasTriggeredSlideAnimation = true
-                }
-            }
-        }
-        return true
-    }
-
-    private var touchStartY: Float = 0f
-
-    private fun enableBlurBackground(enable: Boolean) {
-        Logger.i("setBlurBackground, enable: $enable")
-        val level = if (enable) BLUR_LEVEL_HIGH else BLUR_LEVEL_CLOSE
-        isEnableBlurBackground = enable
-        TUICallEngine.createInstance(context).setBlurBackground(level, object : TUICommonDefine.Callback {
-            override fun onSuccess() {
-                Logger.i("setBlurBackground success.")
-            }
-
-            override fun onError(errCode: Int, errMsg: String?) {
-                isEnableBlurBackground = false
-                Logger.e("setBlurBackground failed, errCode: $errCode, errMsg: $errMsg")
-            }
-        })
-    }
-
-    private fun openLocalCamera() {
-        PermissionRequest.requestCameraPermission(context, object : PermissionCallback() {
-            override fun onGranted() {
-                val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value
-                DeviceStore.shared().openLocalCamera(isFrontCamera, null)
-            }
-
-            override fun onDenied() {
-                Logger.e("openCamera failed, errMsg: camera permission denied")
-            }
-        })
-    }
-
-    private fun isMultiCall(): Boolean {
-        val inviteeIdListSize = CallStore.shared.observerState.activeCall.value.inviteeIds.size
-        val chatGroupId = CallStore.shared.observerState.activeCall.value.chatGroupId
-        return chatGroupId.isNotEmpty() || inviteeIdListSize > 1
-    }
-
-    private fun updateSwitchCameraAndBlurBackgroundButton(cameraIsOpened: Boolean) {
-        val visibility = if (cameraIsOpened && (!isMultiCall() || isBottomViewExpand)) VISIBLE else GONE
-        imageSwitchCamera.visibility = visibility
-        imageBlurBackground.visibility = visibility
-    }
-}

+ 0 - 150
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/controls/VideoCallerWaitingView.kt

@@ -1,150 +0,0 @@
-package io.trtc.tuikit.atomicx.callview.widget.controls
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.RelativeLayout
-import androidx.core.content.ContextCompat
-import com.tencent.cloud.tuikit.engine.call.TUICallEngine
-import com.tencent.cloud.tuikit.engine.common.TUICommonDefine
-import com.tencent.qcloud.tuicore.permission.PermissionCallback
-import com.trtc.tuikit.common.imageloader.ImageLoader
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.callview.core.common.Constants.BLUR_LEVEL_CLOSE
-import io.trtc.tuikit.atomicx.callview.core.common.Constants.BLUR_LEVEL_HIGH
-import io.trtc.tuikit.atomicx.callview.core.common.utils.Logger
-import io.trtc.tuikit.atomicx.callview.core.common.utils.PermissionRequest
-import io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-import io.trtc.tuikit.atomicxcore.api.call.CallStore
-import io.trtc.tuikit.atomicxcore.api.device.DeviceStatus
-import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-class VideoCallerWaitingView(context: Context) : RelativeLayout(context) {
-    private var subscribeStateJob: Job? = null
-
-    private lateinit var buttonCancel: ControlButton
-    private lateinit var buttonSwitchCamera: ControlButton
-    private lateinit var buttonCamera: ControlButton
-    private lateinit var buttonBlurBackground: ControlButton
-
-    private var isEnableBlurBackground:Boolean = false
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        this.layoutParams?.width = LayoutParams.MATCH_PARENT
-        this.layoutParams?.height = LayoutParams.MATCH_PARENT
-        initView()
-        registerCameraObserver()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        subscribeStateJob?.cancel()
-    }
-
-    private fun registerCameraObserver() {
-        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
-            DeviceStore.shared().deviceState.cameraStatus.collect { cameraStatus ->
-                val isCameraOpened = (cameraStatus == DeviceStatus.ON)
-                buttonCamera.imageView.isActivated = isCameraOpened
-            }
-        }
-    }
-
-    private fun initView() {
-        LayoutInflater.from(context).inflate(R.layout.callview_function_view_video_inviting, this)
-        buttonCancel = findViewById(R.id.cb_cancel)
-        buttonCamera = findViewById(R.id.cb_camera)
-        buttonSwitchCamera = findViewById(R.id.cb_switch_camera)
-        buttonBlurBackground = findViewById(R.id.cb_blur)
-
-        buttonSwitchCamera.visibility = VISIBLE
-        buttonCamera.visibility = VISIBLE
-
-        val isCameraOpened = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON
-        buttonCamera.imageView.isActivated = isCameraOpened
-        initViewListener()
-    }
-
-    private fun initViewListener() {
-        buttonCancel.setOnClickListener {
-            buttonCancel.imageView.roundPercent = 1.0f
-            buttonCancel.imageView.setBackgroundColor(ContextCompat.getColor(context, R.color.callview_button_bg_red))
-            ImageLoader.loadGif(context, buttonCancel.imageView, R.drawable.callview_hangup_loading)
-            disableButton(buttonCamera)
-            disableButton(buttonSwitchCamera)
-            disableButton(buttonBlurBackground)
-            CallStore.shared.hangup(null)
-        }
-        buttonSwitchCamera.setOnClickListener {
-            if (!buttonSwitchCamera.isEnabled) {
-                return@setOnClickListener
-            }
-            val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value
-            DeviceStore.shared().switchCamera(!isFrontCamera)
-        }
-        buttonCamera.setOnClickListener {
-            if (!buttonCamera.isEnabled) {
-                return@setOnClickListener
-            }
-            val isCameraOpened = (DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON)
-            buttonCamera.imageView.isActivated = !isCameraOpened
-            buttonSwitchCamera.imageView.isActivated = !isCameraOpened
-            buttonBlurBackground.imageView.isActivated = !isCameraOpened
-
-            if (isCameraOpened) {
-                DeviceStore.shared().closeLocalCamera()
-                buttonCamera.textView.text = context.resources.getString(R.string.callview_toast_disable_camera)
-            } else {
-                openLocalCamera()
-            }
-        }
-        buttonBlurBackground.setOnClickListener {
-            if (!buttonBlurBackground.isEnabled) {
-                return@setOnClickListener
-            }
-            enableBlurBackground(!isEnableBlurBackground)
-        }
-    }
-
-    private fun disableButton(button: View) {
-        button.isEnabled = false
-        button.alpha = 0.8f
-    }
-
-    private fun enableBlurBackground(enable: Boolean) {
-        Logger.i("setBlurBackground, enable: $enable")
-        val level = if (enable) BLUR_LEVEL_HIGH else BLUR_LEVEL_CLOSE
-        buttonBlurBackground.isActivated = enable
-        isEnableBlurBackground = enable
-        TUICallEngine.createInstance(context).setBlurBackground(level, object : TUICommonDefine.Callback {
-            override fun onSuccess() {
-                Logger.i("setBlurBackground success.")
-            }
-
-            override fun onError(errCode: Int, errMsg: String?) {
-                buttonBlurBackground.isActivated = false
-                isEnableBlurBackground = false
-                Logger.e("setBlurBackground failed, errCode: $errCode, errMsg: $errMsg")
-            }
-        })
-    }
-
-    private fun openLocalCamera() {
-        PermissionRequest.requestCameraPermission(context, object : PermissionCallback() {
-            override fun onGranted() {
-                val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value
-                DeviceStore.shared().openLocalCamera(isFrontCamera, null)
-                buttonCamera.textView.text = context.resources.getString(R.string.callview_toast_enable_camera)
-            }
-
-            override fun onDenied() {
-                Logger.e("openCamera failed, errMsg: camera permission denied")
-            }
-        })
-    }
-}

+ 0 - 34
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/service/MusicServiceFactory.kt

@@ -1,34 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.service
-
-import io.trtc.tuikit.atomicx.karaoke.store.MusicCatalogService
-import com.trtc.tuikit.common.system.ContextProvider
-
-const val PACKAGE_RT_CUBE = "com.tencent.trtc"
-class SongServiceFactory {
-    companion object {
-        private const val ONLINE_MUSIC_SERVICE_CLASS = "com.tencent.liteav.karaoke.service.OnlineMusicService"
-        private const val LOCAL_MUSIC_SERVICE_CLASS = "com.tencent.uikit.app.login.LocalMusicService"
-
-        fun getInstance(): MusicCatalogService? {
-            val serviceClassName = if (isInternalDemo()) {
-                ONLINE_MUSIC_SERVICE_CLASS
-            } else {
-                LOCAL_MUSIC_SERVICE_CLASS
-            }
-
-            return try {
-                val clz = Class.forName(serviceClassName)
-                val constructor = clz.getConstructor()
-                constructor.newInstance() as MusicCatalogService
-            } catch (e: Exception) {
-                e.printStackTrace()
-                null
-            }
-        }
-
-        private fun isInternalDemo(): Boolean {
-            return PACKAGE_RT_CUBE == ContextProvider.getApplicationContext().packageName
-        }
-
-    }
-}

+ 0 - 25
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/CallbacksDefine.kt

@@ -1,25 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.store
-
-import io.trtc.tuikit.atomicx.karaoke.store.utils.MusicInfo
-
-interface QueryPlayTokenCallBack {
-    fun onSuccess(
-        musicId: String, playToken: String,
-        copyrightedLicenseKey: String?,
-        copyrightedLicenseUrl: String?,
-    )
-
-    fun onFailure(code: Int, desc: String)
-}
-
-
-interface GetSongListCallBack {
-    fun onSuccess(songList: List<MusicInfo>)
-    fun onFailure(code: Int, desc: String)
-}
-
-interface ActionCallback {
-    fun onSuccess(userSig: String)
-
-    fun onFailed(code: Int, msg: String?)
-}

+ 0 - 1205
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/KaraokeStore.kt

@@ -1,1205 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.store
-
-import android.content.Context
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import com.google.gson.Gson
-import com.google.gson.reflect.TypeToken
-import com.tencent.cloud.tuikit.engine.common.TUICommonDefine
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager.SongInfo
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager.SongListChangeReason
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager.SongListResult
-import com.tencent.cloud.tuikit.engine.room.TUIRoomDefine
-import com.tencent.cloud.tuikit.engine.room.TUIRoomDefine.GetRoomMetadataCallback
-import com.tencent.cloud.tuikit.engine.room.TUIRoomDefine.RoomDismissedReason
-import com.tencent.cloud.tuikit.engine.room.TUIRoomEngine
-import com.tencent.cloud.tuikit.engine.room.TUIRoomObserver
-import com.tencent.qcloud.tuicore.TUILogin
-import com.tencent.trtc.TRTCCloud
-import com.tencent.trtc.TRTCCloudDef
-import com.tencent.trtc.TRTCCloudDef.TRTCParams
-import com.tencent.trtc.TRTCCloudListener
-import com.tencent.trtc.TXChorusMusicPlayer
-import com.tencent.trtc.TXChorusMusicPlayer.TXChorusRole
-import com.tencent.trtc.txcopyrightedmedia.TXCopyrightedMedia
-import com.trtc.tuikit.common.util.ToastUtil
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.karaoke.service.SongServiceFactory
-import io.trtc.tuikit.atomicx.karaoke.store.utils.LyricsFileReader
-import io.trtc.tuikit.atomicx.karaoke.store.utils.MusicInfo
-import io.trtc.tuikit.atomicx.karaoke.store.utils.PlaybackState
-import io.trtc.tuikit.atomicx.widget.basicwidget.toast.AtomicToast
-import io.trtc.tuikit.atomicxcore.api.live.LiveListStore
-import java.io.File
-import java.io.FileOutputStream
-import java.nio.ByteBuffer
-
-private const val TAG = "KaraokeStore"
-private const val LOCAL_MUSIC_PREFIX = "local_demo"
-private const val KEY_ENABLE_REQUEST_MUSIC = "EnableRequestMusic"
-private const val KEY_ENABLE_SCORE = "EnableScore"
-
-class KaraokeStore private constructor(private val context: Context) {
-    companion object {
-        @Volatile
-        private var instance: KaraokeStore? = null
-
-        @JvmStatic
-        fun getInstance(context: Context): KaraokeStore {
-            return instance ?: synchronized(this) {
-                instance ?: KaraokeStore(context.applicationContext).also { instance = it }
-            }
-        }
-
-        @JvmStatic
-        fun destroyInstance() {
-            instance?.destroy()
-            instance = null
-        }
-    }
-
-    var isAwaitingScoreDisplay = true
-
-    val hostPitch: LiveData<Int> get() = _hostPitch
-    val hostScore: LiveData<Int> get() = _hostScore
-    val currentPlayingSong: LiveData<SongInfo> get() = _currentPlayingSong
-    val isRoomOwner: LiveData<Boolean> get() = _isRoomOwner
-    val songCatalog: LiveData<List<MusicInfo>> get() = _songCatalog
-    val songQueue: LiveData<List<SongInfo>> get() = _songQueue
-    val currentTrack: LiveData<TXChorusMusicPlayer.TXChorusMusicTrack> get() = _currentAudioTrack
-    val playbackProgressMs: LiveData<Long> get() = _playbackProgressMs
-    val songDurationMs: LiveData<Long> get() = _songDurationMs
-    val playbackState: LiveData<PlaybackState> get() = _playbackState
-    val isDisplayFloatView: LiveData<Boolean> get() = _isDisplayFloatView
-    val pitchList: LiveData<List<TXChorusMusicPlayer.TXReferencePitch>> get() = _pitchList
-    val songLyrics: LiveData<List<TXChorusMusicPlayer.TXLyricLine>> get() = _songLyrics
-    val currentScore: LiveData<Int> get() = _currentScore
-    val averageScore: LiveData<Int> get() = _averageScore
-    val currentPitch: LiveData<Int> get() = _currentPitch
-    val publishVolume: LiveData<Int> get() = _publishVolume
-    val playoutVolume: LiveData<Int> get() = _playoutVolume
-    val songPitch: LiveData<Float> get() = _songPitch
-    val enableScore: LiveData<Boolean> get() = _enableScore
-    val isRoomDismissed: LiveData<Boolean> get() = _isRoomDismissed
-    private val songListManager: TUISongListManager = TUIRoomEngine.sharedInstance().songListManager
-    private val trtcCloud: TRTCCloud = TUIRoomEngine.sharedInstance().trtcCloud
-    private val gson = Gson()
-    private val mainHandler = Handler(Looper.getMainLooper())
-    private var musicCatalogService: MusicCatalogService? = null
-    private var chorusPlayer: TXChorusMusicPlayer? = null
-    private lateinit var userId: String
-    private var ownerId: String? = null
-    private var isFullScreenUIMode = false
-    private var _isManualStop = false
-    private var _isSwitchingToNext = false
-    private var _isCurrentSongRemoved = false
-    private var _loadingMusicId: String? = null
-
-    private val _hostPitch = MutableLiveData(0)
-    private val _hostScore = MutableLiveData(-1)
-    private val _currentPlayingSong = MutableLiveData<SongInfo>()
-    private val _isRoomOwner = MutableLiveData(false)
-    private val _songCatalog = MutableLiveData<List<MusicInfo>>(emptyList())
-    private val _songQueue = MutableLiveData<List<SongInfo>>(emptyList())
-    private val _currentAudioTrack = MutableLiveData(TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong)
-    private val _playbackProgressMs = MutableLiveData(0L)
-    private val _songDurationMs = MutableLiveData(0L)
-    private val _playbackState = MutableLiveData(PlaybackState.IDLE)
-    private val _isDisplayFloatView = MutableLiveData(true)
-    private val _pitchList = MutableLiveData<List<TXChorusMusicPlayer.TXReferencePitch>>(emptyList())
-    private val _songLyrics = MutableLiveData<List<TXChorusMusicPlayer.TXLyricLine>>(emptyList())
-    private val _currentScore = MutableLiveData(-1)
-    private val _averageScore = MutableLiveData(0)
-    private val _currentPitch = MutableLiveData(0)
-    private val _publishVolume = MutableLiveData(60)
-    private val _playoutVolume = MutableLiveData(95)
-    private val _songPitch = MutableLiveData(0.0F)
-    private val _enableScore = MutableLiveData(true)
-    private val _isRoomDismissed = MutableLiveData(false)
-
-    fun init(roomId: String, isOwner: Boolean) {
-        Log.d(TAG, "init: roomId=$roomId, isOwner=$isOwner")
-        if (chorusPlayer != null) return
-        _isRoomOwner.value = isOwner
-        ownerId = LiveListStore.shared().liveState.currentLive.value.liveOwner.userID
-        userId = TUIRoomEngine.getSelfInfo().userId
-        musicCatalogService = SongServiceFactory.getInstance()
-        setupChorusPlayer(roomId)
-        copyAllAssetsToStorage()
-        fetchRoomMetadata()
-        getWaitingList()
-        loadMusicCatalog()
-        addObserver()
-        if (isOwner) {
-            setScoringEnabled(enableScore.value == true)
-            applyDefaultAudioEffects()
-        }
-    }
-
-    fun destroy() {
-        Log.d(TAG, "destroy: called")
-        stopPlayback()
-        _isRoomDismissed.value = true
-        _songQueue.value = emptyList()
-        _currentPlayingSong.value = SongInfo()
-        _playbackProgressMs.value = 0L
-        _songLyrics.value = emptyList()
-        _pitchList.value = emptyList()
-        _currentScore.value = 0
-        _playbackState.value = PlaybackState.IDLE
-        _isManualStop = true
-        _isSwitchingToNext = false
-        _isCurrentSongRemoved = false
-        _loadingMusicId = null
-        _averageScore.value = 100
-        _currentAudioTrack.value = TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong
-        removeObserver()
-        chorusPlayer?.destroy()
-        chorusPlayer = null
-    }
-
-    fun addObserver() {
-        Log.d(TAG, "addObserver")
-        songListManager.addObserver(songListObserver)
-        TUIRoomEngine.sharedInstance().addObserver(roomEngineObserver)
-        TUIRoomEngine.sharedInstance().trtcCloud.setAudioFrameListener(audioFrameListener)
-    }
-
-    fun removeObserver() {
-        Log.d(TAG, "removeObserver")
-        songListManager.removeObserver(songListObserver)
-        TUIRoomEngine.sharedInstance().removeObserver(roomEngineObserver)
-        TUIRoomEngine.sharedInstance().trtcCloud.setAudioFrameListener(null)
-    }
-
-    fun enableRequestMusic(enable: Boolean) {
-        Log.d(TAG, "enableRequestMusic: enable=$enable")
-        if (_isDisplayFloatView.value != enable) {
-            val metadata = hashMapOf(KEY_ENABLE_REQUEST_MUSIC to enable.toString())
-            TUIRoomEngine.sharedInstance().setRoomMetadataByAdmin(metadata, null)
-            if (!enable) {
-                clearAllSongs()
-                _songQueue.value = emptyList()
-                _currentPlayingSong.value = SongInfo()
-                _playbackProgressMs.value = 0L
-                _songLyrics.value = emptyList()
-                _pitchList.value = emptyList()
-                _currentScore.value = 0
-                _playbackState.value = PlaybackState.IDLE
-                _averageScore.value = 100
-                _currentAudioTrack.value =
-                    TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong
-                _isManualStop = true
-                stopPlayback()
-            }
-        }
-    }
-
-    private fun loadMusicByLeadSinger() {
-        Log.d(TAG, "loadMusicByLeadSinger: isOwner=${isRoomOwner.value}, queueSize=${songQueue.value?.size}")
-        if (isRoomOwner.value == false) return
-        val songQueueValue = songQueue.value
-        if (songQueueValue.isNullOrEmpty()) {
-            _playbackState.value = PlaybackState.IDLE
-            _loadingMusicId = null
-            _isCurrentSongRemoved = false
-            return
-        }
-        val firstSong = songQueueValue.first()
-        val songId = firstSong.songId
-        if (!songId.isNullOrEmpty()) {
-            _isCurrentSongRemoved = false
-            _loadingMusicId = songId
-            Log.d(TAG, "loadMusicByLeadSinger: loading songId=$songId, songName=${firstSong.songName}")
-            loadMusic(songId)
-        }
-    }
-
-    private fun loadMusic(musicId: String) {
-        Log.d(TAG, "loadMusic: musicId=$musicId")
-        if (musicId.startsWith(LOCAL_MUSIC_PREFIX)) {
-            loadLocalDemoMusic(musicId)
-        } else loadCopyrightedMusic(musicId)
-    }
-
-    private fun loadLocalDemoMusic(musicId: String) {
-        Log.d(TAG, "loadLocalDemoMusic: musicId=$musicId")
-        val musicInfo = findSongInCatalog(musicId) ?: return
-        val params = TXChorusMusicPlayer.TXChorusExternalMusicParams().apply {
-            this.musicId = musicInfo.musicId
-            musicUrl = musicInfo.originalUrl
-            accompanyUrl = musicInfo.accompanyUrl
-            encryptBlockLength = 0
-            isEncrypted = false
-        }
-        chorusPlayer?.loadExternalMusic(params)
-    }
-
-    private fun loadCopyrightedMusic(musicId: String) {
-        Log.d(TAG, "loadCopyrightedMusic: musicId=$musicId")
-        musicCatalogService?.queryPlayToken(musicId, userId, object : QueryPlayTokenCallBack {
-            override fun onSuccess(
-                musicId: String,
-                playToken: String,
-                copyrightedLicenseKey: String?,
-                copyrightedLicenseUrl: String?,
-            ) {
-                Log.d(TAG, "loadCopyrightedMusic: queryPlayToken success, musicId=$musicId")
-                val params = TXChorusMusicPlayer.TXChorusCopyrightedMusicParams().apply {
-                    this.musicId = musicId
-                    this.playToken = playToken
-                    this.copyrightedLicenseKey = copyrightedLicenseKey
-                    this.copyrightedLicenseUrl = copyrightedLicenseUrl
-                }
-                chorusPlayer?.loadMusic(params)
-            }
-
-            override fun onFailure(code: Int, desc: String) {
-                Log.e(TAG, "loadCopyrightedMusic: queryPlayToken failed, code=$code, desc=$desc")
-                onKaraokeError(code, desc)
-            }
-        })
-    }
-
-    fun loadMusicCatalog() {
-        Log.d(TAG, "loadMusicCatalog")
-        musicCatalogService?.getSongList(object : GetSongListCallBack {
-            override fun onSuccess(songList: List<MusicInfo>) {
-                Log.d(TAG, "loadMusicCatalog: success, count=${songList.size}")
-                _songCatalog.postValue(songList)
-            }
-
-            override fun onFailure(code: Int, desc: String) {
-                Log.e(TAG, "loadMusicCatalog: failed, code=$code, desc=$desc")
-                onKaraokeError(code, desc)
-            }
-        })
-    }
-
-    fun setChorusRole(roomId: String, chorusRole: TXChorusRole) {
-        Log.d(TAG, "setChorusRole: roomId=$roomId, role=$chorusRole")
-        val robotID = "${roomId}_bgm"
-        musicCatalogService?.generateUserSig(robotID, object : ActionCallback {
-            override fun onSuccess(robotSig: String) {
-                Log.d(TAG, "setChorusRole: generateUserSig success")
-                val params = TRTCParams().apply {
-                    this.sdkAppId = TUILogin.getSdkAppId()
-                    strRoomId = roomId
-                    userId = robotID
-                    userSig = robotSig
-                }
-                chorusPlayer?.setChorusRole(chorusRole, params)
-            }
-
-            override fun onFailed(code: Int, msg: String?) {
-                Log.e(TAG, "setChorusRole: generateUserSig failed, code=$code")
-                onKaraokeError(code, msg)
-            }
-
-        })
-
-    }
-
-    fun startPlayback() {
-        Log.d(TAG, "startPlayback: isOwner=${isRoomOwner.value}")
-        if (isRoomOwner.value == true) {
-            chorusPlayer?.start()
-            switchMusicTrack(TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong, true)
-        }
-    }
-
-    fun stopPlayback() {
-        Log.d(TAG, "stopPlayback: isOwner=${isRoomOwner.value}")
-        if (isRoomOwner.value == true) {
-            chorusPlayer?.stop()
-        }
-    }
-
-    fun pausePlayback() {
-        Log.d(TAG, "pausePlayback: isOwner=${isRoomOwner.value}")
-        if (isRoomOwner.value == true) {
-            chorusPlayer?.pause()
-        }
-    }
-
-    fun resumePlayback() {
-        Log.d(TAG, "resumePlayback: isOwner=${isRoomOwner.value}")
-        if (isRoomOwner.value == true) {
-            chorusPlayer?.resume()
-        }
-    }
-
-    fun switchMusicTrack(
-        trackType: TXChorusMusicPlayer.TXChorusMusicTrack,
-        isInitial: Boolean = false
-    ) {
-        Log.d(TAG, "switchMusicTrack: trackType=$trackType, isInitial=$isInitial")
-        if (isRoomOwner.value != true) return
-
-        val songId = songQueue.value?.firstOrNull()?.songId ?: return
-        val media = TXCopyrightedMedia.instance()
-        media.init()
-        val hasOrigin = !media.genMusicURI(songId, 0, "audio/hi").isNullOrEmpty()
-        val hasAccompany = !media.genMusicURI(songId, 1, "audio/hi").isNullOrEmpty()
-
-        val finalTrack = if (isInitial) {
-            if (hasOrigin) TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong
-            else if (hasAccompany) TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusAccompaniment
-            else null
-        } else {
-            val isTargetAvailable = if (trackType == TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong) hasOrigin else hasAccompany
-
-            if (!isTargetAvailable) {
-                AtomicToast.show(context, context.getString(R.string.karaoke_cant_switch_tracks), AtomicToast.Style.ERROR)
-                return
-            }
-            trackType
-        }
-
-        finalTrack?.let {
-            chorusPlayer?.switchMusicTrack(it)
-            _currentAudioTrack.value = it
-        }
-    }
-
-    fun setPlayoutVolume(volume: Int?) {
-        Log.d(TAG, "setPlayoutVolume: volume=$volume")
-        volume?.let {
-            chorusPlayer?.setPlayoutVolume(it)
-            _playoutVolume.value = it
-        }
-    }
-
-    fun setPublishVolume(volume: Int?) {
-        Log.d(TAG, "setPublishVolume: volume=$volume")
-        volume?.let {
-            chorusPlayer?.setPublishVolume(it)
-            _publishVolume.value = it
-        }
-    }
-
-    fun setMusicPitch(pitch: Float?) {
-        Log.d(TAG, "setMusicPitch: pitch=$pitch")
-        pitch?.let {
-            chorusPlayer?.setMusicPitch(it)
-            _songPitch.value = it
-        }
-    }
-
-    fun addSong(song: SongInfo) {
-        Log.d(TAG, "addSong: songId=${song.songId}, songName=${song.songName}")
-        songListManager.addSong(listOf(song), object : TUIRoomDefine.ActionCallback {
-            override fun onSuccess() {
-                Log.d(TAG, "addSong: success")
-            }
-
-            override fun onError(
-                code: TUICommonDefine.Error?,
-                message: String?,
-            ) {
-                Log.e(TAG, "addSong: error, code=${code?.value}, msg=$message")
-                onKaraokeError(code?.value, message)
-            }
-
-        })
-    }
-
-    fun removeSong(song: SongInfo) {
-        Log.d(TAG, "removeSong: songId=${song.songId}")
-        songListManager.removeSong(listOf(song.songId), object : TUIRoomDefine.ActionCallback {
-            override fun onSuccess() {
-                Log.d(TAG, "removeSong: success")
-            }
-
-            override fun onError(
-                code: TUICommonDefine.Error?,
-                message: String?,
-            ) {
-                Log.e(TAG, "removeSong: error, code=${code?.value}, msg=$message")
-                onKaraokeError(code?.value, message)
-            }
-
-        })
-    }
-
-    fun clearAllSongs() {
-        Log.d(TAG, "clearAllSongs: queueSize=${_songQueue.value?.size}")
-        val currentQueue = _songQueue.value.orEmpty()
-        if (currentQueue.isEmpty()) {
-            return
-        }
-        songListManager.removeSong( currentQueue.map { it.songId }, object : TUIRoomDefine.ActionCallback {
-            override fun onSuccess() {
-                Log.d(TAG, "clearAllSongs: success")
-            }
-
-            override fun onError(
-                code: TUICommonDefine.Error?,
-                message: String?,
-            ) {
-                Log.e(TAG, "clearAllSongs: error, code=${code?.value}")
-                onKaraokeError(code?.value, message)
-            }
-        })
-    }
-
-    fun setNextSong(targetSongId: String) {
-        Log.d(TAG, "setNextSong: targetSongId=$targetSongId")
-        songListManager.setNextSong(targetSongId, object : TUIRoomDefine.ActionCallback {
-            override fun onSuccess() {
-                Log.d(TAG, "setNextSong: success")
-            }
-
-            override fun onError(
-                code: TUICommonDefine.Error?,
-                message: String?,
-            ) {
-                Log.e(TAG, "setNextSong: error, code=${code?.value}")
-                onKaraokeError(code?.value, message)
-            }
-        })
-    }
-
-    fun playNextSong() {
-        val currentQueue = _songQueue.value.orEmpty()
-        Log.d(TAG, "playNextSong: called, isRoomOwner=${_isRoomOwner.value}, queueSize=${currentQueue.size}, isSwitchingToNext=$_isSwitchingToNext, currentPlaying=${_currentPlayingSong.value?.songName}")
-        if (_isRoomOwner.value != true) {
-            Log.d(TAG, "playNextSong: not room owner, set IDLE")
-            _playbackState.value = PlaybackState.IDLE
-            return
-        }
-        if (currentQueue.isEmpty()) {
-            Log.d(TAG, "playNextSong: song queue is empty, skip playNextSong")
-            _playbackState.value = PlaybackState.IDLE
-            return
-        }
-        if (_isSwitchingToNext) {
-            Log.d(TAG, "playNextSong: already switching, skip")
-            return
-        }
-        _isSwitchingToNext = true
-        Log.d(TAG, "playNextSong: start switching to next song, will remove first: ${currentQueue.firstOrNull()?.songName}")
-
-        songListManager.playNextSong(object : TUIRoomDefine.ActionCallback {
-            override fun onSuccess() {
-                Log.d(TAG, "playNextSong: SDK playNextSong success")
-            }
-
-            override fun onError(
-                code: TUICommonDefine.Error?,
-                message: String?,
-            ) {
-                Log.e(TAG, "playNextSong: SDK playNextSong error: $message")
-                _isSwitchingToNext = false
-                onKaraokeError(code?.value, message)
-            }
-        })
-    }
-
-    fun setIsDisplayScoreView(isDisplay: Boolean) {
-        Log.d(TAG, "setIsDisplayScoreView: isDisplay=$isDisplay")
-        isAwaitingScoreDisplay = isDisplay
-    }
-
-    fun setFullScreenUIMode(isFullScreen: Boolean) {
-        Log.d(TAG, "setFullScreenUIMode: isFullScreen=$isFullScreen")
-        this.isFullScreenUIMode = isFullScreen
-    }
-
-    fun updatePlaybackStatus(state: PlaybackState) {
-        Log.d(TAG, "updatePlaybackStatus: state=$state, currentState=${_playbackState.value}")
-        if (_playbackState.value == PlaybackState.STOP) {
-            _playbackState.value = state
-        }
-    }
-
-    fun setScoringEnabled(enable: Boolean) {
-        Log.d(TAG, "setScoringEnabled: enable=$enable")
-        _enableScore.value = enable
-        val metadata = hashMapOf(KEY_ENABLE_SCORE to enable.toString())
-        TUIRoomEngine.sharedInstance().setRoomMetadataByAdmin(metadata, null)
-    }
-
-    fun updateSongCatalog(selectedList: List<MusicInfo>) {
-        Log.d(TAG, "updateSongCatalog: count=${selectedList.size}")
-        _songCatalog.value = selectedList
-    }
-
-    private fun setupChorusPlayer(roomId: String) {
-        Log.d(TAG, "setupChorusPlayer: roomId=$roomId, isOwner=${_isRoomOwner.value}")
-        chorusPlayer = TXChorusMusicPlayer.create(trtcCloud, roomId, chorusMusicObserver)
-        val role = if (_isRoomOwner.value == true) {
-            TXChorusRole.TXChorusRoleLeadSinger
-        } else {
-            TXChorusRole.TXChorusRoleAudience
-        }
-        setChorusRole(roomId, role)
-    }
-
-    private fun getWaitingList() {
-        Log.d(TAG, "getWaitingList")
-        val allSongsAccumulator = mutableListOf<SongInfo>()
-        fetchNextPage(null, allSongsAccumulator, false)
-    }
-
-    private fun fetchWaitingListAndLoadFirst() {
-        Log.d(TAG, "fetchWaitingListAndLoadFirst: clearing loadingMusicId=$_loadingMusicId")
-        _loadingMusicId = null
-        val allSongsAccumulator = mutableListOf<SongInfo>()
-        fetchNextPage(null, allSongsAccumulator, true)
-    }
-
-    private fun fetchNextPage(cursor: String?, currentAccumulator: MutableList<SongInfo>, loadFirstAfterFetch: Boolean) {
-        val count = 20
-        songListManager.getWaitingList(cursor, count, object : TUISongListManager.SongListCallback {
-            override fun onSuccess(result: SongListResult?) {
-                if (result == null) {
-                    _songQueue.value = currentAccumulator
-                    if (loadFirstAfterFetch) {
-                        Log.d(TAG, "fetchNextPage: fetch complete (null result), loading first song")
-                        loadMusicByLeadSinger()
-                    }
-                    return
-                }
-                if (!result.songList.isNullOrEmpty()) {
-                    currentAccumulator.addAll(result.songList)
-                }
-                val nextCursor = result.cursor
-                val hasMoreData = !nextCursor.isNullOrEmpty()
-
-                if (hasMoreData) {
-                    fetchNextPage(nextCursor, currentAccumulator, loadFirstAfterFetch)
-                } else {
-                    Log.i(TAG, "finished fetching all songs. total count: ${currentAccumulator.size}, songs=${currentAccumulator.map { it.songName }}")
-                    _songQueue.value = currentAccumulator
-                    if (loadFirstAfterFetch) {
-                        Log.d(TAG, "fetchNextPage: fetch complete, loading first song: ${currentAccumulator.firstOrNull()?.songName}")
-                        loadMusicByLeadSinger()
-                    }
-                }
-            }
-
-            override fun onError(code: TUICommonDefine.Error?, msg: String?) {
-                onKaraokeError(code?.value, msg)
-                if (loadFirstAfterFetch) {
-                    loadMusicByLeadSinger()
-                }
-            }
-        })
-    }
-
-    private fun fetchRoomMetadata() {
-        Log.d(TAG, "fetchRoomMetadata")
-        TUIRoomEngine.sharedInstance().getRoomMetadata(
-            listOf(KEY_ENABLE_SCORE, KEY_ENABLE_REQUEST_MUSIC), object : GetRoomMetadataCallback {
-                override fun onSuccess(map: HashMap<String?, String?>?) {
-                    Log.d(TAG, "fetchRoomMetadata: success, map=$map")
-                    map?.let {
-                        if (isRoomOwner.value == true) {
-                            if (_enableScore.value == false) {
-                                setScoringEnabled(true)
-                            }
-                        } else {
-                            _enableScore.value = it[KEY_ENABLE_SCORE]?.toBoolean() ?: true
-                            _isDisplayFloatView.value =
-                                it[KEY_ENABLE_REQUEST_MUSIC]?.toBoolean() ?: true
-                        }
-                    }
-                }
-
-                override fun onError(error: TUICommonDefine.Error?, message: String) {
-                    Log.e(TAG, "fetchRoomMetadata: error, code=${error?.value}, msg=$message")
-                    onError(error, message)
-                }
-            })
-    }
-
-    private fun findSongInCatalog(musicId: String): MusicInfo? {
-        return songCatalog.value?.firstOrNull { it.musicId == musicId }
-    }
-
-    private fun findSongLyricPath(musicId: String): String {
-        return _songCatalog.value?.find { it.musicId == musicId }?.lyricUrl ?: ""
-    }
-
-    private fun getSongInfoById(musicId: String): SongInfo {
-        val songInQueue = _songQueue.value?.find { it.songId == musicId }
-        if (songInQueue != null) {
-            return songInQueue
-        }
-        return SongInfo().apply {
-            this.songId = musicId
-        }
-    }
-
-    private val songListObserver: TUISongListManager.Observer =
-        object : TUISongListManager.Observer() {
-            override fun onWaitingListChanged(
-                reason: SongListChangeReason?,
-                changedSongs: MutableList<SongInfo?>?,
-            ) {
-                Log.d(TAG, "onWaitingListChanged: reason=$reason, changedCount=${changedSongs?.size}")
-                updateSongQueue(reason, changedSongs)
-                handlePlayOperation(reason, changedSongs)
-            }
-        }
-
-    private fun updateSongQueue(
-        reason: SongListChangeReason?,
-        changedSongs: MutableList<SongInfo?>?,
-    ) {
-        val currentQueue = _songQueue.value.orEmpty().toMutableList()
-        if (reason == null || changedSongs.isNullOrEmpty()) {
-            return
-        }
-        Log.d(
-            TAG,
-            "updateSongQueue: reason=${reason.name}, changedSongs=${changedSongs.mapNotNull { it?.songName }}, currentQueue=${currentQueue.map { it.songName }}"
-        )
-        when (reason) {
-            SongListChangeReason.ADD -> {
-                changedSongs.filterNotNull().forEach { newSong ->
-                    if (currentQueue.none { it.songId == newSong.songId }) {
-                        currentQueue.add(newSong)
-                    }
-                }
-                _songQueue.value = currentQueue
-            }
-
-            SongListChangeReason.REMOVE -> {
-                val removeSongIds = changedSongs.filterNotNull().map { it.songId }
-                val songsToRemove = currentQueue.filter { it.songId in removeSongIds }
-                currentQueue.removeAll(songsToRemove)
-                _songQueue.value = currentQueue
-                Log.d(TAG, "updateSongQueue REMOVE: removed=${songsToRemove.map { it.songName }}, remaining=${currentQueue.map { it.songName }}")
-            }
-
-            SongListChangeReason.ORDER_CHANGED -> {
-                if (_isSwitchingToNext) {
-                    val songToRemove = changedSongs.filterNotNull().firstOrNull()
-                    if (songToRemove != null) {
-                        currentQueue.removeAll { it.songId == songToRemove.songId }
-                        _songQueue.value = currentQueue
-                        Log.d(TAG, "updateSongQueue ORDER_CHANGED (switching): removed ${songToRemove.songName}, remaining=${currentQueue.map { it.songName }}")
-                        return
-                    }
-                }
-                
-                val songToMoveUp = changedSongs.filterNotNull().firstOrNull()
-                if (songToMoveUp == null) {
-                    Log.d(TAG, "updateSongQueue ORDER_CHANGED: no song to move")
-                    return
-                }
-                
-                currentQueue.removeAll { it.songId == songToMoveUp.songId }
-                val targetIndex = minOf(1, currentQueue.size)
-                currentQueue.add(targetIndex, songToMoveUp)
-                _songQueue.value = currentQueue
-                Log.d(TAG, "updateSongQueue ORDER_CHANGED: moved ${songToMoveUp.songName} to index $targetIndex, newQueue=${currentQueue.map { it.songName }}")
-            }
-
-            SongListChangeReason.UNKNOWN -> {
-                val newQueue = changedSongs.filterNotNull()
-                Log.d(TAG, "updateSongQueue UNKNOWN: using server queue=${newQueue.map { it.songName }}")
-                _songQueue.value = newQueue
-                return
-            }
-        }
-
-        val currentPlayingId = _currentPlayingSong.value?.songId
-        if (!currentPlayingId.isNullOrEmpty()) {
-            val finalQueue = _songQueue.value.orEmpty()
-            val songInNewQueue = finalQueue.find { it.songId == currentPlayingId }
-            if (songInNewQueue != null) {
-                _currentPlayingSong.postValue(songInNewQueue)
-                Log.d(TAG, "Sync current playing info from new queue: ${songInNewQueue.songName}")
-            }
-        }
-        Log.d(TAG, "updateSongQueue: final songQueue=${_songQueue.value?.map { it.songName }}")
-    }
-
-    private fun handlePlayOperation(
-        reason: SongListChangeReason?,
-        changedSongs: MutableList<SongInfo?>?,
-    ) {
-        if (reason == null || changedSongs.isNullOrEmpty()) {
-            return
-        }
-        Log.d(TAG, "handlePlayOperation: reason=$reason, changedSongs=${changedSongs.mapNotNull { it?.songName }}")
-        when (reason) {
-            SongListChangeReason.ADD -> {
-                val isNeedLoadMusic = songQueue.value?.size == changedSongs.size
-                Log.d(TAG, "handlePlayOperation ADD: queueSize=${songQueue.value?.size}, changedSize=${changedSongs.size}, isNeedLoadMusic=$isNeedLoadMusic")
-                if (isNeedLoadMusic) loadMusicByLeadSinger()
-            }
-
-            SongListChangeReason.REMOVE -> {
-                val currentPlayingId = _currentPlayingSong.value?.songId
-                val isCurrentSongAffected = changedSongs.any { it?.songId == currentPlayingId }
-                Log.d(TAG, "handlePlayOperation REMOVE: currentPlayingId=$currentPlayingId, isCurrentSongAffected=$isCurrentSongAffected, isSwitchingToNext=$_isSwitchingToNext")
-
-                if (_isRoomOwner.value == true) {
-                    if (_isSwitchingToNext) {
-                        Log.d(TAG, "handlePlayOperation REMOVE: switching to next, refetch queue and load first song")
-                        _isCurrentSongRemoved = true
-                        stopPlayback()
-                        
-                        fetchWaitingListAndLoadFirst()
-                    } else if (isCurrentSongAffected) {
-                        Log.d(TAG, "handlePlayOperation REMOVE: current song removed manually")
-                        _isCurrentSongRemoved = true
-                        stopPlayback()
-                        loadMusicByLeadSinger()
-                    }
-                }
-            }
-
-            SongListChangeReason.ORDER_CHANGED -> {
-                if (_isSwitchingToNext && _isRoomOwner.value == true) {
-                    val currentPlayingId = _currentPlayingSong.value?.songId
-                    val newQueueFirst = _songQueue.value?.firstOrNull()
-                    Log.d(TAG, "handlePlayOperation ORDER_CHANGED: isSwitching=true, currentPlayingId=$currentPlayingId, newQueueFirst=${newQueueFirst?.songName}")
-                    
-                    if (newQueueFirst != null && newQueueFirst.songId != currentPlayingId) {
-                        Log.d(TAG, "handlePlayOperation ORDER_CHANGED: switching to next, refetch queue and load first song")
-                        _isCurrentSongRemoved = true
-                        stopPlayback()
-                        fetchWaitingListAndLoadFirst()
-                    }
-                }
-            }
-            
-            SongListChangeReason.UNKNOWN -> {
-                if (_isSwitchingToNext && _isRoomOwner.value == true) {
-                    val currentPlayingId = _currentPlayingSong.value?.songId
-                    val newQueue = changedSongs.filterNotNull()
-                    val isCurrentSongStillInQueue = newQueue.any { it.songId == currentPlayingId }
-                    Log.d(TAG, "handlePlayOperation UNKNOWN: isSwitching=true, currentPlayingId=$currentPlayingId, isCurrentSongStillInQueue=$isCurrentSongStillInQueue, newQueue=${newQueue.map { it.songName }}")
-                    
-                    if (!isCurrentSongStillInQueue || (newQueue.isNotEmpty() && newQueue.first().songId != currentPlayingId)) {
-                        Log.d(TAG, "handlePlayOperation UNKNOWN: switching to next, refetch queue and load first song")
-                        _isCurrentSongRemoved = true
-                        stopPlayback()
-                        fetchWaitingListAndLoadFirst()
-                    }
-                }
-            }
-        }
-    }
-
-    private val roomEngineObserver: TUIRoomObserver = object : TUIRoomObserver() {
-        override fun onRoomDismissed(
-            roomId: String?,
-            reason: RoomDismissedReason?,
-        ) {
-            Log.d(TAG, "onRoomDismissed: roomId=$roomId, reason=$reason")
-            destroy()
-        }
-
-        override fun onRoomMetadataChanged(key: String?, value: String?) {
-            Log.d(TAG, "onRoomMetadataChanged: key=$key, value=$value")
-            when (key) {
-                KEY_ENABLE_SCORE -> {
-                    _enableScore.value = value?.toBoolean() ?: true
-                }
-
-                KEY_ENABLE_REQUEST_MUSIC -> {
-                    _isDisplayFloatView.value = value?.toBoolean() ?: true
-                }
-            }
-        }
-    }
-
-    private val audioFrameListener: TRTCCloudListener.TRTCAudioFrameListener =
-        object : TRTCCloudListener.TRTCAudioFrameListener {
-
-            private var lastSentJsonData: String? = null
-            private var sendCounter = 0
-            val userIdKey = "u"
-            val pitchKey = "p"
-            val scoreKey = "s"
-            val avgScoreKey = "a"
-
-            override fun onCapturedAudioFrame(p0: TRTCCloudDef.TRTCAudioFrame?) {
-
-            }
-
-            override fun onLocalProcessedAudioFrame(frame: TRTCCloudDef.TRTCAudioFrame?) {
-                frame ?: return
-
-                if (_isRoomOwner.value != true || (_playbackState.value != PlaybackState.START && _playbackState.value != PlaybackState.RESUME)) {
-                    lastSentJsonData = null
-                    sendCounter = 0
-                    return
-                }
-
-                val dataMap = mutableMapOf<String, Any>()
-                dataMap[userIdKey] = userId
-
-                _currentPitch.value?.let { pitch -> dataMap[pitchKey] = pitch }
-                _currentScore.value?.let { score -> dataMap[scoreKey] = score }
-                _averageScore.value?.let { avgScore -> dataMap[avgScoreKey] = avgScore }
-
-                if (dataMap.size > 1) {
-                    val currentJsonString = gson.toJson(dataMap)
-                    if (currentJsonString != lastSentJsonData) {
-                        lastSentJsonData = currentJsonString
-                        sendCounter = 5
-                    }
-                }
-
-                if (sendCounter > 0 && lastSentJsonData != null) {
-                    val dataBytes = lastSentJsonData!!.toByteArray(Charsets.UTF_8)
-                    frame.extraData = dataBytes
-                    sendCounter--
-                }
-            }
-
-            override fun onRemoteUserAudioFrame(
-                frame: TRTCCloudDef.TRTCAudioFrame?,
-                userId: String?,
-            ) {
-                frame?.extraData ?: return
-                userId ?: return
-                if (frame.extraData.isEmpty()) return
-                if (ownerId == null) return
-
-                try {
-                    val jsonString = String(frame.extraData, Charsets.UTF_8)
-                    val type = object : TypeToken<Map<String, Any>>() {}.type
-                    val dataMap: Map<String, Any> = gson.fromJson(jsonString, type)
-
-                    val itemUserId = dataMap[userIdKey] as? String
-                    if (itemUserId != ownerId) {
-                        return
-                    }
-
-                    (dataMap[pitchKey] as? Double)?.toInt()?.let { pitch ->
-                        if (_hostPitch.value != pitch) {
-                            _hostPitch.postValue(pitch)
-                        }
-                    }
-
-                    (dataMap[scoreKey] as? Double)?.toInt()?.let { score ->
-                        if (_hostScore.value != score) {
-                            _hostScore.postValue(score)
-                        }
-                    }
-
-                    (dataMap[avgScoreKey] as? Double)?.toInt()?.let { avgScore ->
-                        if (_averageScore.value != avgScore) {
-                            _averageScore.postValue(avgScore)
-                        }
-                    }
-
-                } catch (e: Exception) {
-                }
-            }
-
-            override fun onMixedPlayAudioFrame(p0: TRTCCloudDef.TRTCAudioFrame?) {
-            }
-
-            override fun onMixedAllAudioFrame(p0: TRTCCloudDef.TRTCAudioFrame?) {
-            }
-
-            override fun onVoiceEarMonitorAudioFrame(p0: TRTCCloudDef.TRTCAudioFrame?) {
-            }
-        }
-
-    private val chorusMusicObserver: TXChorusMusicPlayer.ITXChorusPlayerListener =
-        object : TXChorusMusicPlayer.ITXChorusPlayerListener {
-            override fun onChorusMusicLoadSucceed(
-                musicId: String,
-                lyricList: List<TXChorusMusicPlayer.TXLyricLine>,
-                pitchList: List<TXChorusMusicPlayer.TXReferencePitch>,
-            ) {
-                Log.d(TAG, "onChorusMusicLoadSucceed: musicId=$musicId, lyricCount=${lyricList.size}, pitchCount=${pitchList.size}, loadingMusicId=$_loadingMusicId, isSwitching=$_isSwitchingToNext, isRemoved=$_isCurrentSongRemoved")
-                
-                if (_isCurrentSongRemoved) {
-                    Log.d(TAG, "onChorusMusicLoadSucceed: ignoring callback because current song was removed")
-                    return
-                }
-                
-                if (_loadingMusicId != null && _loadingMusicId != musicId) {
-                    Log.d(TAG, "onChorusMusicLoadSucceed: ignoring stale callback, expected=$_loadingMusicId, got=$musicId")
-                    return
-                }
-                
-                val queueFirst = _songQueue.value?.firstOrNull()
-                if (queueFirst != null && queueFirst.songId != musicId) {
-                    Log.d(TAG, "onChorusMusicLoadSucceed: musicId mismatch with queue first, reloading. queueFirst=${queueFirst.songId}")
-                    loadMusicByLeadSinger()
-                    return
-                }
-
-                if (musicId.startsWith(LOCAL_MUSIC_PREFIX)) {
-                    val musicPathTest = findSongLyricPath(musicId)
-                    _songLyrics.value = LyricsFileReader().parseLyricInfo(musicPathTest)
-                } else {
-                    _songLyrics.value = lyricList
-                    _pitchList.value = pitchList
-                }
-                _currentScore.postValue(-1)
-                _averageScore.postValue(0)
-                _hostPitch.postValue(0)
-                _hostScore.postValue(-1)
-                _currentPitch.postValue(0)
-                _currentPlayingSong.postValue(getSongInfoById(musicId))
-                _loadingMusicId = null
-                _isCurrentSongRemoved = false
-                _isSwitchingToNext = false
-                startPlayback()
-            }
-
-            override fun onChorusError(error: TXChorusMusicPlayer.TXChorusError, errMsg: String) {
-                Log.e(TAG, "onChorusError: error=$error, errMsg=$errMsg")
-                if (error == TXChorusMusicPlayer.TXChorusError.TXChorusErrorMusicLoadFailed) {
-                    val content = context.getString(R.string.karaoke_music_loading_error)
-                    AtomicToast.show(context,"$content (${error.ordinal})", AtomicToast.Style.ERROR)
-                    playNextSong()
-                }
-            }
-
-            override fun onNetworkQualityUpdated(userId: Int, upQuality: Int, downQuality: Int) {}
-
-            override fun onChorusRequireLoadMusic(musicId: String) {
-                Log.d(TAG, "onChorusRequireLoadMusic: musicId=$musicId")
-                loadMusic(musicId)
-            }
-
-            override fun onChorusMusicLoadProgress(musicId: String, progress: Float) {
-                Log.d(TAG, "onChorusMusicLoadProgress: musicId=$musicId, progress=$progress")
-            }
-
-            override fun onChorusStarted() {
-                Log.d(TAG, "onChorusStarted")
-                _playbackState.value = PlaybackState.START
-                isAwaitingScoreDisplay = true
-                if (isRoomOwner.value == true) {
-                    enableReverb(true)
-                }
-            }
-
-            override fun onChorusPaused() {
-                Log.d(TAG, "onChorusPaused: isOwner=${_isRoomOwner.value}, queueSize=${_songQueue.value?.size}")
-                if (_isRoomOwner.value == false && _songQueue.value.orEmpty().isEmpty()) {
-                    return
-                }
-                _playbackState.value = PlaybackState.PAUSE
-            }
-
-            override fun onChorusResumed() {
-                Log.d(TAG, "onChorusResumed")
-                _playbackState.value = PlaybackState.RESUME
-            }
-
-            override fun onChorusStopped() {
-                Log.d(TAG, "onChorusStopped: isManualStop=$_isManualStop, isSwitchingToNext=$_isSwitchingToNext, isCurrentSongRemoved=$_isCurrentSongRemoved, isRoomOwner=${_isRoomOwner.value}, queueSize=${_songQueue.value?.size}, currentPlaying=${_currentPlayingSong.value?.songName}")
-
-                if (_isManualStop) {
-                    _isManualStop = false
-                    _playbackState.value = PlaybackState.IDLE
-                    return
-                }
-                if (_isCurrentSongRemoved) {
-                    Log.d(TAG, "onChorusStopped: current song already removed, skip playNextSong")
-                    _playbackState.value = PlaybackState.STOP
-                    return
-                }
-                if (_isSwitchingToNext) {
-                    Log.d(TAG, "onChorusStopped: switching in progress, skip playNextSong")
-                    _playbackState.value = PlaybackState.STOP
-                    return
-                }
-                if (_isRoomOwner.value == true) {
-                    enableReverb(false)
-                }
-                _playbackState.value = PlaybackState.STOP
-                val shouldDelayForScore =
-                    isFullScreenUIMode && isAwaitingScoreDisplay && enableScore.value == true
-
-                if (shouldDelayForScore) {
-                    mainHandler.postDelayed({
-                        _songQueue.value?.size?.let {
-                            if (it <= 1) {
-                                _playbackState.value = PlaybackState.IDLE
-                            }
-                            playNextSong()
-                        }
-                    }, 5000)
-                } else {
-                    playNextSong()
-                }
-            }
-
-            override fun onMusicProgressUpdated(progressMs: Long, durationMs: Long) {
-                _playbackProgressMs.value = progressMs
-                _songDurationMs.value = durationMs
-                if (isRoomOwner.value == false) {
-                    if (isAwaitingScoreDisplay && progressMs / 1000 != durationMs / 1000) {
-                        isAwaitingScoreDisplay = false
-                    } else if (!isAwaitingScoreDisplay && progressMs / 1000 == durationMs / 1000) {
-                        isAwaitingScoreDisplay = true
-                    }
-                }
-            }
-
-            override fun onVoicePitchUpdated(pitch: Int, hasVoice: Boolean, progressMs: Long) {
-                _currentPitch.value = if (pitch == -1) 0 else pitch
-            }
-
-            override fun onVoiceScoreUpdated(
-                currentScore: Int,
-                averageScore: Int,
-                currentLine: Int,
-            ) {
-                Log.d(TAG, "onVoiceScoreUpdated: current=$currentScore, avg=$averageScore, line=$currentLine")
-                _currentScore.value = currentScore
-                _averageScore.value = averageScore
-            }
-
-            override fun shouldDecryptAudioData(audioData: ByteBuffer) {
-
-            }
-        }
-
-    private fun applyDefaultAudioEffects() {
-        Log.d(TAG, "applyDefaultAudioEffects")
-        enableDsp()
-        enableHIFI()
-        enableAIECModel2()
-        enableAIEC()
-        enableAI()
-    }
-
-    private fun callTRTCExperimentalApi(api: String, params: Map<String, Any>) {
-        val json = gson.toJson(mapOf("api" to api, "params" to params))
-        trtcCloud.callExperimentalAPI(json)
-    }
-
-    private fun enableDsp() {
-        val params = mapOf(
-            "configs" to listOf(
-                mapOf(
-                    "key" to "Liteav.Audio.common.dsp.version", "value" to "2", "default" to "1"
-                )
-            )
-        )
-        callTRTCExperimentalApi("setPrivateConfig", params)
-    }
-
-    private fun enableHIFI() {
-        val params = mapOf(
-            "configs" to listOf(
-                mapOf(
-                    "key" to "Liteav.Audio.common.smart.3a.strategy.flag",
-                    "value" to "16",
-                    "default" to "1"
-                )
-            )
-        )
-        callTRTCExperimentalApi("setPrivateConfig", params)
-    }
-
-    private fun enableAIECModel2() {
-        val params = mapOf(
-            "configs" to listOf(
-                mapOf(
-                    "key" to "Liteav.Audio.common.ai.ec.model.type",
-                    "value" to "2",
-                    "default" to "2"
-                )
-            )
-        )
-        callTRTCExperimentalApi("setPrivateConfig", params)
-    }
-
-    private fun enableAIEC() {
-        val params = mapOf(
-            "configs" to listOf(
-                mapOf(
-                    "key" to "Liteav.Audio.common.enable.ai.ec.module",
-                    "value" to "1",
-                    "default" to "1"
-                )
-            )
-        )
-        callTRTCExperimentalApi("setPrivateConfig", params)
-    }
-
-    private fun enableAI() {
-        val params = mapOf(
-            "configs" to listOf(
-                mapOf(
-                    "key" to "Liteav.Audio.common.ai.module.enabled",
-                    "value" to "1",
-                    "default" to "1"
-                )
-            )
-        )
-        callTRTCExperimentalApi("setPrivateConfig", params)
-    }
-
-    private fun enableReverb(enable: Boolean) {
-        Log.d(TAG, "enableReverb: enable=$enable")
-        val params = mapOf(
-            "enable" to enable,
-            "RoomSize" to 60,
-            "PreDelay" to 20,
-            "Reverberance" to 40,
-            "Damping" to 50,
-            "ToneLow" to 30,
-            "ToneHigh" to 100,
-            "WetGain" to -3,
-            "DryGain" to 0,
-            "StereoWidth" to 40,
-            "WetOnly" to false
-        )
-        callTRTCExperimentalApi("setCustomReverbParams", params)
-    }
-
-
-    private fun copyAllAssetsToStorage() {
-        val assetFiles = listOf(
-            "nuannuan_bz.mp3", "nuannuan_yc.mp3", "nuannuan_lrc.vtt"
-        )
-        assetFiles.forEach { copyAssetToFile(it) }
-    }
-
-    private fun copyAssetToFile(assetName: String) {
-        val savePath = ContextCompat.getExternalFilesDirs(context, null)[0].absolutePath
-        val destinationFile = File(savePath, assetName)
-        if (destinationFile.exists()) return
-        try {
-            destinationFile.parentFile?.mkdirs()
-            context.assets.open(assetName).use { inputStream ->
-                FileOutputStream(destinationFile).use { outputStream ->
-                    inputStream.copyTo(outputStream)
-                }
-            }
-        } catch (e: Exception) {
-            e.printStackTrace()
-        }
-    }
-
-    private fun onKaraokeError(code: Int?, desc: String?) {
-        val errorCode = code ?: -1
-        val errorMessage = desc ?: "Unknown error"
-        Log.e(TAG, "errorCode: $errorCode, errorMessage: $errorMessage")
-
-        mainHandler.post {
-            val frequencyLimit = -2
-            if (errorCode == frequencyLimit) {
-                val content = context.getString(R.string.common_client_error_freq_limit)
-                AtomicToast.show(context,"$content (${errorCode})", AtomicToast.Style.ERROR)
-            } else {
-                AtomicToast.show(context,"$errorMessage ($errorCode)", AtomicToast.Style.ERROR)
-            }
-        }
-    }
-}

+ 0 - 7
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/MusicCatalogService.kt

@@ -1,7 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.store
-
-abstract class MusicCatalogService {
-    abstract fun getSongList(callback: GetSongListCallBack)
-    abstract fun generateUserSig(userId: String, callback: ActionCallback)
-    open fun queryPlayToken(musicId: String, userId: String, callback: QueryPlayTokenCallBack) {}
-}

+ 0 - 21
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/utils/KaraokeTypes.kt

@@ -1,21 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.store.utils
-
-enum class PlaybackState {
-    IDLE, START, PAUSE, RESUME, STOP
-}
-
-data class MusicInfo(
-    var musicId: String = "",
-    var musicName: String = "",
-    var artist: String = "",
-    var lyricUrl: String = "",
-    var originalUrl: String = "",
-    var accompanyUrl: String = "",
-    var coverUrl: String = "",
-    var duration: Int = 0,
-)
-
-enum class LyricAlign {
-    RIGHT,
-    CENTER
-}

+ 0 - 89
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/store/utils/LyricsFileReader.kt

@@ -1,89 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.store.utils
-
-import android.util.Log
-import com.tencent.trtc.TXChorusMusicPlayer
-import java.io.BufferedReader
-import java.io.File
-import java.io.FileInputStream
-import java.io.InputStreamReader
-
-class LyricsFileReader {
-    companion object {
-        private const val TAG = "LyricsFileReader"
-        private val TIME_PATTERN = """(\d{2}):(\d{2}):(\d{2}).(\d{3})""".toRegex()
-        private val WORD_PATTERN = """\<(\d+),(\d+),(\d+)\>""".toRegex()
-    }
-
-    fun parseLyricInfo(path: String): List<TXChorusMusicPlayer.TXLyricLine>? {
-        val lyricFile = File(path).takeIf { it.exists() && it.length() > 0 } ?: run {
-            Log.w(TAG, "Lyric file not found or empty: $path")
-            return null
-        }
-
-        return runCatching {
-            FileInputStream(lyricFile).use { input ->
-                BufferedReader(InputStreamReader(input)).use { reader ->
-                    buildList {
-                        var line: String?
-                        while (reader.readLine().also { line = it } != null) {
-                            line?.takeIf { TIME_PATTERN.containsMatchIn(it) }?.let { timeLine ->
-                                val lyricsLineInfo = parseLyricTimeLine(timeLine)
-                                val lyricString = reader.readLine()
-                                val updatedLineInfo = parseLyricWords(lyricString, lyricsLineInfo)
-                                add(updatedLineInfo)
-                            }
-                        }
-                    }
-                }
-            }
-        }.onFailure { e ->
-            Log.e(TAG, "Failed to parse lyric file: ${e.message}", e)
-        }.getOrNull()
-    }
-
-    private fun parseLyricTimeLine(lineString: String): TXChorusMusicPlayer.TXLyricLine {
-        val (startTime, endTime) = lineString.split(" --> ").map { dateToMilliseconds(it) }
-        val lyricLine = TXChorusMusicPlayer.TXLyricLine()
-        lyricLine.startTimeMs = startTime
-        lyricLine.durationMs = endTime - startTime
-        lyricLine.characterArray = null
-        return lyricLine
-    }
-
-    private fun parseLyricWords(
-        lineString: String?,
-        lineInfo: TXChorusMusicPlayer.TXLyricLine,
-    ): TXChorusMusicPlayer.TXLyricLine {
-        lineString ?: return lineInfo
-
-        val wordMatches = WORD_PATTERN.findAll(lineString).toList()
-        val words = lineString.split(WORD_PATTERN)
-
-        val wordInfoList = wordMatches.mapIndexed { index, matchResult ->
-            TXChorusMusicPlayer.TXChorusLyricCharacter().apply {
-                startTimeMs = matchResult.groupValues[1].toLong()
-                durationMs = matchResult.groupValues[2].toLong()
-                utf8Character = words.getOrNull(index + 1) ?: ""
-            }
-        }
-        val result = TXChorusMusicPlayer.TXLyricLine()
-        result.startTimeMs = lineInfo.startTimeMs
-        result.durationMs = lineInfo.durationMs
-        result.characterArray = wordInfoList
-        return result
-    }
-
-    private fun dateToMilliseconds(inputString: String): Long {
-        return TIME_PATTERN.matchEntire(inputString)?.let { match ->
-            match.groupValues.let { groups ->
-                groups[1].toLong() * 3600000L +
-                        groups[2].toLong() * 60000 +
-                        groups[3].toLong() * 1000 +
-                        groups[4].toLong()
-            }
-        } ?: run {
-            Log.e(TAG, "Invalid time format: $inputString")
-            -1
-        }
-    }
-}

+ 0 - 443
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/KaraokeControlView.kt

@@ -1,443 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view
-
-
-import android.content.Context
-import android.os.Handler
-import android.os.Looper
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.lifecycle.Observer
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager
-import io.trtc.tuikit.atomicx.karaoke.store.KaraokeStore
-import io.trtc.tuikit.atomicx.karaoke.store.utils.LyricAlign
-import io.trtc.tuikit.atomicx.karaoke.store.utils.PlaybackState
-import com.tencent.trtc.TXChorusMusicPlayer
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-import io.trtc.tuikit.atomicx.widget.basicwidget.popover.AtomicPopover
-
-
-class KaraokeControlView @JvmOverloads constructor(
-    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
-) : ConstraintLayout(context, attrs, defStyleAttr) {
-    var isAudienceFirstEnterRoom = true
-    private lateinit var store: KaraokeStore
-    private lateinit var lyricView: LyricView
-    private lateinit var pitchView: PitchView
-    private lateinit var textScore: TextView
-    private lateinit var textSeg: TextView
-    private lateinit var imageNext: ImageView
-    private lateinit var imagePause: ImageView
-    private lateinit var imageSetting: ImageView
-    private lateinit var textMusicName: TextView
-    private lateinit var layoutRoot: FrameLayout
-    private lateinit var layoutTime: LinearLayout
-    private lateinit var layoutScore: FrameLayout
-    private lateinit var textMusicAuthor: TextView
-    private lateinit var textPlayProgress: TextView
-    private lateinit var textPlayDuration: TextView
-    private lateinit var textRequesterName: TextView
-    private lateinit var layoutFunction: LinearLayout
-    private lateinit var imageEnableOriginal: ImageView
-    private lateinit var layoutRequestMusic: LinearLayout
-    private lateinit var textAudienceWaitingTips: TextView
-    private lateinit var textAudiencePauseTips: TextView
-    private lateinit var imageRequesterAvatar: AtomicAvatar
-    private lateinit var songRequestPanel: SongRequestPanel
-    private val mainHandler = Handler(Looper.getMainLooper())
-    private val isOwnerObserver = Observer<Boolean> { updateOwnerSpecificViews() }
-    private val currentTrackObserver = Observer(this::onCurrentTrackChanged)
-    private val currentMusicObserver = Observer(this::onCurrentMusicChanged)
-    private val playQueueObserver = Observer(this::onPlayQueueChanged)
-    private val durationObserver = Observer(this::onDurationChanged)
-    private val playbackStateObserver = Observer(this::onPlaybackStateChanged)
-    private val progressObserver = Observer(this::onProgressChanged)
-    private val pitchListObserver = Observer(this::onPitchListChanged)
-    private val currentPitchObserver = Observer(this::onCurrentPitchChanged)
-    private val currentScoreObserver = Observer(this::onCurrentScoreChanged)
-    private val enableScoreObserver = Observer(this::onEnableScoreChanged)
-    private val remoteScoresObserver = Observer(this::onRemoteScoresChanged)
-    private val remotePitchesObserver = Observer(this::onRemotePitchesChanged)
-
-    init {
-        LayoutInflater.from(context).inflate(R.layout.karaoke_control_view, this, true)
-        bindViewId()
-    }
-
-    fun init(roomId: String, isOwner: Boolean) {
-        store = KaraokeStore.getInstance(context)
-        store.init(roomId, isOwner)
-        songRequestPanel = SongRequestPanel(context, store, false)
-        initViews()
-        addObservers()
-    }
-
-    fun release() {
-        removeObservers()
-        KaraokeStore.destroyInstance()
-        mainHandler.removeCallbacksAndMessages(null)
-    }
-
-    fun showSongRequestPanel() {
-        songRequestPanel.show()
-    }
-
-    private fun bindViewId() {
-        layoutRoot = findViewById(R.id.fl_root)
-        layoutTime = findViewById(R.id.ll_time_bar)
-        imagePause = findViewById(R.id.iv_pause)
-        textScore = findViewById(R.id.tv_score)
-        layoutScore = findViewById(R.id.fl_score)
-        textSeg = findViewById(R.id.tv_seg)
-        imageNext = findViewById(R.id.iv_next)
-        textMusicName = findViewById(R.id.tv_music_name)
-        imageSetting = findViewById(R.id.iv_setting)
-        textPlayProgress = findViewById(R.id.progress)
-        textMusicAuthor = findViewById(R.id.tv_music_artist)
-        layoutFunction = findViewById(R.id.ll_right_icons)
-        imageEnableOriginal = findViewById(R.id.iv_original)
-        textPlayDuration = findViewById(R.id.duration)
-        textRequesterName = findViewById(R.id.tv_requester_name)
-        imageRequesterAvatar = findViewById(R.id.iv_user_avatar)
-        textAudienceWaitingTips = findViewById(R.id.tv_waiting_tips)
-        textAudiencePauseTips = findViewById(R.id.tv_pause_tips)
-        layoutRequestMusic = findViewById(R.id.ll_order_music)
-    }
-
-    private fun initViews() {
-        initPitchView()
-        initLyricView()
-        initClickListeners()
-    }
-
-    private fun initClickListeners() {
-        imagePause.setOnClickListener {
-            if (store.playbackState.value == PlaybackState.START || store.playbackState.value == PlaybackState.RESUME) {
-                store.pausePlayback()
-            } else {
-                store.resumePlayback()
-            }
-        }
-
-        imageSetting.setOnClickListener {
-            val atomicPopover = AtomicPopover(context)
-            val karaokeSettingPanel = KaraokeSettingPanel(context)
-            karaokeSettingPanel.init(store)
-            karaokeSettingPanel.setOnBackButtonClickListener(object :
-                KaraokeSettingPanel.OnBackButtonClickListener {
-                override fun onClick() {
-                    atomicPopover.dismiss()
-                }
-            })
-            atomicPopover.setContent(karaokeSettingPanel)
-            atomicPopover.show()
-        }
-        layoutRequestMusic.setOnClickListener { view ->
-            view.post {
-                if (songRequestPanel.isShowing) {
-                    return@post
-                }
-                songRequestPanel.show()
-            }
-        }
-        imageNext.setOnClickListener {
-            store.playNextSong()
-            store.setIsDisplayScoreView(false)
-        }
-        imageEnableOriginal.setOnClickListener {
-            if (store.currentTrack.value == TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong) {
-                store.switchMusicTrack(TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusAccompaniment)
-            } else {
-                store.switchMusicTrack(TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong)
-            }
-        }
-    }
-
-    private fun addObservers() {
-        store.playbackProgressMs.observeForever(progressObserver)
-        store.playbackState.observeForever(playbackStateObserver)
-        store.songDurationMs.observeForever(durationObserver)
-        store.isRoomOwner.observeForever(isOwnerObserver)
-        store.currentTrack.observeForever(currentTrackObserver)
-        store.currentPlayingSong.observeForever(currentMusicObserver)
-        store.songQueue.observeForever(playQueueObserver)
-        store.pitchList.observeForever(pitchListObserver)
-        store.currentPitch.observeForever(currentPitchObserver)
-        store.currentScore.observeForever(currentScoreObserver)
-        store.enableScore.observeForever(enableScoreObserver)
-        store.hostScore.observeForever(remoteScoresObserver)
-        store.hostPitch.observeForever(remotePitchesObserver)
-    }
-
-    private fun removeObservers() {
-        store.playbackProgressMs.removeObserver(progressObserver)
-        store.playbackState.removeObserver(playbackStateObserver)
-        store.songDurationMs.removeObserver(durationObserver)
-        store.isRoomOwner.removeObserver(isOwnerObserver)
-        store.currentTrack.removeObserver(currentTrackObserver)
-        store.currentPlayingSong.removeObserver(currentMusicObserver)
-        store.currentTrack.removeObserver(currentTrackObserver)
-        store.pitchList.removeObserver(pitchListObserver)
-        store.currentPitch.removeObserver(currentPitchObserver)
-        store.currentScore.removeObserver(currentScoreObserver)
-        store.enableScore.removeObserver(enableScoreObserver)
-        store.hostScore.removeObserver(remoteScoresObserver)
-        store.hostPitch.removeObserver(remotePitchesObserver)
-    }
-
-    private fun onProgressChanged(progress: Long) {
-        lyricView.setPlayProgress(progress)
-        pitchView.setPlayProgress(progress)
-        textPlayProgress.text = formatTime(progress)
-    }
-
-    private fun onDurationChanged(durationMs: Long) {
-        textPlayDuration.text = formatTime(durationMs)
-    }
-
-    override fun onVisibilityChanged(changedView: View, visibility: Int) {
-        super.onVisibilityChanged(changedView, visibility)
-        if (!this::store.isInitialized) {
-            return
-        }
-        if (visibility == VISIBLE) {
-            store.setFullScreenUIMode(true)
-        }
-    }
-
-    private fun onCurrentTrackChanged(currentTrack: TXChorusMusicPlayer.TXChorusMusicTrack) {
-        val resource =
-            if (currentTrack == TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong) R.drawable.karaoke_original_on
-            else R.drawable.karaoke_original_off
-        imageEnableOriginal.setImageResource(resource)
-    }
-
-    private fun onCurrentMusicChanged(currentSong: TUISongListManager.SongInfo) {
-        if (currentSong.songId.isEmpty() || store.songQueue.value?.isEmpty() == true) {
-            textMusicName.text = context.getString(R.string.karaoke_no_song)
-            textMusicAuthor.text = null
-            return
-        }
-        textMusicName.text = currentSong.songName
-        textMusicAuthor.text = "- " + currentSong?.artistName
-    }
-
-    private fun onPlaybackStateChanged(playbackState: PlaybackState) {
-        when (playbackState) {
-            PlaybackState.IDLE -> handleIdleState()
-            PlaybackState.START -> handleStartState()
-            PlaybackState.RESUME -> handleResumeState()
-            PlaybackState.PAUSE -> handlePausedState()
-            PlaybackState.STOP -> handleStoppedState()
-        }
-    }
-
-    private fun onPitchListChanged(pitchList: List<TXChorusMusicPlayer.TXReferencePitch>) {
-        pitchView.setPitchList(pitchList)
-    }
-
-    private fun onCurrentPitchChanged(pitch: Int) {
-        pitchView.setUserPitch(pitch)
-    }
-
-    private fun onCurrentScoreChanged(score: Int) {
-        pitchView.setScore(score)
-    }
-
-    private fun onEnableScoreChanged(enableScore: Boolean) {
-        pitchView.setScoringEnabled(enableScore)
-    }
-
-    private fun onRemoteScoresChanged(score: Int) {
-        if (store.isRoomOwner.value == false) {
-            pitchView.setScore(score)
-        }
-    }
-
-    private fun onRemotePitchesChanged(pitch: Int) {
-        if (store.isRoomOwner.value == false) {
-            pitchView.setUserPitch(pitch)
-        }
-    }
-
-    private fun handleIdleState() {
-        layoutFunction.visibility = GONE
-        layoutScore.visibility = GONE
-        lyricView.visibility = GONE
-        pitchView.visibility = GONE
-        textMusicName.text = context.getString(R.string.karaoke_no_song)
-        textMusicAuthor.text = null
-        onProgressChanged(0)
-        onDurationChanged(0)
-        if (store.isRoomOwner.value == true) {
-            layoutRequestMusic.visibility = VISIBLE
-        } else {
-            updateAudienceWaitingUI()
-        }
-    }
-
-    private fun updateUIForPlayingState() {
-        if (store.isRoomOwner.value == false) {
-            isAudienceFirstEnterRoom = false
-        }
-        layoutScore.visibility = GONE
-        lyricView.visibility = VISIBLE
-        pitchView.visibility = VISIBLE
-        layoutTime.visibility = VISIBLE
-        layoutRequestMusic.visibility = GONE
-        textAudienceWaitingTips.visibility = GONE
-        textAudiencePauseTips.visibility = GONE
-        if (store.isRoomOwner.value == true) {
-            layoutFunction.visibility = VISIBLE
-        }
-        setSongProgressViewsVisible(true)
-        imagePause.setImageResource(R.drawable.karaoke_music_resume)
-        imageNext.setImageResource(R.drawable.karaoke_music_next)
-        imageSetting.setImageResource(R.drawable.karaoke_setting)
-    }
-
-    private fun handleStartState() {
-        updateUIForPlayingState()
-    }
-
-    private fun handleResumeState() {
-        updateUIForPlayingState()
-    }
-
-    private fun handlePausedState() {
-        if (store.isRoomOwner.value == true) {
-            layoutFunction.visibility = VISIBLE
-        } else {
-            updateAudienceWaitingUI()
-        }
-        imagePause.setImageResource(R.drawable.karaoke_music_pause)
-    }
-
-    private fun setSongProgressViewsVisible(isVisible: Boolean) {
-        val visibility = if (isVisible) VISIBLE else GONE
-        textPlayDuration.visibility = visibility
-        textPlayProgress.visibility = visibility
-        textSeg.visibility = visibility
-    }
-
-    private fun handleStoppedState() {
-        layoutFunction.visibility = GONE
-        lyricView.visibility = GONE
-        pitchView.visibility = GONE
-
-        if (store.enableScore.value == true && store.isAwaitingScoreDisplay) {
-            store.songDurationMs.value?.let { duration ->
-                textPlayProgress.text = formatTime(duration)
-            }
-            layoutScore.visibility = VISIBLE
-            textScore.text = store.averageScore.value.toString()
-            imageRequesterAvatar.setContent(
-                AtomicAvatar.AvatarContent.URL(
-                    store.currentPlayingSong.value?.requester?.avatarUrl ?: "",
-                    R.drawable.karaoke_song_cover
-                )
-            )
-            textRequesterName.text = store.currentPlayingSong.value?.requester?.userName
-        } else {
-            store.updatePlaybackStatus(PlaybackState.IDLE)
-        }
-    }
-
-    private fun updateOwnerSpecificViews() {
-        val isOwner = store.isRoomOwner.value == true
-        if (isOwner) {
-            layoutRequestMusic.visibility = VISIBLE
-        } else {
-            updateAudienceWaitingUI()
-        }
-    }
-
-    private fun updateAudienceWaitingUI() {
-        if (store.isRoomOwner.value == true) {
-            textAudienceWaitingTips.visibility = GONE
-            textAudiencePauseTips.visibility = GONE
-            return
-        }
-        val isQueueEmpty = store.songQueue.value.orEmpty().isEmpty()
-        val currentState = store.playbackState.value
-        if (isQueueEmpty) {
-            textAudienceWaitingTips.visibility = VISIBLE
-            textAudiencePauseTips.visibility = GONE
-        } else {
-            if (currentState == PlaybackState.PAUSE && isAudienceFirstEnterRoom) {
-                textAudienceWaitingTips.visibility = GONE
-                textAudiencePauseTips.visibility = VISIBLE
-                layoutTime.visibility = GONE
-                setSongProgressViewsVisible(false)
-                lyricView.visibility = GONE
-                pitchView.visibility = GONE
-            } else {
-                textAudienceWaitingTips.visibility = GONE
-                textAudiencePauseTips.visibility = GONE
-            }
-        }
-    }
-
-    private fun onPlayQueueChanged(list: List<TUISongListManager.SongInfo>) {
-        if (store.isRoomOwner.value == true) {
-            return
-        }
-        updateAudienceWaitingUI()
-        if (list.isEmpty()) {
-            store.updatePlaybackStatus(PlaybackState.IDLE)
-        }
-    }
-
-    private fun formatTime(millis: Long): String {
-        val totalSeconds = millis / 1000
-        val minutes = totalSeconds / 60
-        val seconds = totalSeconds % 60
-        return String.format("%d:%02d", minutes, seconds)
-    }
-
-    fun initPitchView() {
-        if (layoutRoot is ViewGroup) {
-            (layoutRoot as ViewGroup).clipChildren = false
-            (layoutRoot as ViewGroup).clipToPadding = false
-        }
-
-        pitchView = PitchView(context)
-        val width = FrameLayout.LayoutParams.MATCH_PARENT
-        val height =
-            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 56f, resources.displayMetrics)
-                .toInt()
-        val lp = FrameLayout.LayoutParams(width, height)
-        lp.topMargin =
-            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 56f, resources.displayMetrics)
-                .toInt()
-        pitchView.layoutParams = lp
-        pitchView.setBackgroundResource(R.drawable.karaoke_pitch_bg)
-        layoutRoot.addView(pitchView)
-        pitchView.visibility = GONE
-    }
-
-    fun initLyricView() {
-        lyricView = LyricView(context, store)
-        val width = FrameLayout.LayoutParams.MATCH_PARENT
-        val height =
-            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics)
-                .toInt()
-        val lp = FrameLayout.LayoutParams(width, height)
-        lp.topMargin =
-            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 116f, resources.displayMetrics)
-                .toInt()
-        lyricView.layoutParams = lp
-        layoutRoot.addView(lyricView)
-        lyricView.setLyricAlign(LyricAlign.CENTER)
-        lyricView.setLyricTextSize(18f, 12f)
-        lyricView.visibility = GONE
-    }
-}

+ 0 - 405
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/KaraokeFloatingView.kt

@@ -1,405 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view
-
-
-import android.content.Context
-import android.graphics.Color
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.LayoutInflater
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewConfiguration
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.LinearLayout
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.lifecycle.Observer
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager
-import com.tencent.trtc.TXChorusMusicPlayer
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.karaoke.store.KaraokeStore
-import io.trtc.tuikit.atomicx.karaoke.store.utils.PlaybackState
-import io.trtc.tuikit.atomicx.widget.basicwidget.popover.AtomicPopover
-import io.trtc.tuikit.atomicxcore.api.live.CoHostStore
-import io.trtc.tuikit.atomicxcore.api.live.CoHostStore.Companion.create
-import io.trtc.tuikit.atomicxcore.api.live.SeatUserInfo
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlin.math.abs
-
-class KaraokeFloatingView @JvmOverloads constructor(
-    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
-) : ConstraintLayout(context, attrs, defStyleAttr) {
-    enum class FloatingMode { RIGHT_HALF_MOVE, CENTER_FIXED }
-
-    private lateinit var store: KaraokeStore
-    private lateinit var liveID: String
-    private var coHostStore: CoHostStore? = null
-    private var subscribeStateJob: Job? = null
-    private lateinit var imagePause: ImageView
-    private lateinit var imageNext: ImageView
-    private lateinit var imageRequestMusic: ImageView
-    private lateinit var imageSetting: ImageView
-    private lateinit var imageEnableOriginal: ImageView
-    private lateinit var lyricView: LyricView
-    private lateinit var pitchView: PitchView
-    private lateinit var frameFunction: FrameLayout
-    private lateinit var layoutRoot: LinearLayout
-    private lateinit var songRequestPanel: SongRequestPanel
-    private var parentView: ViewGroup? = null
-    private var mode: FloatingMode = FloatingMode.RIGHT_HALF_MOVE
-    private var isDragging = false
-    private var lastY = 0f
-    private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
-    private var moveRangeTop = 0f
-    private var moveRangeBottom = 0f
-    private val rightMarginPx: Float = 10 * context.resources.displayMetrics.density
-    private val playQueueObserver = Observer(this::onPlayQueueChanged)
-    private val progressObserver = Observer(this::onProgressChanged)
-    private val isDisplayFloatViewObserver = Observer(this::onDisplayFloatViewChanged)
-    private val playbackStateObserver = Observer(this::onPlaybackStateChanged)
-    private val pitchListObserver = Observer(this::onPitchListChanged)
-    private val currentPitchObserver = Observer(this::onCurrentPitchChanged)
-    private val currentScoreObserver = Observer(this::onCurrentScoreChanged)
-    private val enableScoreObserver = Observer(this::onEnableScoreChanged)
-    private val remoteScoresObserver = Observer(this::onRemoteScoresChanged)
-    private val remotePitchesObserver = Observer(this::onRemotePitchesChanged)
-    private val currentTrackObserver = Observer(this::onCurrentTrackChanged)
-
-    init {
-        LayoutInflater.from(context).inflate(R.layout.karaoke_floating_view, this, true)
-        setBackgroundColor(Color.TRANSPARENT)
-        isClickable = true
-        bindViewId()
-    }
-
-    fun init(roomId: String, isOwner: Boolean) {
-        store = KaraokeStore.getInstance(context)
-        liveID = roomId
-        coHostStore = create(roomId)
-        store.init(roomId, isOwner)
-        songRequestPanel = SongRequestPanel(context, store, isOwner)
-        setupDynamicViews()
-        initClickListeners()
-        addObservers()
-    }
-
-    fun release() {
-        removeObservers()
-        KaraokeStore.destroyInstance()
-    }
-
-    fun attachAsFloating(parent: ViewGroup, mode: FloatingMode) {
-        this@KaraokeFloatingView.mode = mode
-        parentView = parent
-        isClickable = true
-        this.visibility = INVISIBLE
-        (this.parent as? ViewGroup)?.removeView(this)
-        parent.addView(
-            this, FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.WRAP_CONTENT,
-                FrameLayout.LayoutParams.WRAP_CONTENT
-            )
-        )
-        post {
-            updateFloatingLayout()
-            this.visibility = VISIBLE
-        }
-    }
-
-    fun detachFromFloating() {
-        (this.parent as? ViewGroup)?.removeView(this)
-    }
-
-    fun showSongRequestPanel() {
-        songRequestPanel.show()
-    }
-
-    private fun bindViewId() {
-        layoutRoot = findViewById(R.id.ll_root)
-        imagePause = findViewById(R.id.iv_pause)
-        imageNext = findViewById(R.id.iv_next)
-        imageRequestMusic = findViewById(R.id.iv_order_music)
-        imageSetting = findViewById(R.id.iv_setting)
-        imageEnableOriginal = findViewById(R.id.iv_original)
-        frameFunction = findViewById(R.id.fl_function)
-    }
-
-    private fun setupDynamicViews() {
-        if (layoutRoot is ViewGroup) {
-            (layoutRoot as ViewGroup).clipChildren = false
-            (layoutRoot as ViewGroup).clipToPadding = false
-        }
-        pitchView = PitchView(context)
-        val width = TypedValue.applyDimension(
-            TypedValue.COMPLEX_UNIT_DIP, 177f, resources.displayMetrics
-        ).toInt()
-        val height = TypedValue.applyDimension(
-            TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics
-        ).toInt()
-        val layoutParams = LinearLayout.LayoutParams(width, height)
-        layoutParams.topMargin = TypedValue.applyDimension(
-            TypedValue.COMPLEX_UNIT_DIP, 20f,
-            resources.displayMetrics
-        ).toInt()
-        pitchView.layoutParams = layoutParams
-        layoutRoot.addView(pitchView, 0)
-        lyricView = LyricView(context, store)
-        val params = LinearLayout.LayoutParams(width, height)
-        lyricView.layoutParams = params
-        val index: Int = layoutRoot.indexOfChild(pitchView)
-        layoutRoot.addView(lyricView, index + 1)
-    }
-
-    private fun initClickListeners() {
-        imagePause.setOnClickListener {
-            if (store.playbackState.value == PlaybackState.START || store.playbackState.value == PlaybackState.RESUME) {
-                store.pausePlayback()
-            } else {
-                store.resumePlayback()
-            }
-        }
-        imageNext.setOnClickListener {
-            store.playNextSong()
-            store.setIsDisplayScoreView(false)
-        }
-        imageRequestMusic.setOnClickListener { view ->
-            view.post {
-                if (songRequestPanel.isShowing) {
-                    return@post
-                }
-                songRequestPanel.show()
-            }
-        }
-        frameFunction.setOnClickListener { view ->
-            view.post {
-                if (songRequestPanel.isShowing) {
-                    return@post
-                }
-                songRequestPanel.show()
-            }
-        }
-        imageSetting.setOnClickListener {
-            val atomicPopover = AtomicPopover(context)
-            val karaokeSettingPanel = KaraokeSettingPanel(context)
-            karaokeSettingPanel.init(store)
-            karaokeSettingPanel.setOnBackButtonClickListener(object :
-                KaraokeSettingPanel.OnBackButtonClickListener {
-                override fun onClick() {
-                    atomicPopover.dismiss()
-                }
-            })
-            atomicPopover.setContent(karaokeSettingPanel)
-            atomicPopover.show()
-        }
-        imageEnableOriginal.setOnClickListener {
-            if (store.currentTrack.value == TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong) {
-                store.switchMusicTrack(TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusAccompaniment)
-            } else {
-                store.switchMusicTrack(TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong)
-            }
-        }
-    }
-
-    private fun addObservers() {
-        store.playbackProgressMs.observeForever(progressObserver)
-        store.songQueue.observeForever(playQueueObserver)
-        store.isDisplayFloatView.observeForever(isDisplayFloatViewObserver)
-        store.playbackState.observeForever(playbackStateObserver)
-        store.pitchList.observeForever(pitchListObserver)
-        store.currentPitch.observeForever(currentPitchObserver)
-        store.currentScore.observeForever(currentScoreObserver)
-        store.enableScore.observeForever(enableScoreObserver)
-        store.hostScore.observeForever(remoteScoresObserver)
-        store.hostPitch.observeForever(remotePitchesObserver)
-        store.currentTrack.observeForever(currentTrackObserver)
-        addConnectionObserver()
-    }
-
-    private fun removeObservers() {
-        store.playbackProgressMs.removeObserver(progressObserver)
-        store.songQueue.removeObserver(playQueueObserver)
-        store.isDisplayFloatView.removeObserver(isDisplayFloatViewObserver)
-        store.playbackState.removeObserver(playbackStateObserver)
-        store.pitchList.removeObserver(pitchListObserver)
-        store.currentPitch.removeObserver(currentPitchObserver)
-        store.currentScore.removeObserver(currentScoreObserver)
-        store.enableScore.removeObserver(enableScoreObserver)
-        store.hostScore.removeObserver(remoteScoresObserver)
-        store.hostPitch.removeObserver(remotePitchesObserver)
-        store.currentTrack.removeObserver(currentTrackObserver)
-        subscribeStateJob?.cancel()
-    }
-
-    fun addConnectionObserver() {
-        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
-            coHostStore?.coHostState?.connected?.collect {
-                onConnectedListChanged(it)
-            }
-        }
-    }
-
-    fun onConnectedListChanged(connectedRoomList: List<SeatUserInfo>) {
-        val isConnected = connectedRoomList.any { it.liveID == liveID }
-        if (isConnected) {
-            store.enableRequestMusic(false)
-        } else {
-            store.enableRequestMusic(true)
-        }
-    }
-
-    private fun onProgressChanged(progress: Long) {
-        lyricView.setPlayProgress(progress)
-        pitchView.setPlayProgress(progress)
-    }
-
-    private fun onDisplayFloatViewChanged(isDisplay: Boolean) {
-        layoutRoot.visibility = if (isDisplay) VISIBLE else GONE
-        if (isDisplay && isAttachedToWindow) {
-            post { updateFloatingLayout() }
-        }
-    }
-
-    private fun onPlaybackStateChanged(playbackState: PlaybackState) {
-        if (playbackState == PlaybackState.START) {
-            imagePause.setImageResource(R.drawable.karaoke_music_resume)
-        } else if (playbackState == PlaybackState.RESUME) {
-            imagePause.setImageResource(R.drawable.karaoke_music_resume)
-        } else if (playbackState == PlaybackState.PAUSE) {
-            imagePause.setImageResource(R.drawable.karaoke_music_pause)
-        } else {
-            imagePause.setImageResource(R.drawable.karaoke_music_pause)
-        }
-    }
-
-    private fun onPitchListChanged(pitchList: List<TXChorusMusicPlayer.TXReferencePitch>) {
-        pitchView.setPitchList(pitchList)
-    }
-
-    private fun onCurrentPitchChanged(pitch: Int) {
-        pitchView.setUserPitch(pitch)
-    }
-
-    private fun onCurrentScoreChanged(score: Int) {
-        pitchView.setScore(score)
-    }
-
-    private fun onEnableScoreChanged(enableScore: Boolean) {
-        pitchView.setScoringEnabled(enableScore)
-    }
-
-    private fun onRemoteScoresChanged(score: Int) {
-        if (store.isRoomOwner.value == false) {
-            pitchView.setScore(score)
-        }
-    }
-
-    private fun onRemotePitchesChanged(pitch: Int) {
-        if (store.isRoomOwner.value == false) {
-            pitchView.setUserPitch(pitch)
-        }
-    }
-
-    private fun onCurrentTrackChanged(currentTrack: TXChorusMusicPlayer.TXChorusMusicTrack) {
-        val resource =
-            if (currentTrack == TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong) R.drawable.karaoke_original_on
-            else R.drawable.karaoke_original_off
-        imageEnableOriginal.setImageResource(resource)
-    }
-
-    private fun onPlayQueueChanged(list: List<TUISongListManager.SongInfo>) {
-        val isOwner = store.isRoomOwner.value == true
-        val isQueueEmpty = list.isEmpty()
-
-        val showFunctionBar = isOwner && !isQueueEmpty
-        frameFunction.visibility = if (showFunctionBar) VISIBLE else GONE
-        imageRequestMusic.visibility = if (showFunctionBar) GONE else VISIBLE
-
-        val showLyricAndPitch = !isQueueEmpty
-        lyricView.visibility = if (showLyricAndPitch) VISIBLE else GONE
-        pitchView.visibility = if (showLyricAndPitch) VISIBLE else GONE
-    }
-
-    private fun updateFloatingLayout() {
-        val parent = parentView ?: return
-        val parentW = parent.width
-        val parentH = parent.height
-        val myW = width
-        val myH = height
-
-        if (mode == FloatingMode.RIGHT_HALF_MOVE) {
-            moveRangeTop = parentH / 4f
-            moveRangeBottom = parentH * 3f / 4f - myH
-            this.y = parentH / 2f - myH / 2f
-            this.x = parentW - rightMarginPx - myW
-        } else if (mode == FloatingMode.CENTER_FIXED) {
-            val d110 = context.resources.displayMetrics.density * 110
-            this.y = d110
-            this.x = (parentW - myW) / 2f
-        }
-    }
-
-    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
-        if (ev == null || mode != FloatingMode.RIGHT_HALF_MOVE) return false
-        when (ev.action) {
-            MotionEvent.ACTION_DOWN -> {
-                lastY = ev.rawY
-                isDragging = false
-            }
-
-            MotionEvent.ACTION_MOVE -> {
-                if (abs(ev.rawY - lastY) > touchSlop) {
-                    isDragging = true
-                    return true
-                }
-            }
-        }
-        return false
-    }
-
-    override fun onVisibilityChanged(changedView: View, visibility: Int) {
-        super.onVisibilityChanged(changedView, visibility)
-        if (!this::store.isInitialized) {
-            return
-        }
-        if (visibility == VISIBLE) {
-            store.setFullScreenUIMode(false)
-        }
-    }
-
-    override fun onTouchEvent(event: MotionEvent?): Boolean {
-        if (event == null) return false
-        when (event.action) {
-            MotionEvent.ACTION_DOWN -> {
-                lastY = event.rawY
-                isDragging = false
-                performClick()
-                return true
-            }
-
-            MotionEvent.ACTION_MOVE -> {
-                if (mode != FloatingMode.RIGHT_HALF_MOVE) return false
-                val dy = event.rawY - lastY
-                this.y = (y + dy).coerceIn(moveRangeTop, moveRangeBottom)
-                val parentW = parentView?.width ?: 0
-                this.x = parentW - rightMarginPx - width
-                lastY = event.rawY
-                return true
-            }
-
-            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
-                isDragging = false
-                return true
-            }
-        }
-        return super.onTouchEvent(event)
-    }
-
-    override fun performClick(): Boolean {
-        super.performClick()
-        return true
-    }
-}

+ 0 - 145
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/KaraokeSettingPanel.kt

@@ -1,145 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.widget.FrameLayout
-import android.widget.SeekBar
-import android.widget.TextView
-import androidx.appcompat.widget.SwitchCompat
-import com.tencent.trtc.TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusAccompaniment
-import com.tencent.trtc.TXChorusMusicPlayer.TXChorusMusicTrack.TXChorusOriginalSong
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.karaoke.store.KaraokeStore
-
-class KaraokeSettingPanel @JvmOverloads constructor(
-    private val context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-) : FrameLayout(context, attrs, defStyleAttr) {
-
-    private lateinit var textPlayoutVolume: TextView
-    private lateinit var textCaptureVolume: TextView
-    private lateinit var textMusicPitch: TextView
-    private lateinit var store: KaraokeStore
-    private var onBackButtonClickListener: OnBackButtonClickListener? = null
-
-    init {
-        LayoutInflater.from(this@KaraokeSettingPanel.context)
-            .inflate(R.layout.karaoke_music_setting_panel, this, true)
-    }
-
-    fun init(store: KaraokeStore) {
-        this@KaraokeSettingPanel.store = store
-        initView()
-    }
-
-    private fun initView() {
-        bindViewId()
-        initFinishView()
-        initPlayoutVolumeView()
-        initCaptureVolumeView()
-        initMusicPitchView()
-        initEnableOriginView()
-        initEnableScoreView()
-    }
-
-    private fun bindViewId() {
-        textCaptureVolume = findViewById(R.id.tv_capture_volume)
-        textPlayoutVolume = findViewById(R.id.tv_playout_volume)
-        textMusicPitch = findViewById(R.id.tv_music_pitch)
-    }
-
-    private fun initEnableOriginView() {
-        val switchOrigin = findViewById<SwitchCompat>(R.id.sc_enable_origin)
-        switchOrigin.isChecked = store.currentTrack.value == TXChorusOriginalSong
-        switchOrigin.setOnCheckedChangeListener { _, isChecked ->
-            if (isChecked) {
-                store.switchMusicTrack(TXChorusOriginalSong)
-            } else {
-                store.switchMusicTrack(TXChorusAccompaniment)
-            }
-            switchOrigin.isChecked = store.currentTrack.value == TXChorusOriginalSong
-        }
-    }
-
-    private fun initEnableScoreView() {
-        val switchScore = findViewById<SwitchCompat>(R.id.sc_enable_score)
-        switchScore.isChecked = store.enableScore.value == true
-        switchScore.setOnCheckedChangeListener { _, enable ->
-            store.setScoringEnabled(enable)
-        }
-    }
-
-    private fun initCaptureVolumeView() {
-        textCaptureVolume.text = store.publishVolume.value.toString()
-        val seekMusicVolume = findViewById<SeekBar>(R.id.sb_capture_volume)
-        seekMusicVolume.progress = store.publishVolume.value ?: 0
-        seekMusicVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
-            override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
-                textCaptureVolume.text = i.toString()
-            }
-
-            override fun onStartTrackingTouch(seekBar: SeekBar) {}
-
-            override fun onStopTrackingTouch(seekBar: SeekBar) {
-                store.setPublishVolume(seekBar.progress)
-            }
-        })
-    }
-
-    private fun initPlayoutVolumeView() {
-        textPlayoutVolume.text = store.playoutVolume.value.toString()
-        val seekPlayoutVolume = findViewById<SeekBar>(R.id.sb_playout_volume)
-        seekPlayoutVolume.progress = store.playoutVolume.value ?: 0
-        seekPlayoutVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
-            override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
-                textPlayoutVolume.text = i.toString()
-            }
-
-            override fun onStartTrackingTouch(seekBar: SeekBar) {}
-
-            override fun onStopTrackingTouch(seekBar: SeekBar) {
-                store.setPlayoutVolume(seekBar.progress)
-            }
-        })
-    }
-
-    private fun initMusicPitchView() {
-        val seekBar = findViewById<SeekBar>(R.id.sb_music_pitch)
-        val initialPitch = store.songPitch.value ?: 0f
-        var initProgress = ((initialPitch + 1.0f) * 10).toInt()
-        initProgress = initProgress.coerceIn(0, 20)
-        seekBar.progress = initProgress
-
-        textMusicPitch.text = String.format("%.1f", initialPitch)
-
-        seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
-            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
-                val pitch = (progress - 10) * 0.1f
-                textMusicPitch.text = String.format("%.1f", pitch)
-            }
-
-            override fun onStartTrackingTouch(seekBar: SeekBar) {}
-
-            override fun onStopTrackingTouch(seekBar: SeekBar) {
-                val pitch = (seekBar.progress - 10) * 0.1f
-                store.setMusicPitch(pitch)
-            }
-        })
-    }
-
-    private fun initFinishView() {
-        findViewById<TextView>(R.id.tv_finish).setOnClickListener {
-            onBackButtonClickListener?.onClick()
-        }
-    }
-
-    fun setOnBackButtonClickListener(listener: OnBackButtonClickListener?) {
-        onBackButtonClickListener = listener
-    }
-
-    interface OnBackButtonClickListener {
-        fun onClick()
-    }
-}

+ 0 - 220
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/LyricView.kt

@@ -1,220 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.graphics.Path
-import android.graphics.Typeface
-import android.text.TextPaint
-import android.util.TypedValue
-import android.view.View
-import androidx.core.content.ContextCompat
-import com.tencent.trtc.TXChorusMusicPlayer
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.karaoke.store.KaraokeStore
-import io.trtc.tuikit.atomicx.karaoke.store.utils.LyricAlign
-
-val TXChorusMusicPlayer.TXLyricLine.fullContent: String
-    get() = characterArray.joinToString("") { it.utf8Character }
-
-class LyricView(
-    context: Context,
-    private val store: KaraokeStore,
-) : View(context) {
-    private var currentProgressMs: Long = 0L
-    private var currentLineIndex: Int = 0
-    private var highlightTextSizeSp: Float = 14f
-    private var nextLineTextSizeSp: Float = 10f
-    private var lineSpace: Float = spToPx(nextLineTextSizeSp) * 1.8f
-    private val colorBlue = ContextCompat.getColor(context, R.color.karaoke_lyric_blue)
-    private val colorWhite = ContextCompat.getColor(context, R.color.karaoke_white)
-    private val colorGrey = ContextCompat.getColor(context, R.color.karaoke_lyric_grey)
-    private var lyricAlign: LyricAlign = LyricAlign.RIGHT
-
-    private val paintCurrentLine = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
-        color = colorWhite
-        textAlign = Paint.Align.RIGHT
-        textSize = spToPx(highlightTextSizeSp)
-        typeface = Typeface.DEFAULT_BOLD
-    }
-    private val paintHighlightedLine = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
-        color = colorBlue
-        textAlign = Paint.Align.RIGHT
-        textSize = spToPx(highlightTextSizeSp)
-        typeface = Typeface.DEFAULT_BOLD
-    }
-    private val paintNextLine = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
-        color = colorGrey
-        textAlign = Paint.Align.RIGHT
-        textSize = spToPx(nextLineTextSizeSp)
-        typeface = Typeface.DEFAULT
-    }
-
-    init {
-        updatePaintAlign()
-        updatePaintTextSize()
-    }
-
-    fun setLyricAlign(align: LyricAlign) {
-        if (lyricAlign != align) {
-            lyricAlign = align
-            updatePaintAlign()
-            invalidate()
-        }
-    }
-
-    private fun updatePaintAlign() {
-        val align = when (lyricAlign) {
-            LyricAlign.RIGHT -> Paint.Align.RIGHT
-            LyricAlign.CENTER -> Paint.Align.CENTER
-        }
-        paintCurrentLine.textAlign = align
-        paintHighlightedLine.textAlign = align
-        paintNextLine.textAlign = align
-    }
-
-    fun setLyricTextSize(highlightSp: Float, nextLineSp: Float) {
-        highlightTextSizeSp = highlightSp
-        nextLineTextSizeSp = nextLineSp
-        updatePaintTextSize()
-        invalidate()
-    }
-
-    private fun updatePaintTextSize() {
-        paintCurrentLine.textSize = spToPx(highlightTextSizeSp)
-        paintHighlightedLine.textSize = spToPx(highlightTextSizeSp)
-        paintNextLine.textSize = spToPx(nextLineTextSizeSp)
-        lineSpace = spToPx(nextLineTextSizeSp) * 1.8f
-    }
-
-    fun spToPx(sp: Float): Float {
-        return TypedValue.applyDimension(
-            TypedValue.COMPLEX_UNIT_SP,
-            sp,
-            context.resources.displayMetrics
-        )
-    }
-
-    fun setPlayProgress(progressMs: Long) {
-        currentProgressMs = progressMs
-        store.songLyrics.value?.let { mLyricList ->
-            if (mLyricList.isEmpty()) return@let
-            val newIndex = mLyricList.indexOfLast { it.startTimeMs <= progressMs }
-            currentLineIndex = if (newIndex != -1) newIndex else 0
-        }
-        invalidate()
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        super.onDraw(canvas)
-        val mLyricList = store.songLyrics.value ?: return
-        if (currentLineIndex !in mLyricList.indices) return
-
-        val mViewWidth = width.toFloat()
-        val mViewHeight = height.toFloat()
-        val mTextX = when (lyricAlign) {
-            LyricAlign.RIGHT -> mViewWidth
-            LyricAlign.CENTER -> mViewWidth / 2
-        }
-
-        val line1Y = mViewHeight / 2
-        val line2Y = line1Y + lineSpace
-
-        val currentLineData = mLyricList[currentLineIndex]
-        val currentLineText = currentLineData.fullContent
-        val currentLineWidth = paintCurrentLine.measureText(currentLineText)
-
-        val nextLineIndex = currentLineIndex + 1
-        val nextLineText =
-            if (nextLineIndex in mLyricList.indices) mLyricList[nextLineIndex].fullContent else ""
-
-        if (currentLineWidth > mViewWidth) {
-            val fittingChars = paintCurrentLine.breakText(currentLineText, true, mViewWidth, null)
-            val line1 = currentLineText.substring(0, fittingChars)
-            val line2 = currentLineText.substring(fittingChars)
-
-            val line1Duration =
-                currentLineData.characterArray.take(fittingChars).sumOf { it.durationMs }
-            val timeInLine = (currentProgressMs - currentLineData.startTimeMs).coerceAtLeast(0)
-
-            if (timeInLine < line1Duration) {
-                val progress = if (line1Duration > 0) timeInLine.toFloat() / line1Duration else 0f
-                drawSingleLineHighlight(canvas, line1, progress, line1Y, mTextX)
-                drawTruncatedLine(canvas, line2, line2Y, mTextX, false)
-            } else {
-                val line2Duration = (currentLineData.durationMs - line1Duration).coerceAtLeast(1)
-                val timeInLine2 = timeInLine - line1Duration
-                val progress = timeInLine2.toFloat() / line2Duration
-                drawSingleLineHighlight(canvas, line2, progress, line1Y, mTextX)
-                drawTruncatedLine(canvas, nextLineText, line2Y, mTextX, true)
-            }
-        } else {
-            val progress = calcCurrentLineProgress(currentProgressMs, currentLineData)
-            drawSingleLineHighlight(canvas, currentLineText, progress, line1Y, mTextX)
-            drawTruncatedLine(canvas, nextLineText, line2Y, mTextX, true)
-        }
-    }
-
-    private fun drawSingleLineHighlight(
-        canvas: Canvas,
-        text: String,
-        progress: Float,
-        y: Float,
-        x: Float
-    ) {
-        canvas.drawText(text, x, y, paintCurrentLine)
-
-        val textWidth = paintCurrentLine.measureText(text)
-        val highlightWidth = textWidth * progress.coerceIn(0f, 1f)
-        val textLeft = when (lyricAlign) {
-            LyricAlign.RIGHT -> x - textWidth
-            LyricAlign.CENTER -> x - textWidth / 2
-        }
-        canvas.save()
-        val clipPath = Path()
-        clipPath.addRect(
-            textLeft, y - paintCurrentLine.textSize,
-            textLeft + highlightWidth, y + paintCurrentLine.descent(), Path.Direction.CW
-        )
-        canvas.clipPath(clipPath)
-
-        canvas.drawText(text, x, y, paintHighlightedLine)
-        canvas.restore()
-    }
-
-    private fun drawTruncatedLine(
-        canvas: Canvas,
-        text: String,
-        y: Float,
-        x: Float,
-        truncate: Boolean
-    ) {
-        if (text.isEmpty()) return
-
-        if (truncate && paintNextLine.measureText(text) > width && width > 0) {
-            val ellipsis = "..."
-            val ellipsisWidth = paintNextLine.measureText(ellipsis)
-            val availableWidth = width - ellipsisWidth
-            val fittingChars = paintNextLine.breakText(text, true, availableWidth, null)
-            val truncatedText = text.substring(0, fittingChars) + ellipsis
-            canvas.drawText(truncatedText, x, y, paintNextLine)
-        } else {
-            canvas.drawText(text, x, y, paintNextLine)
-        }
-    }
-
-    companion object {
-        fun calcCurrentLineProgress(
-            currentTimeMillis: Long,
-            txLyricLine: TXChorusMusicPlayer.TXLyricLine,
-        ): Float {
-            val lineDuration = txLyricLine.durationMs
-            if (lineDuration <= 0) {
-                return if (currentTimeMillis > txLyricLine.startTimeMs) 1f else 0f
-            }
-            val offsetTime = currentTimeMillis - txLyricLine.startTimeMs
-            val progress = offsetTime / lineDuration.toFloat()
-            return progress.coerceIn(0f, 1f)
-        }
-    }
-}

+ 0 - 345
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/PitchView.kt

@@ -1,345 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.graphics.Typeface
-import android.graphics.Typeface.BOLD
-import android.graphics.drawable.Drawable
-import android.util.TypedValue
-import android.view.View
-import androidx.core.content.ContextCompat
-import com.tencent.trtc.TXChorusMusicPlayer
-import io.trtc.tuikit.atomicx.R
-import kotlin.math.abs
-import kotlin.math.cos
-import kotlin.math.sin
-import kotlin.random.Random
-
-class PitchView(
-    context: Context,
-) : View(context) {
-
-    private data class Butterfly(
-        val drawable: Drawable, val x0: Float, val y0: Float, val angle: Float,
-        val scale: Float, val baseRotation: Float, val startTime: Long, val lifeMs: Long
-    )
-
-    private val PITCH_TIME_TO_PIXELS_RATIO = 0.15f
-    private val PITCH_LINE_HEIGHT_DP = 3f
-    private val PITCH_DOT_RADIUS_DP = 4.5f
-    private val PITCH_HIT_TOLERANCE = 0.0f
-    private val SCORE_TEXT_SIZE_SP = 8f
-    private val SCORE_LABEL_GAP_DP = 3f
-    private val SCORE_BUBBLE_HEIGHT_DP = 12f
-    private val DOT_ANIMATION_SMOOTHING_FACTOR = 0.2f
-    private val lineColor = ContextCompat.getColor(context, R.color.karaoke_pitch_line)
-    private val highlightColor = ContextCompat.getColor(context, R.color.karaoke_text_color_red)
-    private val dotColor = ContextCompat.getColor(context, R.color.karaoke_white)
-    private val scoreLineColor = ContextCompat.getColor(context, R.color.karaoke_color_grey_8c)
-    private val scoreTextColor = ContextCompat.getColor(context, R.color.karaoke_pitch_score_text)
-    private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
-        style = Paint.Style.STROKE
-        strokeWidth = dpToPx(PITCH_LINE_HEIGHT_DP)
-        color = lineColor
-        strokeCap = Paint.Cap.ROUND
-    }
-    private val highlightLinePaint = Paint(linePaint).apply { color = highlightColor }
-    private val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
-        style = Paint.Style.FILL
-        color = dotColor
-        setShadowLayer(8f, 0f, 2f, 0x77000000)
-    }
-    private val scoreLinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
-        color = scoreLineColor
-        strokeWidth = dpToPx(1f)
-        style = Paint.Style.STROKE
-    }
-    private val scoreTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
-        color = scoreTextColor
-        style = Paint.Style.FILL
-        textSize = spToPx(SCORE_TEXT_SIZE_SP)
-        textAlign = Paint.Align.CENTER
-        typeface = Typeface.create(Typeface.DEFAULT, BOLD)
-    }
-    private val scoreTagDrawable: Drawable? by lazy {
-        ContextCompat.getDrawable(context, R.drawable.karaoke_score_bg)
-    }
-    private val butterflies = mutableListOf<Butterfly>()
-    private val butterflyDrawables: List<Drawable?> by lazy {
-        listOf(ContextCompat.getDrawable(context, R.drawable.karaoke_song_well_icon))
-    }
-    private val butterflyFlyDistance = dpToPx(52f)
-    private val butterflyLife = 1350L
-    private var pitchList: List<TXChorusMusicPlayer.TXReferencePitch> = emptyList()
-    private var userPitch: Int = 0
-    private var currentProgressMs: Long = 0L
-    private var currentScore: Int = -1
-    private var isScoringEnabled: Boolean = false
-    private var hitProgress: FloatArray = FloatArray(0)
-    private var pitchStartOffsetsPx: List<Float> = emptyList()
-    private var minPitch: Int = 0
-    private var maxPitch: Int = 100
-    private var scrollOffset: Float = 0f
-    private var currentDotTargetY: Float? = null
-    private var currentDotAnimatedY: Float? = null
-    private var lastHitSegmentIndexForButterfly = -1
-
-    fun setPitchList(list: List<TXChorusMusicPlayer.TXReferencePitch>?) {
-        val pitchList = list ?: emptyList()
-        this@PitchView.pitchList = pitchList
-        hitProgress = FloatArray(pitchList.size)
-
-        if (pitchList.isEmpty()) {
-            pitchStartOffsetsPx = emptyList()
-        } else {
-            minPitch = 0
-            maxPitch = 100
-            pitchStartOffsetsPx = pitchList.map { it.startTimeMs * PITCH_TIME_TO_PIXELS_RATIO }
-        }
-        resetState()
-        invalidate()
-    }
-
-    fun setPlayProgress(progressMs: Long) {
-        currentProgressMs = progressMs
-        updateStateByProgress()
-        invalidate()
-    }
-
-    fun setUserPitch(pitch: Int) {
-        val newPitch = pitch.coerceIn(0, 100)
-        if (userPitch != newPitch) {
-            userPitch = newPitch
-            invalidate()
-        }
-    }
-
-    fun setScore(score: Int) {
-        if (currentScore != score) {
-            currentScore = score
-            invalidate()
-        }
-    }
-
-    fun setScoringEnabled(enabled: Boolean) {
-        if (isScoringEnabled != enabled) {
-            isScoringEnabled = enabled
-            if (!enabled) {
-                currentScore = -1
-            }
-            invalidate()
-        }
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        super.onDraw(canvas)
-
-        if (width == 0 || height == 0) {
-            return
-        }
-
-        val viewCenterX = width / 2f
-        val viewHeight = height.toFloat()
-
-        if (pitchList.isEmpty()) {
-            canvas.drawLine(viewCenterX, 0f, viewCenterX, viewHeight, scoreLinePaint)
-            drawUserPitchDot(canvas, viewCenterX)
-            return
-        }
-
-        for (i in pitchList.indices) {
-            val pitchData = pitchList[i]
-            val lineLength = pitchData.durationMs * PITCH_TIME_TO_PIXELS_RATIO
-            val startOffsetPx = pitchStartOffsetsPx[i]
-
-            val x1 = viewCenterX + startOffsetPx - scrollOffset
-            val x2 = x1 + lineLength
-
-            if (x2 < 0 || x1 > width) {
-                continue
-            }
-
-            val y = convertPitchToY(pitchData.referencePitch.toFloat())
-
-            canvas.drawLine(x1, y, x2, y, linePaint)
-
-            val hitRatio = hitProgress[i]
-            if (hitRatio > 0) {
-                val highlightWidth = (x2 - x1) * hitRatio
-                canvas.drawLine(x1, y, x1 + highlightWidth, y, highlightLinePaint)
-            }
-        }
-
-        canvas.drawLine(viewCenterX, 0f, viewCenterX, viewHeight, scoreLinePaint)
-        drawUserPitchDot(canvas, viewCenterX)
-        drawButterflies(canvas)
-    }
-
-    private fun resetState() {
-        currentProgressMs = 0L
-        userPitch = 0
-        scrollOffset = 0f
-        currentDotTargetY = null
-        currentDotAnimatedY = null
-        butterflies.clear()
-        lastHitSegmentIndexForButterfly = -1
-        currentScore = -1
-        hitProgress.fill(0f)
-    }
-
-    private fun updateStateByProgress() {
-        scrollOffset = currentProgressMs * PITCH_TIME_TO_PIXELS_RATIO
-
-        val currentSegmentIndex = pitchList.indexOfFirst {
-            currentProgressMs >= it.startTimeMs && currentProgressMs < (it.startTimeMs + it.durationMs)
-        }
-
-        if (currentSegmentIndex != -1) {
-            checkPitchHit(currentSegmentIndex)
-        } else {
-            lastHitSegmentIndexForButterfly = -1
-        }
-
-        updateButterflies()
-    }
-
-    private fun checkPitchHit(currentSegmentIndex: Int) {
-        if (userPitch < 0) return
-
-        val segment = pitchList[currentSegmentIndex]
-        val referencePitch = segment.referencePitch
-        val pitchDifference = abs(userPitch - referencePitch)
-
-        if (pitchDifference <= PITCH_HIT_TOLERANCE) {
-            val progressInSegment = (currentProgressMs - segment.startTimeMs).toFloat()
-            val currentHitRatio = (progressInSegment / segment.durationMs).coerceIn(0f, 1f)
-            hitProgress[currentSegmentIndex] = hitProgress[currentSegmentIndex].coerceAtLeast(currentHitRatio)
-
-            if (lastHitSegmentIndexForButterfly != currentSegmentIndex) {
-                lastHitSegmentIndexForButterfly = currentSegmentIndex
-                val y = convertPitchToY(referencePitch.toFloat())
-                emitButterfly(width / 2f, y)
-            }
-        }
-    }
-
-    private fun drawUserPitchDot(canvas: Canvas, centerX: Float) {
-        val defaultY = convertPitchToY(minPitch.toFloat())
-        var animatedY = currentDotAnimatedY
-
-        val finalTargetY = if (currentScore < 0) {
-            defaultY
-        } else {
-            convertPitchToY(userPitch.toFloat())
-        }
-
-        if (animatedY == null) {
-            animatedY = finalTargetY
-        }
-
-        val diff = finalTargetY - animatedY
-        if (abs(diff) < 1f) {
-            animatedY = finalTargetY
-        } else {
-            animatedY += diff * DOT_ANIMATION_SMOOTHING_FACTOR
-            invalidate()
-        }
-
-        currentDotAnimatedY = animatedY
-        canvas.drawCircle(centerX, animatedY, dpToPx(PITCH_DOT_RADIUS_DP), dotPaint)
-
-        if (isScoringEnabled) {
-            drawScoreTag(canvas, centerX, animatedY)
-        }
-    }
-
-    private fun drawScoreTag(canvas: Canvas, centerX: Float, dotY: Float) {
-        val tagDrawable = scoreTagDrawable ?: return
-        val scoreText = if (currentScore < 0) "评分" else currentScore.toString()
-
-        val tagHeight = dpToPx(SCORE_BUBBLE_HEIGHT_DP)
-        val scale = tagHeight / tagDrawable.intrinsicHeight.toFloat()
-        val tagWidth = tagDrawable.intrinsicWidth * scale
-
-        val dotRadius = dpToPx(PITCH_DOT_RADIUS_DP)
-        val labelGap = dpToPx(SCORE_LABEL_GAP_DP)
-
-        val tagTop = (dotY - dotRadius - labelGap - tagHeight)
-        tagDrawable.setBounds(
-            (centerX - tagWidth / 2).toInt(),
-            tagTop.toInt(),
-            (centerX + tagWidth / 2).toInt(),
-            (tagTop + tagHeight).toInt()
-        )
-        tagDrawable.draw(canvas)
-
-        val textBaseY = tagTop + tagHeight / 2f + getTextHeightCenterOffset(scoreTextPaint)
-        canvas.drawText(scoreText, centerX, textBaseY, scoreTextPaint)
-    }
-
-    private fun convertPitchToY(pitch: Float): Float {
-        if (height == 0) return 0f
-
-        val drawTop = height * 0.1f
-        val drawHeight = height * 0.8f
-        val pitchRange = (maxPitch - minPitch).toFloat().coerceAtLeast(1f)
-        val clampedPitch = pitch.coerceIn(minPitch.toFloat(), maxPitch.toFloat())
-        val percent = (clampedPitch - minPitch) / pitchRange
-        return drawTop + (1.0f - percent) * drawHeight
-    }
-
-    private fun dpToPx(dp: Float): Float =
-        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
-
-    private fun spToPx(sp: Float): Float =
-        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, resources.displayMetrics)
-
-    private fun getTextHeightCenterOffset(paint: Paint): Float {
-        val metrics = paint.fontMetrics
-        return (metrics.descent - metrics.ascent) / 2 - metrics.descent
-    }
-
-    private fun emitButterfly(x: Float, y: Float) {
-        val drawable = butterflyDrawables.filterNotNull().randomOrNull() ?: return
-        val angle = 240f + (Random.nextFloat() - 0.5f) * 20f
-        val scale = 1.00f + Random.nextFloat() * 0.34f
-        val baseRotation = -15f + Random.nextFloat() * 30f
-        butterflies.add(
-            Butterfly(
-                drawable = drawable, x0 = x, y0 = y, angle = angle, scale = scale,
-                baseRotation = baseRotation, startTime = System.currentTimeMillis(), lifeMs = butterflyLife
-            )
-        )
-    }
-
-    private fun updateButterflies() {
-        val now = System.currentTimeMillis()
-        butterflies.removeAll { now - it.startTime > it.lifeMs }
-    }
-
-    private fun drawButterflies(canvas: Canvas) {
-        val now = System.currentTimeMillis()
-        for (b in butterflies) {
-            val t = ((now - b.startTime).toFloat() / b.lifeMs).coerceIn(0f, 1f)
-            val rad = Math.toRadians(b.angle.toDouble())
-            val dx = butterflyFlyDistance * t * cos(rad).toFloat()
-            val dy = butterflyFlyDistance * t * sin(rad).toFloat()
-            val x = b.x0 + dx
-            val y = b.y0 + dy
-            val scale = b.scale * (1.00f - 0.14f * t)
-            val d = b.drawable
-            val w = d.intrinsicWidth * scale
-            val h = d.intrinsicHeight * scale
-            val alpha = (180 * (1 - t)).toInt().coerceIn(0, 255)
-            canvas.save()
-            canvas.translate(x, y)
-            val swing = sin(t * Math.PI * 2.0 * 1.1f).toFloat() * 18f
-            canvas.rotate(b.baseRotation + swing + b.angle)
-            d.setBounds((-w / 2).toInt(), (-h / 2).toInt(), (w / 2).toInt(), (h / 2).toInt())
-            d.alpha = alpha
-            d.draw(canvas)
-            canvas.restore()
-        }
-    }
-}

+ 0 - 196
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/SongRequestPanel.kt

@@ -1,196 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.Observer
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.tabs.TabLayout
-import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.karaoke.store.KaraokeStore
-import io.trtc.tuikit.atomicx.karaoke.store.utils.MusicInfo
-import io.trtc.tuikit.atomicx.karaoke.view.adapter.KaraokeOrderedListAdapter
-import io.trtc.tuikit.atomicx.karaoke.view.adapter.KaraokeSongListAdapter
-import io.trtc.tuikit.atomicx.widget.basicwidget.popover.AtomicPopover
-
-class SongRequestPanel(
-    context: Context,
-    private val store: KaraokeStore,
-    private val isDisplayExitView: Boolean,
-) : AtomicPopover(context) {
-    private lateinit var recyclerSongBrowserView: RecyclerView
-    private lateinit var recyclerOrderedListView: RecyclerView
-    private var orderedTabView: TextView? = null
-    private val adapterSongList = KaraokeSongListAdapter(store)
-    private val adapterOrderedList = KaraokeOrderedListAdapter(store)
-    private val songSelectedListObserver = Observer(this::songSelectedListChange)
-    private val roomDismissedObserver = Observer(this::roomDismissedChange)
-    private val songLibraryListObserver = Observer(this::songLibraryListChange)
-
-    init {
-        initView()
-    }
-
-    private fun initView() {
-        val view: View =
-            LayoutInflater.from(context).inflate(R.layout.karaoke_song_request_panel, null)
-
-        setPanelHeight(PanelHeight.Ratio(0.6F))
-        initTabLayout(view)
-        initExitView(view)
-        initSongBrowserView(view)
-        initQueueManagerView(view)
-        configDialogHeight(view)
-        setContent(view)
-    }
-
-    private fun addObserve() {
-        store.songCatalog.observeForever(songLibraryListObserver)
-        store.songQueue.observeForever(songSelectedListObserver)
-        store.isRoomDismissed.observeForever(roomDismissedObserver)
-    }
-
-    private fun removeObserve() {
-        store.songCatalog.removeObserver(songLibraryListObserver)
-        store.songQueue.removeObserver(songSelectedListObserver)
-        store.isRoomDismissed.removeObserver(roomDismissedObserver)
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        addObserve()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        removeObserve()
-    }
-
-    private fun songLibraryListChange(list: List<MusicInfo>) {
-        adapterSongList.submitList(list.toList())
-    }
-
-    private fun songSelectedListChange(list: List<TUISongListManager.SongInfo>) {
-        orderedTabView?.text = context.getString(R.string.karaoke_ordered_count, list.size)
-        adapterOrderedList.submitList(list.toList())
-        store.updateSongCatalog(adapterSongList.currentList)
-        adapterSongList.submitList(adapterSongList.currentList)
-        triggerSongListRefresh()
-    }
-
-    private fun triggerSongListRefresh() {
-        val currentList = adapterSongList.currentList
-        adapterSongList.submitList(currentList.toList())
-    }
-
-    private fun roomDismissedChange(isRoomDismissed: Boolean) {
-        if (isRoomDismissed && this.isShowing) {
-            hide()
-        }
-    }
-
-    private fun initSongBrowserView(view: View) {
-        recyclerSongBrowserView = view.findViewById(R.id.rv_song_browser_list)
-        recyclerSongBrowserView.layoutManager = LinearLayoutManager(context)
-        recyclerSongBrowserView.adapter = adapterSongList
-        recyclerSongBrowserView.visibility = View.VISIBLE
-    }
-
-    private fun initQueueManagerView(view: View) {
-        recyclerOrderedListView = view.findViewById(R.id.rv_ordered_list)
-        recyclerOrderedListView.layoutManager = LinearLayoutManager(context)
-        recyclerOrderedListView.adapter = adapterOrderedList
-        recyclerOrderedListView.visibility = GONE
-    }
-
-    private fun initExitView(view: View) {
-        val exitView: FrameLayout = view.findViewById(R.id.fl_exit_request)
-        if (store.isRoomOwner.value == false || !isDisplayExitView) {
-            exitView.visibility = GONE
-        }
-        exitView.setOnClickListener {
-            super.hide()
-            store.enableRequestMusic(false)
-        }
-    }
-
-    override fun show() {
-        super.show()
-        if (store.isDisplayFloatView.value == false) {
-            store.enableRequestMusic(true)
-        }
-    }
-
-    private fun initTabLayout(view: View) {
-        val tabLayout = view.findViewById<TabLayout>(R.id.tab)
-        tabLayout.removeAllTabs()
-        val tabTitles = listOf(
-            R.string.karaoke_order_song,
-            R.string.karaoke_ordered_count
-        )
-        val tabColors = listOf(
-            R.color.karaoke_color_white,
-            R.color.karaoke_text_color_grey_4d
-        )
-
-        tabTitles.forEachIndexed { index, titleRes ->
-            tabLayout.addTab(createTab(view, titleRes, tabColors[index], index), index == 0)
-        }
-
-        tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
-            override fun onTabSelected(tab: TabLayout.Tab) {
-                setTabTextColor(tab, R.color.karaoke_color_white)
-                when (tab.position) {
-                    0 -> {
-                        recyclerSongBrowserView.visibility = View.VISIBLE
-                        recyclerOrderedListView.visibility = GONE
-                    }
-
-                    1 -> {
-                        recyclerSongBrowserView.visibility = GONE
-                        recyclerOrderedListView.visibility = View.VISIBLE
-                    }
-                }
-            }
-
-            override fun onTabUnselected(tab: TabLayout.Tab) {
-                setTabTextColor(tab, R.color.karaoke_text_color_grey_4d)
-
-            }
-
-            override fun onTabReselected(tab: TabLayout.Tab) {}
-        })
-    }
-
-    private fun createTab(view: View, titleRes: Int, textColorRes: Int, index: Int): TabLayout.Tab {
-        val context = view.context
-        val tabView =
-            LayoutInflater.from(context).inflate(R.layout.karaoke_tab_item, null) as TextView
-        tabView.text = context.getString(titleRes)
-        tabView.setTextColor(ContextCompat.getColor(context, textColorRes))
-        if (index == 1) {
-            orderedTabView = tabView
-        }
-        return (view.findViewById<TabLayout>(R.id.tab)).newTab().setCustomView(tabView)
-    }
-
-    private fun setTabTextColor(tab: TabLayout.Tab, colorRes: Int) {
-        val tabView = tab.customView as? TextView ?: return
-        tabView.setTextColor(ContextCompat.getColor(tabView.context, colorRes))
-    }
-
-    private fun configDialogHeight(view: View) {
-        view.layoutParams = ViewGroup.LayoutParams(
-            ViewGroup.LayoutParams.MATCH_PARENT,
-            (context.resources.displayMetrics.heightPixels * 0.6).toInt()
-        )
-    }
-}

+ 0 - 165
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/adapter/KaraokeOrderedListAdapter.kt

@@ -1,165 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view.adapter
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager
-import io.trtc.tuikit.atomicx.karaoke.store.KaraokeStore
-import io.trtc.tuikit.atomicx.karaoke.store.utils.PlaybackState
-import io.trtc.tuikit.atomicx.karaoke.view.adapter.KaraokeOrderedListAdapter.SongViewHolder
-import com.trtc.tuikit.common.imageloader.ImageLoader
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-
-class KaraokeOrderedListAdapter(private val store: KaraokeStore) :
-    ListAdapter<TUISongListManager.SongInfo, SongViewHolder>(DIFF) {
-    companion object {
-        val DIFF = object : DiffUtil.ItemCallback<TUISongListManager.SongInfo>() {
-            override fun areItemsTheSame(
-                oldItem: TUISongListManager.SongInfo,
-                newItem: TUISongListManager.SongInfo,
-            ): Boolean {
-                return oldItem == newItem
-            }
-
-            override fun areContentsTheSame(
-                oldItem: TUISongListManager.SongInfo,
-                newItem: TUISongListManager.SongInfo,
-            ): Boolean {
-                return false
-            }
-        }
-    }
-
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
-        val view = LayoutInflater.from(parent.context)
-            .inflate(R.layout.karaoke_music_requested_item, parent, false)
-        return SongViewHolder(view)
-    }
-
-    override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
-        holder.bind(getItem(position), position)
-    }
-
-    inner class SongViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-        private val imagePlaying: ImageView = itemView.findViewById(R.id.iv_playing)
-        private val textOrderIndex: TextView = itemView.findViewById(R.id.tv_order_index)
-        private val imageCover: ImageView = itemView.findViewById(R.id.iv_cover)
-        private val textSongName: TextView = itemView.findViewById(R.id.tv_song_name)
-        private val imageRequesterAvatar: AtomicAvatar =
-            itemView.findViewById(R.id.iv_user_avatar)
-        private val textRequesterName: TextView = itemView.findViewById(R.id.tv_requester_name)
-        private val imagePause: ImageView = itemView.findViewById(R.id.iv_pause)
-        private val imageNext: ImageView = itemView.findViewById(R.id.iv_next)
-        private val imagePin: ImageView = itemView.findViewById(R.id.iv_pin)
-        private val imageDelete: ImageView = itemView.findViewById(R.id.iv_delete)
-
-        fun bind(song: TUISongListManager.SongInfo, position: Int) {
-            textSongName.text = song.songName
-            initMusicPositionView(position)
-            initPlayingPauseView(position)
-            initPlayingNextView(position)
-            initMusicDeleteView(song, position)
-            initMusicPinView(song, position)
-            initOrderName(song)
-            initAvatarView(song)
-            initMusicCover(song)
-            initFunctionVisible()
-        }
-
-        private fun initOrderName(song: TUISongListManager.SongInfo) {
-            if (song.songName.isEmpty()) {
-                textRequesterName.text = song.requester.userId
-            } else {
-                textRequesterName.text = song.requester.userName
-            }
-        }
-
-        private fun initAvatarView(song: TUISongListManager.SongInfo) {
-            imageRequesterAvatar.setContent(
-                AtomicAvatar.AvatarContent.URL(
-                    song.requester.avatarUrl,
-                    R.drawable.karaoke_song_cover
-                )
-            )
-        }
-
-        private fun initMusicCover(music: TUISongListManager.SongInfo) {
-            ImageLoader.load(
-                imageCover.context,
-                imageCover,
-                music.coverUrl,
-                R.drawable.karaoke_song_cover
-            )
-        }
-
-        private fun initMusicPositionView(position: Int) {
-            imagePlaying.visibility = if (position == 0) VISIBLE else GONE
-            textOrderIndex.visibility = if (position == 0) GONE else VISIBLE
-            textOrderIndex.text = (position + 1).toString()
-        }
-
-        private fun initPlayingPauseView(position: Int) {
-            if (position == 0) {
-                imagePause.visibility = VISIBLE
-                val isPlaying = (store.playbackState.value != PlaybackState.STOP &&
-                        store.playbackState.value != PlaybackState.PAUSE)
-                imagePause.setImageResource(
-                    if (isPlaying) R.drawable.karaoke_music_resume else R.drawable.karaoke_music_pause
-                )
-                imagePause.setOnClickListener {
-                    val newIsPlaying =  (store.playbackState.value != PlaybackState.STOP &&
-                            store.playbackState.value != PlaybackState.PAUSE)
-                    if (newIsPlaying) {
-                        store.pausePlayback()
-                        imagePause.setImageResource(R.drawable.karaoke_music_pause)
-                    } else {
-                        store.resumePlayback()
-                        imagePause.setImageResource(R.drawable.karaoke_music_resume)
-                    }
-                }
-            } else {
-                imagePause.visibility = GONE
-                imagePause.setOnClickListener(null)
-            }
-        }
-
-        private fun initPlayingNextView(position: Int) {
-            imageNext.visibility = if (0 == position) VISIBLE else GONE
-            imageNext.setOnClickListener {
-                store.playNextSong()
-                store.setIsDisplayScoreView(false)
-            }
-        }
-
-        private fun initMusicDeleteView(music: TUISongListManager.SongInfo, position: Int) {
-            imageDelete.visibility = if (0 != position) VISIBLE else GONE
-            imageDelete.setOnClickListener {
-                store.removeSong(music)
-            }
-        }
-
-        private fun initMusicPinView(song: TUISongListManager.SongInfo, position: Int) {
-            imagePin.visibility = if (position > 1) VISIBLE else GONE
-            imagePin.setOnClickListener {
-                store.setNextSong(song.songId)
-            }
-        }
-
-        private fun initFunctionVisible() {
-            if (store.isRoomOwner.value == false) {
-                imagePause.visibility = GONE
-                imageNext.visibility = GONE
-                imagePin.visibility = GONE
-                imageDelete.visibility = GONE
-            }
-        }
-    }
-}

+ 0 - 107
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/karaoke/view/adapter/KaraokeSongListAdapter.kt

@@ -1,107 +0,0 @@
-package io.trtc.tuikit.atomicx.karaoke.view.adapter
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-import com.tencent.cloud.tuikit.engine.extension.TUISongListManager
-import com.tencent.cloud.tuikit.engine.room.TUIRoomDefine
-import com.tencent.cloud.tuikit.engine.room.TUIRoomEngine
-import com.trtc.tuikit.common.imageloader.ImageLoader
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.karaoke.store.KaraokeStore
-import io.trtc.tuikit.atomicx.karaoke.store.utils.MusicInfo
-
-class KaraokeSongListAdapter(private val store: KaraokeStore) :
-    ListAdapter<MusicInfo, KaraokeSongListAdapter.SongViewHolder>(DIFF) {
-    companion object {
-        val DIFF = object : DiffUtil.ItemCallback<MusicInfo>() {
-            override fun areItemsTheSame(oldItem: MusicInfo, newItem: MusicInfo): Boolean {
-                return oldItem == newItem
-            }
-
-            override fun areContentsTheSame(oldItem: MusicInfo, newItem: MusicInfo): Boolean {
-                return false
-            }
-        }
-    }
-
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
-        val view =
-            LayoutInflater.from(parent.context).inflate(R.layout.karaoke_music_library_item, parent, false)
-        return SongViewHolder(view)
-    }
-
-    override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
-        holder.bind(getItem(position))
-    }
-
-    inner class SongViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-        private val imageCover: ImageView = itemView.findViewById(R.id.iv_cover)
-        private val textSongName: TextView = itemView.findViewById(R.id.tv_song_name)
-        private val textSinger: TextView = itemView.findViewById(R.id.tv_singer)
-        private val buttonRequestSong: Button = itemView.findViewById(R.id.btn_request_music)
-
-        fun bind(music: MusicInfo) {
-            textSongName.text = music.musicName
-            textSinger.text = music.artist
-            initRequestSongButton(music)
-            initMusicCover(music)
-            initFunctionVisible()
-        }
-
-        private fun initMusicCover(music: MusicInfo) {
-            ImageLoader.load(
-                imageCover.context,
-                imageCover,
-                music.coverUrl,
-                R.drawable.karaoke_song_cover
-            )
-        }
-
-        private fun initRequestSongButton(music: MusicInfo) {
-            val isOrdered = store.songQueue.value?.any { it.songId == music.musicId } == true
-            if (isOrdered) {
-                buttonRequestSong.apply {
-                    background =
-                        ContextCompat.getDrawable(context, R.drawable.karaoke_btn_grey_edge_bg)
-                    text = context.getString(R.string.karaoke_ordered)
-                    isEnabled = false
-                    setOnClickListener(null)
-                }
-            } else {
-                buttonRequestSong.apply {
-                    background = ContextCompat.getDrawable(context, R.drawable.karaoke_btn_blue_bg)
-                    text = context.getString(R.string.karaoke_order_song)
-                    isEnabled = true
-                    setOnClickListener {
-                        val selfInfo: TUIRoomDefine.LoginUserInfo = TUIRoomEngine.getSelfInfo()
-                        val songInfo : TUISongListManager.SongInfo = TUISongListManager.SongInfo()
-                        songInfo.songId = music.musicId
-                        songInfo.songName = music.musicName
-                        songInfo.artistName = music.artist
-                        songInfo.duration = music.duration
-                        songInfo.coverUrl = music.coverUrl
-                        songInfo.requester.userId = selfInfo.userId
-                        songInfo.requester.userName = selfInfo.userName
-                        songInfo.requester.avatarUrl = selfInfo.avatarUrl
-                        store.addSong(songInfo)
-                    }
-                }
-            }
-        }
-
-        private fun initFunctionVisible() {
-            if (store.isRoomOwner.value == false) {
-                buttonRequestSong.visibility = GONE
-            }
-        }
-    }
-}

+ 0 - 24
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/pictureinpicture/PictureInPictureStore.kt

@@ -1,24 +0,0 @@
-package io.trtc.tuikit.atomicx.pictureinpicture
-
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.update
-
-data class PictureInPictureState(
-    val isPictureInPictureMode: StateFlow<Boolean>,
-)
-
-class PictureInPictureStore private constructor() {
-
-    companion object {
-        val shared: PictureInPictureStore by lazy { PictureInPictureStore() }
-    }
-
-    private val _isPictureInPictureMode = MutableStateFlow(false)
-
-    val state = PictureInPictureState(isPictureInPictureMode = _isPictureInPictureMode)
-
-    fun updateIsPictureInPictureMode(isPictureInPictureMode: Boolean) {
-        _isPictureInPictureMode.update { isPictureInPictureMode }
-    }
-}

+ 0 - 145
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/ThemeStore.kt

@@ -1,145 +0,0 @@
-package io.trtc.tuikit.atomicx.theme
-
-import android.content.Context
-import io.trtc.tuikit.atomicx.theme.tokens.ColorTokens
-import io.trtc.tuikit.atomicx.theme.tokens.DesignTokenSet
-import io.trtc.tuikit.atomicx.theme.utils.ColorAlgorithm
-import io.trtc.tuikit.atomicx.theme.utils.ThemePersistUtil
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-data class Theme(
-    val id: String,
-    val displayName: String,
-    val tokens: DesignTokenSet
-) {
-    companion object {
-
-        fun lightTheme(context: Context): Theme {
-            return Theme(
-                id = "light",
-                displayName = "Light",
-                tokens = DesignTokenSet.defaultLight(context)
-            )
-        }
-
-        fun darkTheme(context: Context): Theme {
-            return Theme(
-                id = "dark",
-                displayName = "Dark",
-                tokens = DesignTokenSet.defaultDark(context)
-            )
-        }
-    }
-}
-
-data class ThemeState(
-    val currentTheme: Theme
-)
-
-class ThemeStore private constructor(context: Context) {
-    private val appContext: Context = context.applicationContext
-    
-    private val _themeState = MutableStateFlow(ThemeState(Theme.darkTheme(appContext)))
-    val themeState: StateFlow<ThemeState> = _themeState.asStateFlow()
-
-    private var themePersistUtil: ThemePersistUtil = ThemePersistUtil(appContext)
-
-    init {
-        loadPersistedTheme()
-    }
-
-    companion object {
-        @Volatile
-        private var instance: ThemeStore? = null
-
-        fun shared(context: Context): ThemeStore {
-            return instance ?: synchronized(this) {
-                instance ?: ThemeStore(context.applicationContext).also {
-                    instance = it
-                }
-            }
-        }
-    }
-
-    fun setTheme(theme: Theme) {
-        updateThemeState(theme)
-        persistTheme(theme)
-    }
-
-    fun setPrimaryColor(hexColor: String) {
-        if (!hexColor.matches(Regex("^#[0-9A-Fa-f]{6}$"))) {
-            return
-        }
-
-        val palette = ColorAlgorithm.generateColorPalette(
-            appContext,
-            hexColor,
-            if (_themeState.value.currentTheme.id == Theme.darkTheme(appContext).id) "dark" else "light"
-        )
-
-        val newTokens = if (_themeState.value.currentTheme.id == Theme.darkTheme(appContext).id) {
-            ColorTokens.generateDarkTokens(appContext, palette)
-        } else {
-            ColorTokens.generateLightTokens(appContext, palette)
-        }
-
-        val newTheme = _themeState.value.currentTheme.copy(
-            tokens = _themeState.value.currentTheme.tokens.copy(color = newTokens)
-        )
-
-        updateThemeState(newTheme)
-        themePersistUtil.setCustomPrimaryColor(hexColor)
-    }
-
-    private fun updateThemeState(theme: Theme) {
-        _themeState.value = ThemeState(currentTheme = theme)
-    }
-
-    private fun loadPersistedTheme() {
-        if (themePersistUtil.getUserHasManuallySetTheme()) {
-            themePersistUtil.getCurrentThemeId()?.let { themeId ->
-                loadTheme(themeId)
-                return
-            }
-        }
-
-        if (themePersistUtil.getFollowSystemAppearance()) {
-            loadThemeBasedOnSystemAppearance()
-        }
-    }
-
-    private fun persistTheme(theme: Theme) {
-        themePersistUtil.setCurrentThemeId(theme.id)
-        themePersistUtil.setUserHasManuallySetTheme(true)
-        themePersistUtil.setFollowSystemAppearance(false)
-    }
-
-    private fun loadTheme(themeId: String) {
-        val theme = when (themeId) {
-            "light" -> Theme.lightTheme(appContext)
-            "dark" -> Theme.darkTheme(appContext)
-            else -> Theme.lightTheme(appContext)
-        }
-
-        themePersistUtil.getCustomPrimaryColor()?.let { hexColor ->
-            val palette = ColorAlgorithm.generateColorPalette(appContext, hexColor, themeId)
-            val customTokens = if (themeId == "dark") {
-                ColorTokens.generateDarkTokens(appContext, palette)
-            } else {
-                ColorTokens.generateLightTokens(appContext, palette)
-            }
-            val customTheme = theme.copy(
-                tokens = theme.tokens.copy(color = customTokens)
-            )
-            updateThemeState(customTheme)
-        } ?: run {
-            updateThemeState(theme)
-        }
-    }
-
-    private fun loadThemeBasedOnSystemAppearance() {
-
-    }
-}

+ 0 - 678
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/ColorTokens.kt

@@ -1,678 +0,0 @@
-package io.trtc.tuikit.atomicx.theme.tokens
-
-import android.content.Context
-import android.util.AttributeSet
-import androidx.annotation.ColorInt
-import androidx.core.content.ContextCompat
-import io.trtc.tuikit.atomicx.R
-
-data class ColorTokens(
-    // text & icon
-    @ColorInt val textColorPrimary: Int,
-    @ColorInt val textColorSecondary: Int,
-    @ColorInt val textColorTertiary: Int,
-    @ColorInt val textColorDisable: Int,
-    @ColorInt val textColorButton: Int,
-    @ColorInt val textColorButtonDisabled: Int,
-    @ColorInt val textColorLink: Int,
-    @ColorInt val textColorLinkHover: Int,
-    @ColorInt val textColorLinkActive: Int,
-    @ColorInt val textColorLinkDisabled: Int,
-    @ColorInt val textColorAntiPrimary: Int,
-    @ColorInt val textColorAntiSecondary: Int,
-    @ColorInt val textColorWarning: Int,
-    @ColorInt val textColorSuccess: Int,
-    @ColorInt val textColorError: Int,
-    // background
-    @ColorInt val bgColorTopBar: Int,
-    @ColorInt val bgColorOperate: Int,
-    @ColorInt val bgColorDialog: Int,
-    @ColorInt val bgColorDialogModule: Int,
-    @ColorInt val bgColorEntryCard: Int,
-    @ColorInt val bgColorFunction: Int,
-    @ColorInt val bgColorBottomBar: Int,
-    @ColorInt val bgColorInput: Int,
-    @ColorInt val bgColorBubbleReciprocal: Int,
-    @ColorInt val bgColorBubbleOwn: Int,
-    @ColorInt val bgColorDefault: Int,
-    @ColorInt val bgColorTagMask: Int,
-    @ColorInt val bgColorElementMask: Int,
-    @ColorInt val bgColorMask: Int,
-    @ColorInt val bgColorMaskDisappeared: Int,
-    @ColorInt val bgColorMaskBegin: Int,
-    @ColorInt val bgColorAvatar: Int,
-    // border
-    @ColorInt val strokeColorPrimary: Int,
-    @ColorInt val strokeColorSecondary: Int,
-    @ColorInt val strokeColorModule: Int,
-    // shadow
-    @ColorInt val shadowColor: Int,
-    // status
-    @ColorInt val listColorDefault: Int,
-    @ColorInt val listColorHover: Int,
-    @ColorInt val listColorFocused: Int,
-    // button
-    @ColorInt val buttonColorPrimaryDefault: Int,
-    @ColorInt val buttonColorPrimaryHover: Int,
-    @ColorInt val buttonColorPrimaryActive: Int,
-    @ColorInt val buttonColorPrimaryDisabled: Int,
-    @ColorInt val buttonColorSecondaryDefault: Int,
-    @ColorInt val buttonColorSecondaryHover: Int,
-    @ColorInt val buttonColorSecondaryActive: Int,
-    @ColorInt val buttonColorSecondaryDisabled: Int,
-    @ColorInt val buttonColorAccept: Int,
-    @ColorInt val buttonColorHangupDefault: Int,
-    @ColorInt val buttonColorHangupDisabled: Int,
-    @ColorInt val buttonColorHangupHover: Int,
-    @ColorInt val buttonColorHangupActive: Int,
-    @ColorInt val buttonColorOn: Int,
-    @ColorInt val buttonColorOff: Int,
-    // dropdown
-    @ColorInt val dropdownColorDefault: Int,
-    @ColorInt val dropdownColorHover: Int,
-    @ColorInt val dropdownColorActive: Int,
-    // scrollbar
-    @ColorInt val scrollbarColorDefault: Int,
-    @ColorInt val scrollbarColorHover: Int,
-    // floating
-    @ColorInt val floatingColorDefault: Int,
-    @ColorInt val floatingColorOperate: Int,
-    // checkbox
-    @ColorInt val checkboxColorSelected: Int,
-    // toast
-    @ColorInt val toastColorWarning: Int,
-    @ColorInt val toastColorSuccess: Int,
-    @ColorInt val toastColorError: Int,
-    @ColorInt val toastColorDefault: Int,
-    // tag
-    @ColorInt val tagColorLevel1: Int,
-    @ColorInt val tagColorLevel2: Int,
-    @ColorInt val tagColorLevel3: Int,
-    @ColorInt val tagColorLevel4: Int,
-    // switch
-    @ColorInt val switchColorOff: Int,
-    @ColorInt val switchColorOn: Int,
-    @ColorInt val switchColorButton: Int,
-    // slider
-    @ColorInt val sliderColorFilled: Int,
-    @ColorInt val sliderColorEmpty: Int,
-    @ColorInt val sliderColorButton: Int,
-    // tab
-    @ColorInt val tabColorSelected: Int,
-    @ColorInt val tabColorUnselected: Int,
-    @ColorInt val tabColorOption: Int,
-) {
-    operator fun get(key: String): Int {
-        return when (key) {
-            // --- Text & Icon ---
-            "textColorPrimary" -> textColorPrimary
-            "textColorSecondary" -> textColorSecondary
-            "textColorTertiary" -> textColorTertiary
-            "textColorDisable" -> textColorDisable
-            "textColorButton" -> textColorButton
-            "textColorButtonDisabled" -> textColorButtonDisabled
-            "textColorLink" -> textColorLink
-            "textColorLinkHover" -> textColorLinkHover
-            "textColorLinkActive" -> textColorLinkActive
-            "textColorLinkDisabled" -> textColorLinkDisabled
-            "textColorAntiPrimary" -> textColorAntiPrimary
-            "textColorAntiSecondary" -> textColorAntiSecondary
-            "textColorWarning" -> textColorWarning
-            "textColorSuccess" -> textColorSuccess
-            "textColorError" -> textColorError
-
-            // --- Background ---
-            "bgColorTopBar" -> bgColorTopBar
-            "bgColorOperate" -> bgColorOperate
-            "bgColorDialog" -> bgColorDialog
-            "bgColorDialogModule" -> bgColorDialogModule
-            "bgColorEntryCard" -> bgColorEntryCard
-            "bgColorFunction" -> bgColorFunction
-            "bgColorBottomBar" -> bgColorBottomBar
-            "bgColorInput" -> bgColorInput
-            "bgColorBubbleReciprocal" -> bgColorBubbleReciprocal
-            "bgColorBubbleOwn" -> bgColorBubbleOwn
-            "bgColorDefault" -> bgColorDefault
-            "bgColorTagMask" -> bgColorTagMask
-            "bgColorElementMask" -> bgColorElementMask
-            "bgColorMask" -> bgColorMask
-            "bgColorMaskDisappeared" -> bgColorMaskDisappeared
-            "bgColorMaskBegin" -> bgColorMaskBegin
-            "bgColorAvatar" -> bgColorAvatar
-
-            // --- Border ---
-            "strokeColorPrimary" -> strokeColorPrimary
-            "strokeColorSecondary" -> strokeColorSecondary
-            "strokeColorModule" -> strokeColorModule
-
-            // --- Shadow ---
-            "shadowColor" -> shadowColor
-
-            // --- Status ---
-            "listColorDefault" -> listColorDefault
-            "listColorHover" -> listColorHover
-            "listColorFocused" -> listColorFocused
-
-            // --- Button ---
-            "buttonColorPrimaryDefault" -> buttonColorPrimaryDefault
-            "buttonColorPrimaryHover" -> buttonColorPrimaryHover
-            "buttonColorPrimaryActive" -> buttonColorPrimaryActive
-            "buttonColorPrimaryDisabled" -> buttonColorPrimaryDisabled
-            "buttonColorSecondaryDefault" -> buttonColorSecondaryDefault
-            "buttonColorSecondaryHover" -> buttonColorSecondaryHover
-            "buttonColorSecondaryActive" -> buttonColorSecondaryActive
-            "buttonColorSecondaryDisabled" -> buttonColorSecondaryDisabled
-            "buttonColorAccept" -> buttonColorAccept
-            "buttonColorHangupDefault" -> buttonColorHangupDefault
-            "buttonColorHangupDisabled" -> buttonColorHangupDisabled
-            "buttonColorHangupHover" -> buttonColorHangupHover
-            "buttonColorHangupActive" -> buttonColorHangupActive
-            "buttonColorOn" -> buttonColorOn
-            "buttonColorOff" -> buttonColorOff
-
-            // --- Dropdown ---
-            "dropdownColorDefault" -> dropdownColorDefault
-            "dropdownColorHover" -> dropdownColorHover
-            "dropdownColorActive" -> dropdownColorActive
-
-            // --- Scrollbar ---
-            "scrollbarColorDefault" -> scrollbarColorDefault
-            "scrollbarColorHover" -> scrollbarColorHover
-
-            // --- Floating ---
-            "floatingColorDefault" -> floatingColorDefault
-            "floatingColorOperate" -> floatingColorOperate
-
-            // --- Checkbox ---
-            "checkboxColorSelected" -> checkboxColorSelected
-
-            // --- Toast ---
-            "toastColorWarning" -> toastColorWarning
-            "toastColorSuccess" -> toastColorSuccess
-            "toastColorError" -> toastColorError
-            "toastColorDefault" -> toastColorDefault
-
-            // --- Tag ---
-            "tagColorLevel1" -> tagColorLevel1
-            "tagColorLevel2" -> tagColorLevel2
-            "tagColorLevel3" -> tagColorLevel3
-            "tagColorLevel4" -> tagColorLevel4
-
-            // --- Switch ---
-            "switchColorOff" -> switchColorOff
-            "switchColorOn" -> switchColorOn
-            "switchColorButton" -> switchColorButton
-
-            // --- Slider ---
-            "sliderColorFilled" -> sliderColorFilled
-            "sliderColorEmpty" -> sliderColorEmpty
-            "sliderColorButton" -> sliderColorButton
-
-            // --- Tab ---
-            "tabColorSelected" -> tabColorSelected
-            "tabColorUnselected" -> tabColorUnselected
-            "tabColorOption" -> tabColorOption
-
-            else -> android.graphics.Color.TRANSPARENT
-        }
-    }
-
-    companion object {
-
-        fun defaultLight(context: Context): ColorTokens {
-            return ColorTokens(
-                // text & icon
-                textColorPrimary = ContextCompat.getColor(context, R.color.black_2),
-                textColorSecondary = ContextCompat.getColor(context, R.color.black_4),
-                textColorTertiary = ContextCompat.getColor(context, R.color.black_5),
-                textColorDisable = ContextCompat.getColor(context, R.color.black_6),
-                textColorButton = ContextCompat.getColor(context, R.color.white_1),
-                textColorButtonDisabled = ContextCompat.getColor(context, R.color.white_1),
-                textColorLink = ContextCompat.getColor(context, R.color.theme_light_6),
-                textColorLinkHover = ContextCompat.getColor(context, R.color.theme_light_5),
-                textColorLinkActive = ContextCompat.getColor(context, R.color.theme_light_7),
-                textColorLinkDisabled = ContextCompat.getColor(context, R.color.theme_light_2),
-                textColorAntiPrimary = ContextCompat.getColor(context, R.color.black_2),
-                textColorAntiSecondary = ContextCompat.getColor(context, R.color.black_4),
-                textColorWarning = ContextCompat.getColor(context, R.color.orange_light_6),
-                textColorSuccess = ContextCompat.getColor(context, R.color.green_light_6),
-                textColorError = ContextCompat.getColor(context, R.color.red_light_6),
-                // background
-                bgColorTopBar = ContextCompat.getColor(context, R.color.gray_light_1),
-                bgColorOperate = ContextCompat.getColor(context, R.color.white_1),
-                bgColorDialog = ContextCompat.getColor(context, R.color.white_1),
-                bgColorDialogModule = ContextCompat.getColor(context, R.color.gray_light_2),
-                bgColorEntryCard = ContextCompat.getColor(context, R.color.gray_light_2),
-                bgColorFunction = ContextCompat.getColor(context, R.color.gray_light_2),
-                bgColorBottomBar = ContextCompat.getColor(context, R.color.white_1),
-                bgColorInput = ContextCompat.getColor(context, R.color.gray_light_2),
-                bgColorBubbleReciprocal = ContextCompat.getColor(context, R.color.gray_light_2),
-                bgColorBubbleOwn = ContextCompat.getColor(context, R.color.theme_light_2),
-                bgColorDefault = ContextCompat.getColor(context, R.color.gray_light_2),
-                bgColorTagMask = ContextCompat.getColor(context, R.color.white_4),
-                bgColorElementMask = ContextCompat.getColor(context, R.color.black_6),
-                bgColorMask = ContextCompat.getColor(context, R.color.black_4),
-                bgColorMaskDisappeared = ContextCompat.getColor(context, R.color.white_7),
-                bgColorMaskBegin = ContextCompat.getColor(context, R.color.white_1),
-                bgColorAvatar = ContextCompat.getColor(context, R.color.theme_light_2),
-                // border
-                strokeColorPrimary = ContextCompat.getColor(context, R.color.gray_light_3),
-                strokeColorSecondary = ContextCompat.getColor(context, R.color.gray_light_2),
-                strokeColorModule = ContextCompat.getColor(context, R.color.gray_light_3),
-                // shadow
-                shadowColor = ContextCompat.getColor(context, R.color.black_8),
-                // status
-                listColorDefault = ContextCompat.getColor(context, R.color.white_1),
-                listColorHover = ContextCompat.getColor(context, R.color.gray_light_1),
-                listColorFocused = ContextCompat.getColor(context, R.color.theme_light_1),
-                // button
-                buttonColorPrimaryDefault = ContextCompat.getColor(context, R.color.theme_light_6),
-                buttonColorPrimaryHover = ContextCompat.getColor(context, R.color.theme_light_5),
-                buttonColorPrimaryActive = ContextCompat.getColor(context, R.color.theme_light_7),
-                buttonColorPrimaryDisabled = ContextCompat.getColor(context, R.color.theme_light_2),
-                buttonColorSecondaryDefault = ContextCompat.getColor(context, R.color.gray_light_2),
-                buttonColorSecondaryHover = ContextCompat.getColor(context, R.color.gray_light_1),
-                buttonColorSecondaryActive = ContextCompat.getColor(context, R.color.gray_light_3),
-                buttonColorSecondaryDisabled = ContextCompat.getColor(
-                    context,
-                    R.color.gray_light_1
-                ),
-                buttonColorAccept = ContextCompat.getColor(context, R.color.green_light_6),
-                buttonColorHangupDefault = ContextCompat.getColor(context, R.color.red_light_6),
-                buttonColorHangupDisabled = ContextCompat.getColor(context, R.color.red_light_2),
-                buttonColorHangupHover = ContextCompat.getColor(context, R.color.red_light_5),
-                buttonColorHangupActive = ContextCompat.getColor(context, R.color.red_light_7),
-                buttonColorOn = ContextCompat.getColor(context, R.color.white_1),
-                buttonColorOff = ContextCompat.getColor(context, R.color.black_5),
-                // dropdown
-                dropdownColorDefault = ContextCompat.getColor(context, R.color.white_1),
-                dropdownColorHover = ContextCompat.getColor(context, R.color.gray_light_1),
-                dropdownColorActive = ContextCompat.getColor(context, R.color.theme_light_1),
-                // scrollbar
-                scrollbarColorDefault = ContextCompat.getColor(context, R.color.black_7),
-                scrollbarColorHover = ContextCompat.getColor(context, R.color.black_6),
-                // floating
-                floatingColorDefault = ContextCompat.getColor(context, R.color.white_1),
-                floatingColorOperate = ContextCompat.getColor(context, R.color.gray_light_2),
-                // checkbox
-                checkboxColorSelected = ContextCompat.getColor(context, R.color.theme_light_6),
-                // toast
-                toastColorWarning = ContextCompat.getColor(context, R.color.orange_light_1),
-                toastColorSuccess = ContextCompat.getColor(context, R.color.green_light_1),
-                toastColorError = ContextCompat.getColor(context, R.color.red_light_1),
-                toastColorDefault = ContextCompat.getColor(context, R.color.theme_light_1),
-                // tag
-                tagColorLevel1 = ContextCompat.getColor(context, R.color.accent_turquoise_light),
-                tagColorLevel2 = ContextCompat.getColor(context, R.color.theme_light_5),
-                tagColorLevel3 = ContextCompat.getColor(context, R.color.accent_purple_light),
-                tagColorLevel4 = ContextCompat.getColor(context, R.color.accent_magenta_light),
-                // switch
-                switchColorOff = ContextCompat.getColor(context, R.color.gray_light_4),
-                switchColorOn = ContextCompat.getColor(context, R.color.theme_light_6),
-                switchColorButton = ContextCompat.getColor(context, R.color.white_1),
-                // slider
-                sliderColorFilled = ContextCompat.getColor(context, R.color.theme_light_6),
-                sliderColorEmpty = ContextCompat.getColor(context, R.color.gray_light_3),
-                sliderColorButton = ContextCompat.getColor(context, R.color.white_1),
-                // tab
-                tabColorSelected = ContextCompat.getColor(context, R.color.theme_light_2),
-                tabColorUnselected = ContextCompat.getColor(context, R.color.gray_light_2),
-                tabColorOption = ContextCompat.getColor(context, R.color.gray_light_3)
-            )
-        }
-
-        fun defaultDark(context: Context): ColorTokens {
-            return ColorTokens(
-                // text & icon
-                textColorPrimary = ContextCompat.getColor(context, R.color.text_color_primary),
-                textColorSecondary = ContextCompat.getColor(context, R.color.text_color_secondary),
-                textColorTertiary = ContextCompat.getColor(context, R.color.text_color_tertiary),
-                textColorDisable = ContextCompat.getColor(context, R.color.text_color_disable),
-                textColorButton = ContextCompat.getColor(context, R.color.text_color_button),
-                textColorButtonDisabled = ContextCompat.getColor(
-                    context,
-                    R.color.text_color_button_disabled
-                ),
-                textColorLink = ContextCompat.getColor(context, R.color.text_color_link),
-                textColorLinkHover = ContextCompat.getColor(context, R.color.text_color_link_hover),
-                textColorLinkActive = ContextCompat.getColor(
-                    context,
-                    R.color.text_color_link_active
-                ),
-                textColorLinkDisabled = ContextCompat.getColor(
-                    context,
-                    R.color.text_color_link_disabled
-                ),
-                textColorAntiPrimary = ContextCompat.getColor(
-                    context,
-                    R.color.text_color_anti_primary
-                ),
-                textColorAntiSecondary = ContextCompat.getColor(
-                    context,
-                    R.color.text_color_anti_secondary
-                ),
-                textColorWarning = ContextCompat.getColor(context, R.color.text_color_warning),
-                textColorSuccess = ContextCompat.getColor(context, R.color.text_color_success),
-                textColorError = ContextCompat.getColor(context, R.color.text_color_error),
-                // background
-                bgColorTopBar = ContextCompat.getColor(context, R.color.bg_color_top_bar),
-                bgColorOperate = ContextCompat.getColor(context, R.color.bg_color_operate),
-                bgColorDialog = ContextCompat.getColor(context, R.color.bg_color_dialog),
-                bgColorDialogModule = ContextCompat.getColor(
-                    context,
-                    R.color.bg_color_dialog_module
-                ),
-                bgColorEntryCard = ContextCompat.getColor(context, R.color.bg_color_entry_card),
-                bgColorFunction = ContextCompat.getColor(context, R.color.bg_color_function),
-                bgColorBottomBar = ContextCompat.getColor(context, R.color.bg_color_bottom_bar),
-                bgColorInput = ContextCompat.getColor(context, R.color.bg_color_input),
-                bgColorBubbleReciprocal = ContextCompat.getColor(
-                    context,
-                    R.color.bg_color_bubble_reciprocal
-                ),
-                bgColorBubbleOwn = ContextCompat.getColor(context, R.color.bg_color_bubble_own),
-                bgColorDefault = ContextCompat.getColor(context, R.color.bg_color_default),
-                bgColorTagMask = ContextCompat.getColor(context, R.color.bg_color_tag_mask),
-                bgColorElementMask = ContextCompat.getColor(context, R.color.bg_color_element_mask),
-                bgColorMask = ContextCompat.getColor(context, R.color.bg_color_mask),
-                bgColorMaskDisappeared = ContextCompat.getColor(
-                    context,
-                    R.color.bg_color_mask_disappeared
-                ),
-                bgColorMaskBegin = ContextCompat.getColor(context, R.color.bg_color_mask_begin),
-                bgColorAvatar = ContextCompat.getColor(context, R.color.bg_color_avatar),
-                // border
-                strokeColorPrimary = ContextCompat.getColor(context, R.color.stroke_color_primary),
-                strokeColorSecondary = ContextCompat.getColor(
-                    context,
-                    R.color.stroke_color_secondary
-                ),
-                strokeColorModule = ContextCompat.getColor(context, R.color.stroke_color_module),
-                // shadow
-                shadowColor = ContextCompat.getColor(context, R.color.shadow_color),
-                // status
-                listColorDefault = ContextCompat.getColor(context, R.color.list_color_default),
-                listColorHover = ContextCompat.getColor(context, R.color.list_color_hover),
-                listColorFocused = ContextCompat.getColor(context, R.color.list_color_focused),
-                // button
-                buttonColorPrimaryDefault = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_primary_default
-                ),
-                buttonColorPrimaryHover = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_primary_hover
-                ),
-                buttonColorPrimaryActive = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_primary_active
-                ),
-                buttonColorPrimaryDisabled = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_primary_disabled
-                ),
-                buttonColorSecondaryDefault = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_secondary_default
-                ),
-                buttonColorSecondaryHover = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_secondary_hover
-                ),
-                buttonColorSecondaryActive = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_secondary_active
-                ),
-                buttonColorSecondaryDisabled = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_secondary_disabled
-                ),
-                buttonColorAccept = ContextCompat.getColor(context, R.color.button_color_accept),
-                buttonColorHangupDefault = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_hangup_default
-                ),
-                buttonColorHangupDisabled = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_hangup_disabled
-                ),
-                buttonColorHangupHover = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_hangup_hover
-                ),
-                buttonColorHangupActive = ContextCompat.getColor(
-                    context,
-                    R.color.button_color_hangup_active
-                ),
-                buttonColorOn = ContextCompat.getColor(context, R.color.button_color_on),
-                buttonColorOff = ContextCompat.getColor(context, R.color.button_color_off),
-                // dropdown
-                dropdownColorDefault = ContextCompat.getColor(
-                    context,
-                    R.color.dropdown_color_default
-                ),
-                dropdownColorHover = ContextCompat.getColor(context, R.color.dropdown_color_hover),
-                dropdownColorActive = ContextCompat.getColor(
-                    context,
-                    R.color.dropdown_color_active
-                ),
-                // scrollbar
-                scrollbarColorDefault = ContextCompat.getColor(
-                    context,
-                    R.color.scrollbar_color_default
-                ),
-                scrollbarColorHover = ContextCompat.getColor(
-                    context,
-                    R.color.scrollbar_color_hover
-                ),
-                // floating
-                floatingColorDefault = ContextCompat.getColor(
-                    context,
-                    R.color.floating_color_default
-                ),
-                floatingColorOperate = ContextCompat.getColor(
-                    context,
-                    R.color.floating_color_operate
-                ),
-                // checkbox
-                checkboxColorSelected = ContextCompat.getColor(
-                    context,
-                    R.color.checkbox_color_selected
-                ),
-                // toast
-                toastColorWarning = ContextCompat.getColor(context, R.color.toast_color_warning),
-                toastColorSuccess = ContextCompat.getColor(context, R.color.toast_color_success),
-                toastColorError = ContextCompat.getColor(context, R.color.toast_color_error),
-                toastColorDefault = ContextCompat.getColor(context, R.color.toast_color_default),
-                // tag
-                tagColorLevel1 = ContextCompat.getColor(context, R.color.tag_color_level1),
-                tagColorLevel2 = ContextCompat.getColor(context, R.color.tag_color_level2),
-                tagColorLevel3 = ContextCompat.getColor(context, R.color.tag_color_level3),
-                tagColorLevel4 = ContextCompat.getColor(context, R.color.tag_color_level4),
-                // switch
-                switchColorOff = ContextCompat.getColor(context, R.color.switch_color_off),
-                switchColorOn = ContextCompat.getColor(context, R.color.switch_color_on),
-                switchColorButton = ContextCompat.getColor(context, R.color.switch_color_button),
-                // slider
-                sliderColorFilled = ContextCompat.getColor(context, R.color.slider_color_filled),
-                sliderColorEmpty = ContextCompat.getColor(context, R.color.slider_color_empty),
-                sliderColorButton = ContextCompat.getColor(context, R.color.slider_color_button),
-                // tab
-                tabColorSelected = ContextCompat.getColor(context, R.color.tab_color_selected),
-                tabColorUnselected = ContextCompat.getColor(context, R.color.tab_color_unselected),
-                tabColorOption = ContextCompat.getColor(context, R.color.tab_color_option)
-            )
-        }
-
-        fun generateLightTokens(context: Context, palette: List<Int>): ColorTokens {
-            val themeLight1 = palette[0]
-            val themeLight2 = palette[1]
-            val themeLight5 = palette[4]
-            val themeLight6 = palette[5]
-            val themeLight7 = palette[6]
-
-            return defaultLight(context).copy(
-                textColorLink = themeLight6,
-                textColorLinkHover = themeLight5,
-                textColorLinkActive = themeLight7,
-                textColorLinkDisabled = themeLight2,
-                bgColorBubbleOwn = themeLight2,
-                bgColorAvatar = themeLight2,
-                listColorFocused = themeLight1,
-                buttonColorPrimaryDefault = themeLight6,
-                buttonColorPrimaryHover = themeLight5,
-                buttonColorPrimaryActive = themeLight7,
-                buttonColorPrimaryDisabled = themeLight2,
-                dropdownColorActive = themeLight1,
-                checkboxColorSelected = themeLight6,
-                toastColorDefault = themeLight1,
-                tagColorLevel2 = themeLight5,
-                switchColorOn = themeLight6,
-                sliderColorFilled = themeLight6,
-                tabColorSelected = themeLight2
-            )
-        }
-
-        fun generateDarkTokens(context: Context, palette: List<Int>): ColorTokens {
-            val themeDark2 = palette[1]
-            val themeDark5 = palette[4]
-            val themeDark6 = palette[5]
-            val themeDark7 = palette[6]
-
-            return defaultDark(context).copy(
-                textColorLink = themeDark6,
-                textColorLinkHover = themeDark5,
-                textColorLinkActive = themeDark7,
-                textColorLinkDisabled = themeDark2,
-                bgColorBubbleOwn = themeDark7,
-                bgColorAvatar = themeDark2,
-                listColorFocused = themeDark2,
-                buttonColorPrimaryDefault = themeDark6,
-                buttonColorPrimaryHover = themeDark5,
-                buttonColorPrimaryActive = themeDark7,
-                buttonColorPrimaryDisabled = themeDark2,
-                dropdownColorActive = themeDark2,
-                checkboxColorSelected = themeDark5,
-                toastColorDefault = themeDark2,
-                tagColorLevel2 = themeDark5,
-                switchColorOn = themeDark5,
-                sliderColorFilled = themeDark5,
-                tabColorSelected = themeDark5
-            )
-        }
-
-        private val tokenNameToResIdMap = mapOf(
-            "textColorPrimary" to R.color.text_color_primary,
-            "textColorSecondary" to R.color.text_color_secondary,
-            "textColorTertiary" to R.color.text_color_tertiary,
-            "textColorDisable" to R.color.text_color_disable,
-            "textColorButton" to R.color.text_color_button,
-            "textColorButtonDisabled" to R.color.text_color_button_disabled,
-            "textColorLink" to R.color.text_color_link,
-            "textColorLinkHover" to R.color.text_color_link_hover,
-            "textColorLinkActive" to R.color.text_color_link_active,
-            "textColorLinkDisabled" to R.color.text_color_link_disabled,
-            "textColorAntiPrimary" to R.color.text_color_anti_primary,
-            "textColorAntiSecondary" to R.color.text_color_anti_secondary,
-            "textColorWarning" to R.color.text_color_warning,
-            "textColorSuccess" to R.color.text_color_success,
-            "textColorError" to R.color.text_color_error,
-            "bgColorTopBar" to R.color.bg_color_top_bar,
-            "bgColorOperate" to R.color.bg_color_operate,
-            "bgColorDialog" to R.color.bg_color_dialog,
-            "bgColorDialogModule" to R.color.bg_color_dialog_module,
-            "bgColorEntryCard" to R.color.bg_color_entry_card,
-            "bgColorFunction" to R.color.bg_color_function,
-            "bgColorBottomBar" to R.color.bg_color_bottom_bar,
-            "bgColorInput" to R.color.bg_color_input,
-            "bgColorBubbleReciprocal" to R.color.bg_color_bubble_reciprocal,
-            "bgColorBubbleOwn" to R.color.bg_color_bubble_own,
-            "bgColorDefault" to R.color.bg_color_default,
-            "bgColorTagMask" to R.color.bg_color_tag_mask,
-            "bgColorElementMask" to R.color.bg_color_element_mask,
-            "bgColorMask" to R.color.bg_color_mask,
-            "bgColorMaskDisappeared" to R.color.bg_color_mask_disappeared,
-            "bgColorMaskBegin" to R.color.bg_color_mask_begin,
-            "bgColorAvatar" to R.color.bg_color_avatar,
-            "strokeColorPrimary" to R.color.stroke_color_primary,
-            "strokeColorSecondary" to R.color.stroke_color_secondary,
-            "strokeColorModule" to R.color.stroke_color_module,
-            "shadowColor" to R.color.shadow_color,
-            "listColorDefault" to R.color.list_color_default,
-            "listColorHover" to R.color.list_color_hover,
-            "listColorFocused" to R.color.list_color_focused,
-            "buttonColorPrimaryDefault" to R.color.button_color_primary_default,
-            "buttonColorPrimaryHover" to R.color.button_color_primary_hover,
-            "buttonColorPrimaryActive" to R.color.button_color_primary_active,
-            "buttonColorPrimaryDisabled" to R.color.button_color_primary_disabled,
-            "buttonColorSecondaryDefault" to R.color.button_color_secondary_default,
-            "buttonColorSecondaryHover" to R.color.button_color_secondary_hover,
-            "buttonColorSecondaryActive" to R.color.button_color_secondary_active,
-            "buttonColorSecondaryDisabled" to R.color.button_color_secondary_disabled,
-            "buttonColorAccept" to R.color.button_color_accept,
-            "buttonColorHangupDefault" to R.color.button_color_hangup_default,
-            "buttonColorHangupDisabled" to R.color.button_color_hangup_disabled,
-            "buttonColorHangupHover" to R.color.button_color_hangup_hover,
-            "buttonColorHangupActive" to R.color.button_color_hangup_active,
-            "buttonColorOn" to R.color.button_color_on,
-            "buttonColorOff" to R.color.button_color_off,
-            "dropdownColorDefault" to R.color.dropdown_color_default,
-            "dropdownColorHover" to R.color.dropdown_color_hover,
-            "dropdownColorActive" to R.color.dropdown_color_active,
-            "scrollbarColorDefault" to R.color.scrollbar_color_default,
-            "scrollbarColorHover" to R.color.scrollbar_color_hover,
-            "floatingColorDefault" to R.color.floating_color_default,
-            "floatingColorOperate" to R.color.floating_color_operate,
-            "checkboxColorSelected" to R.color.checkbox_color_selected,
-            "toastColorWarning" to R.color.toast_color_warning,
-            "toastColorSuccess" to R.color.toast_color_success,
-            "toastColorError" to R.color.toast_color_error,
-            "toastColorDefault" to R.color.toast_color_default,
-            "tagColorLevel1" to R.color.tag_color_level1,
-            "tagColorLevel2" to R.color.tag_color_level2,
-            "tagColorLevel3" to R.color.tag_color_level3,
-            "tagColorLevel4" to R.color.tag_color_level4,
-            "switchColorOff" to R.color.switch_color_off,
-            "switchColorOn" to R.color.switch_color_on,
-            "switchColorButton" to R.color.switch_color_button,
-            "sliderColorFilled" to R.color.slider_color_filled,
-            "sliderColorEmpty" to R.color.slider_color_empty,
-            "sliderColorButton" to R.color.slider_color_button,
-            "tabColorSelected" to R.color.tab_color_selected,
-            "tabColorUnselected" to R.color.tab_color_unselected,
-            "tabColorOption" to R.color.tab_color_option
-        )
-
-        private val resIdToTokenNameMap by lazy {
-            tokenNameToResIdMap.entries.associate { it.value to it.key }
-        }
-
-        fun getTokenKeyFromColorResId(colorResId: Int): String? {
-            return resIdToTokenNameMap[colorResId]
-        }
-
-        fun parseColorAttribute(
-            attrs: AttributeSet?,
-            attrId: Int,
-            attrName: String,
-        ): String? {
-            if (attrs == null) return null
-
-            for (i in 0 until attrs.attributeCount) {
-                if (attrs.getAttributeNameResource(i) == attrId) {
-                    val resourceId = attrs.getAttributeResourceValue(i, -1)
-                    if (resourceId != -1) {
-                        return try {
-                            getTokenKeyFromColorResId(resourceId)
-                        } catch (e: Exception) {
-                            android.util.Log.e("ColorTokens", "Error parsing $attrName", e)
-                            null
-                        }
-                    }
-                    break
-                }
-            }
-            return null
-        }
-    }
-}

+ 0 - 34
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/DesignTokenSet.kt

@@ -1,34 +0,0 @@
-package io.trtc.tuikit.atomicx.theme.tokens
-
-import android.content.Context
-
-data class DesignTokenSet(
-    val id: String,
-    val displayName: String,
-    val color: ColorTokens,
-    val font: FontTokens,
-    val shadow: ShadowTokens
-) {
-    companion object {
-
-        fun defaultLight(context: Context): DesignTokenSet {
-            return DesignTokenSet(
-                id = "light",
-                displayName = "Light Theme",
-                color = ColorTokens.defaultLight(context),
-                font = FontTokens(),
-                shadow = ShadowTokens.standard()
-            )
-        }
-
-        fun defaultDark(context: Context): DesignTokenSet {
-            return DesignTokenSet(
-                id = "dark",
-                displayName = "Dark Theme",
-                color = ColorTokens.defaultDark(context),
-                font = FontTokens(),
-                shadow = ShadowTokens.standard()
-            )
-        }
-    }
-}

+ 0 - 36
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/FontTokens.kt

@@ -1,36 +0,0 @@
-package io.trtc.tuikit.atomicx.theme.tokens
-
-import android.graphics.Typeface
-
-data class Font(
-    val size: Float,
-    val weight: Int
-)
-
-data class FontTokens(
-    val bold40: Font = Font(size = 40f, weight = Typeface.BOLD),
-    val bold36: Font = Font(size = 36f, weight = Typeface.BOLD),
-    val bold34: Font = Font(size = 34f, weight = Typeface.BOLD),
-    val bold32: Font = Font(size = 32f, weight = Typeface.BOLD),
-    val bold28: Font = Font(size = 28f, weight = Typeface.BOLD),
-    val bold24: Font = Font(size = 24f, weight = Typeface.BOLD),
-    val bold20: Font = Font(size = 20f, weight = Typeface.BOLD),
-    val bold18: Font = Font(size = 18f, weight = Typeface.BOLD),
-    val bold16: Font = Font(size = 16f, weight = Typeface.BOLD),
-    val bold14: Font = Font(size = 14f, weight = Typeface.BOLD),
-    val bold12: Font = Font(size = 12f, weight = Typeface.BOLD),
-    val bold10: Font = Font(size = 10f, weight = Typeface.BOLD),
-
-    val regular40: Font = Font(size = 40f, weight = Typeface.NORMAL),
-    val regular36: Font = Font(size = 36f, weight = Typeface.NORMAL),
-    val regular34: Font = Font(size = 34f, weight = Typeface.NORMAL),
-    val regular32: Font = Font(size = 32f, weight = Typeface.NORMAL),
-    val regular28: Font = Font(size = 28f, weight = Typeface.NORMAL),
-    val regular24: Font = Font(size = 24f, weight = Typeface.NORMAL),
-    val regular20: Font = Font(size = 20f, weight = Typeface.NORMAL),
-    val regular18: Font = Font(size = 18f, weight = Typeface.NORMAL),
-    val regular16: Font = Font(size = 16f, weight = Typeface.NORMAL),
-    val regular14: Font = Font(size = 14f, weight = Typeface.NORMAL),
-    val regular12: Font = Font(size = 12f, weight = Typeface.NORMAL),
-    val regular10: Font = Font(size = 10f, weight = Typeface.NORMAL)
-)

+ 0 - 41
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/tokens/ShadowTokens.kt

@@ -1,41 +0,0 @@
-package io.trtc.tuikit.atomicx.theme.tokens
-
-import androidx.annotation.ColorInt
-
-data class ShadowStyle(
-    val elevation: Float,
-    @ColorInt val color: Int,
-    val x: Float = 0f,
-    val y: Float = 0f,
-    val radius: Float = 0f,
-    val opacity: Float = 1.0f
-)
-
-data class ShadowTokens(
-    val smallShadow: ShadowStyle,
-    val mediumShadow: ShadowStyle
-) {
-    companion object {
-
-        fun standard(): ShadowTokens {
-            return ShadowTokens(
-                smallShadow = ShadowStyle(
-                    elevation = 2f,
-                    color = 0x1F000000.toInt(),
-                    x = 0f,
-                    y = 2f,
-                    radius = 4f,
-                    opacity = 1.0f
-                ),
-                mediumShadow = ShadowStyle(
-                    elevation = 4f,
-                    color = 0x29000000.toInt(),
-                    x = 0f,
-                    y = 4f,
-                    radius = 8f,
-                    opacity = 1.0f
-                )
-            )
-        }
-    }
-}

+ 0 - 258
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/utils/ColorAlgorithm.kt

@@ -1,258 +0,0 @@
-package io.trtc.tuikit.atomicx.theme.utils
-
-import android.content.Context
-import androidx.annotation.ColorInt
-import androidx.core.content.ContextCompat
-import io.trtc.tuikit.atomicx.R
-import kotlin.math.*
-import androidx.core.graphics.toColorInt
-
-object ColorAlgorithm {
-    fun generateColorPalette(context: Context, baseColor: String, theme: String): List<Int> {
-        return if (isStandardColor(baseColor)) {
-            val paletteType = getClosestPaletteType(baseColor)
-            getPaletteFromResources(context, paletteType, theme)
-        } else {
-            generateDynamicColorVariations(baseColor, theme)
-        }
-    }
-
-    @ColorInt
-    fun parseHexColor(hexColor: String): Int {
-        val cleanHex = hexColor.removePrefix("#")
-        return "#$cleanHex".toColorInt()
-    }
-
-    private enum class PaletteType {
-        BLUE, GREEN, RED, ORANGE
-    }
-
-    private fun getPaletteFromResources(
-        context: Context,
-        type: PaletteType,
-        theme: String
-    ): List<Int> {
-        val colorIds = when (type) {
-            PaletteType.BLUE -> if (theme == "dark") {
-                listOf(
-                    R.color.theme_dark_1, R.color.theme_dark_2, R.color.theme_dark_3,
-                    R.color.theme_dark_4, R.color.theme_dark_5, R.color.theme_dark_6,
-                    R.color.theme_dark_7, R.color.theme_dark_8, R.color.theme_dark_9,
-                    R.color.theme_dark_10
-                )
-            } else {
-                listOf(
-                    R.color.theme_light_1, R.color.theme_light_2, R.color.theme_light_3,
-                    R.color.theme_light_4, R.color.theme_light_5, R.color.theme_light_6,
-                    R.color.theme_light_7, R.color.theme_light_8, R.color.theme_light_9,
-                    R.color.theme_light_10
-                )
-            }
-
-            PaletteType.GREEN -> if (theme == "dark") {
-                listOf(
-                    R.color.green_dark_1, R.color.green_dark_2, R.color.green_dark_3,
-                    R.color.green_dark_4, R.color.green_dark_5, R.color.green_dark_6,
-                    R.color.green_dark_7, R.color.green_dark_8, R.color.green_dark_9,
-                    R.color.green_dark_10
-                )
-            } else {
-                listOf(
-                    R.color.green_light_1, R.color.green_light_2, R.color.green_light_3,
-                    R.color.green_light_4, R.color.green_light_5, R.color.green_light_6,
-                    R.color.green_light_7, R.color.green_light_8, R.color.green_light_9,
-                    R.color.green_light_10
-                )
-            }
-
-            PaletteType.RED -> if (theme == "dark") {
-                listOf(
-                    R.color.red_dark_1, R.color.red_dark_2, R.color.red_dark_3,
-                    R.color.red_dark_4, R.color.red_dark_5, R.color.red_dark_6,
-                    R.color.red_dark_7, R.color.red_dark_8, R.color.red_dark_9,
-                    R.color.red_dark_10
-                )
-            } else {
-                listOf(
-                    R.color.red_light_1, R.color.red_light_2, R.color.red_light_3,
-                    R.color.red_light_4, R.color.red_light_5, R.color.red_light_6,
-                    R.color.red_light_7, R.color.red_light_8, R.color.red_light_9,
-                    R.color.red_light_10
-                )
-            }
-
-            PaletteType.ORANGE -> if (theme == "dark") {
-                listOf(
-                    R.color.orange_dark_1, R.color.orange_dark_2, R.color.orange_dark_3,
-                    R.color.orange_dark_4, R.color.orange_dark_5, R.color.orange_dark_6,
-                    R.color.orange_dark_7, R.color.orange_dark_8, R.color.orange_dark_9,
-                    R.color.orange_dark_10
-                )
-            } else {
-                listOf(
-                    R.color.orange_light_1, R.color.orange_light_2, R.color.orange_light_3,
-                    R.color.orange_light_4, R.color.orange_light_5, R.color.orange_light_6,
-                    R.color.orange_light_7, R.color.orange_light_8, R.color.orange_light_9,
-                    R.color.orange_light_10
-                )
-            }
-        }
-
-        return colorIds.map { ContextCompat.getColor(context, it) }
-    }
-
-    private val STANDARD_COLORS = mapOf(
-        PaletteType.BLUE to "#1c66e5",
-        PaletteType.GREEN to "#0abf77",
-        PaletteType.RED to "#e54545",
-        PaletteType.ORANGE to "#ff7200"
-    )
-
-    private fun isStandardColor(color: String): Boolean {
-        val inputHsl = hexToHSL(color)
-        return STANDARD_COLORS.values.any { standardColor ->
-            val standardHsl = hexToHSL(standardColor)
-            val dh =
-                min(
-                    abs(inputHsl.first - standardHsl.first),
-                    360 - abs(inputHsl.first - standardHsl.first)
-                )
-            dh < 15 && abs(inputHsl.second - standardHsl.second) < 15 && abs(inputHsl.third - standardHsl.third) < 15
-        }
-    }
-
-    private fun getClosestPaletteType(color: String): PaletteType {
-        val hsl = hexToHSL(color)
-
-        val distances = STANDARD_COLORS.map { (type, baseColor) ->
-            type to colorDistance(hsl, hexToHSL(baseColor))
-        }
-
-        return distances.minByOrNull { it.second }?.first ?: PaletteType.BLUE
-    }
-
-    private val HSL_ADJUSTMENTS = mapOf(
-        "light" to mapOf(
-            1 to Pair(-40.0, 45.0),
-            2 to Pair(-30.0, 35.0),
-            3 to Pair(-20.0, 25.0),
-            4 to Pair(-10.0, 15.0),
-            5 to Pair(-5.0, 5.0),
-            6 to Pair(0.0, 0.0),
-            7 to Pair(5.0, -10.0),
-            8 to Pair(10.0, -20.0),
-            9 to Pair(15.0, -30.0),
-            10 to Pair(20.0, -40.0)
-        ),
-        "dark" to mapOf(
-            1 to Pair(-60.0, -35.0),
-            2 to Pair(-50.0, -25.0),
-            3 to Pair(-40.0, -15.0),
-            4 to Pair(-30.0, -5.0),
-            5 to Pair(-20.0, 5.0),
-            6 to Pair(0.0, 0.0),
-            7 to Pair(-10.0, 15.0),
-            8 to Pair(-20.0, 30.0),
-            9 to Pair(-30.0, 45.0),
-            10 to Pair(-40.0, 60.0)
-        )
-    )
-
-    private fun generateDynamicColorVariations(baseColor: String, theme: String): List<Int> {
-        val variations = mutableListOf<Int>()
-        val adjustments = HSL_ADJUSTMENTS[theme] ?: HSL_ADJUSTMENTS["light"]!!
-        val baseHsl = hexToHSL(baseColor)
-        val saturationFactor = when {
-            baseHsl.second > 70 -> 0.8
-            baseHsl.second < 30 -> 1.2
-            else -> 1.0
-        }
-        val lightnessFactor = when {
-            baseHsl.third > 70 -> 0.8
-            baseHsl.third < 30 -> 1.2
-            else -> 1.0
-        }
-
-        for (i in 1..10) {
-            val adjustment = adjustments[i] ?: Pair(0.0, 0.0)
-            val adjustedS = adjustment.first * saturationFactor
-            val adjustedL = adjustment.second * lightnessFactor
-            variations.add(parseHexColor(adjustColor(baseColor, Pair(adjustedS, adjustedL))))
-        }
-
-        return variations
-    }
-
-    private fun adjustColor(color: String, adjustment: Pair<Double, Double>): String {
-        val hsl = hexToHSL(color)
-        val newS = max(0.0, min(100.0, hsl.second + adjustment.first))
-        val newL = max(0.0, min(100.0, hsl.third + adjustment.second))
-        return hslToHex(hsl.first, newS, newL)
-    }
-
-    private fun colorDistance(
-        c1: Triple<Double, Double, Double>,
-        c2: Triple<Double, Double, Double>
-    ): Double {
-        val dh = min(abs(c1.first - c2.first), 360 - abs(c1.first - c2.first))
-        val ds = c1.second - c2.second
-        val dl = c1.third - c2.third
-        return sqrt(dh * dh + ds * ds + dl * dl)
-    }
-
-    private fun hexToHSL(hex: String): Triple<Double, Double, Double> {
-        val cleanHex = hex.removePrefix("#")
-        val colorInt = cleanHex.toLong(16)
-
-        val r = ((colorInt shr 16) and 0xFF) / 255.0
-        val g = ((colorInt shr 8) and 0xFF) / 255.0
-        val b = (colorInt and 0xFF) / 255.0
-
-        val maxVal = maxOf(r, g, b)
-        val minVal = minOf(r, g, b)
-        var h = 0.0
-        var s = 0.0
-        val l = (maxVal + minVal) / 2.0
-
-        if (maxVal != minVal) {
-            val d = maxVal - minVal
-            s = if (l > 0.5) d / (2.0 - maxVal - minVal) else d / (maxVal + minVal)
-
-            h = when (maxVal) {
-                r -> (g - b) / d + (if (g < b) 6.0 else 0.0)
-                g -> (b - r) / d + 2.0
-                b -> (r - g) / d + 4.0
-                else -> 0.0
-            }
-            h /= 6.0
-        }
-
-        return Triple(h * 360.0, s * 100.0, l * 100.0)
-    }
-
-    private fun hslToHex(h: Double, s: Double, l: Double): String {
-        val hNorm = h / 360.0
-        val sNorm = s / 100.0
-        val lNorm = l / 100.0
-
-        val c = (1.0 - abs(2.0 * lNorm - 1.0)) * sNorm
-        val x = c * (1.0 - abs((hNorm * 6.0) % 2.0 - 1.0))
-        val m = lNorm - c / 2.0
-
-        val (r, g, b) = when ((hNorm * 6.0).toInt()) {
-            0 -> Triple(c, x, 0.0)
-            1 -> Triple(x, c, 0.0)
-            2 -> Triple(0.0, c, x)
-            3 -> Triple(0.0, x, c)
-            4 -> Triple(x, 0.0, c)
-            5 -> Triple(c, 0.0, x)
-            else -> Triple(0.0, 0.0, 0.0)
-        }
-
-        val red = ((r + m) * 255.0).toInt()
-        val green = ((g + m) * 255.0).toInt()
-        val blue = ((b + m) * 255.0).toInt()
-
-        return "#%02X%02X%02X".format(red, green, blue)
-    }
-}

+ 0 - 65
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/theme/utils/ThemePersistUtil.kt

@@ -1,65 +0,0 @@
-package io.trtc.tuikit.atomicx.theme.utils
-
-import android.content.Context
-import android.content.SharedPreferences
-import androidx.core.content.edit
-
-class ThemePersistUtil(context: Context) {
-    
-    private val sharedPreferences: SharedPreferences = 
-        context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
-    
-    companion object {
-        private const val PREFS_NAME = "theme_preferences"
-        private const val KEY_CURRENT_THEME_ID = "current_theme_id"
-        private const val KEY_USER_HAS_MANUALLY_SET_THEME = "user_has_manually_set_theme"
-        private const val KEY_FOLLOW_SYSTEM_APPEARANCE = "follow_system_appearance"
-        private const val KEY_CUSTOM_PRIMARY_COLOR = "custom_primary_color"
-    }
-
-    fun getCurrentThemeId(): String? {
-        return sharedPreferences.getString(KEY_CURRENT_THEME_ID, null)
-    }
-
-    fun setCurrentThemeId(themeId: String) {
-        sharedPreferences.edit {
-            putString(KEY_CURRENT_THEME_ID, themeId)
-        }
-    }
-
-    fun getUserHasManuallySetTheme(): Boolean {
-        return sharedPreferences.getBoolean(KEY_USER_HAS_MANUALLY_SET_THEME, false)
-    }
-
-    fun setUserHasManuallySetTheme(hasSet: Boolean) {
-        sharedPreferences.edit {
-            putBoolean(KEY_USER_HAS_MANUALLY_SET_THEME, hasSet)
-        }
-    }
-
-    fun getFollowSystemAppearance(): Boolean {
-        return sharedPreferences.getBoolean(KEY_FOLLOW_SYSTEM_APPEARANCE, true)
-    }
-
-    fun setFollowSystemAppearance(follow: Boolean) {
-        sharedPreferences.edit {
-            putBoolean(KEY_FOLLOW_SYSTEM_APPEARANCE, follow)
-        }
-    }
-
-    fun getCustomPrimaryColor(): String? {
-        return sharedPreferences.getString(KEY_CUSTOM_PRIMARY_COLOR, null)
-    }
-
-    fun setCustomPrimaryColor(hexColor: String) {
-        sharedPreferences.edit {
-            putString(KEY_CUSTOM_PRIMARY_COLOR, hexColor)
-        }
-    }
-
-    fun clearCustomPrimaryColor() {
-        sharedPreferences.edit {
-            remove(KEY_CUSTOM_PRIMARY_COLOR)
-        }
-    }
-}

+ 0 - 481
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/alertdialog/AtomicAlertDialog.kt

@@ -1,481 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.basicwidget.alertdialog
-
-import android.app.Dialog
-import android.content.Context
-import android.graphics.Typeface
-import android.graphics.drawable.GradientDrawable
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.annotation.ColorInt
-import androidx.core.view.setPadding
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.theme.ThemeStore
-import io.trtc.tuikit.atomicx.theme.tokens.ColorTokens
-import io.trtc.tuikit.atomicx.theme.tokens.DesignTokenSet
-import io.trtc.tuikit.atomicx.widget.basicwidget.popover.AtomicPopover
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.Job
-import android.graphics.Color
-
-class AtomicAlertDialog(
-    private val context: Context,
-    private val gravity: AtomicPopover.PanelGravity = AtomicPopover.PanelGravity.CENTER,
-) {
-    private val DIVIDER_THICKNESS_PX = 1
-
-    private lateinit var rootLayout: LinearLayout
-    private lateinit var config: DialogConfig
-    private var atomicPopover: AtomicPopover? = null
-    private var dialogScope: CoroutineScope? = null
-    private var countdownJob: Job? = null
-    private var cancelButtonView: TextView? = null
-    private var originalCancelText: String = ""
-
-    enum class TextColorPreset {
-        PRIMARY, GREY, BLUE, RED
-    }
-
-    data class DialogConfig(
-        var title: String = "",
-        var content: String? = null,
-        var iconView: View? = null,
-        var autoDismiss: Boolean = false,
-        var countdownDuration: Long = 0,
-        var confirmConfig: ButtonConfig? = null,
-        var cancelConfig: ButtonConfig? = null,
-        val itemList: MutableList<ButtonConfig> = mutableListOf(),
-    )
-
-    class ButtonConfig(
-        var text: String,
-        var type: TextColorPreset,
-        var onClick: ((Dialog) -> Unit)?,
-        var isBold: Boolean,
-    )
-
-    fun init(block: DialogConfig.() -> Unit) {
-        config = DialogConfig().apply(block)
-        val tokens = getCurrentTokens(context)
-        rootLayout = createRootLayout(tokens)
-        renderDialogContent(rootLayout, config, null, tokens)
-    }
-
-    fun show(): Dialog {
-        atomicPopover?.dismiss()
-        dialogScope?.cancel()
-        countdownJob?.cancel()
-        dialogScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
-
-        val panel = AtomicPopover(context, gravity)
-
-        atomicPopover = panel.apply {
-            setContent(rootLayout)
-            setCancelable(config.autoDismiss)
-            setCanceledOnTouchOutside(config.autoDismiss)
-            setOnDismissListener {
-                atomicPopover = null
-                countdownJob?.cancel()
-            }
-        }
-
-        dialogScope?.launch {
-            ThemeStore.shared(context).themeState.collectLatest {
-                if (panel.isShowing) {
-                    val newTokens = getCurrentTokens(context)
-                    updateDialogTheme(rootLayout, config, panel, newTokens)
-                }
-            }
-        }
-
-        atomicPopover?.show()
-        
-        if (config.countdownDuration > 0 && cancelButtonView != null) {
-            startCountdown(config.countdownDuration)
-        }
-        
-        return atomicPopover!!
-    }
-
-    fun dismiss() {
-        dialogScope?.cancel()
-        dialogScope = null
-        countdownJob?.cancel()
-        countdownJob = null
-        atomicPopover?.dismiss()
-        atomicPopover = null
-    }
-
-    fun isShowing(): Boolean {
-        return atomicPopover?.isShowing == true
-    }
-
-    private fun getCurrentTokens(context: Context): DesignTokenSet {
-        return ThemeStore.shared(context).themeState.value.currentTheme.tokens
-    }
-
-    private fun createRootLayout(tokens: DesignTokenSet): LinearLayout {
-        return LinearLayout(context).apply {
-            orientation = LinearLayout.VERTICAL
-        }
-    }
-
-    private fun renderDialogContent(
-        root: LinearLayout,
-        config: DialogConfig,
-        dialog: Dialog?,
-        tokens: DesignTokenSet,
-    ) {
-        root.removeAllViews()
-        renderHeaderSection(root, config, tokens)
-        renderBottomSection(root, config, dialog, tokens)
-    }
-
-    private fun updateDialogTheme(
-        root: LinearLayout,
-        config: DialogConfig,
-        dialog: Dialog,
-        tokens: DesignTokenSet,
-    ) {
-        root.removeAllViews()
-        renderDialogContent(root, config, dialog, tokens)
-    }
-
-    private fun renderHeaderSection(
-        root: LinearLayout,
-        config: DialogConfig,
-        tokens: DesignTokenSet,
-    ) {
-        val headerContainer = createHeaderContainer()
-        root.addView(headerContainer)
-        val titleWrapper = createTitleWrapper()
-        headerContainer.addView(titleWrapper)
-        renderIconIfPresent(titleWrapper, config)
-        renderTitleText(titleWrapper, config, tokens)
-        renderMessageContentIfPresent(headerContainer, config, tokens)
-    }
-
-    private fun createHeaderContainer(): LinearLayout {
-        return LinearLayout(context).apply {
-            orientation = LinearLayout.VERTICAL
-            gravity = Gravity.CENTER
-            val paddingPx = context.resources.getDimensionPixelSize(R.dimen.spacing_24)
-            setPadding(paddingPx, paddingPx, paddingPx, paddingPx)
-        }
-    }
-
-    private fun createTitleWrapper(): LinearLayout {
-        return LinearLayout(context).apply {
-            orientation = LinearLayout.HORIZONTAL
-            gravity = Gravity.CENTER_VERTICAL
-            layoutParams = LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT
-            )
-        }
-    }
-
-    private fun renderIconIfPresent(
-        parent: ViewGroup,
-        config: DialogConfig,
-    ) {
-        val iconView = config.iconView
-        if (iconView != null) {
-            val size = context.resources.getDimensionPixelSize(R.dimen.spacing_20)
-            iconView.layoutParams = LinearLayout.LayoutParams(size, size).apply {
-                marginEnd = context.resources.getDimensionPixelSize(R.dimen.spacing_8)
-            }
-            (iconView.parent as? ViewGroup)?.removeView(iconView)
-            parent.addView(iconView)
-        }
-    }
-
-    private fun renderTitleText(parent: ViewGroup, config: DialogConfig, tokens: DesignTokenSet) {
-        val finalColor = tokens.color.textColorPrimary
-        val finalSize = tokens.font.bold16.size
-        val tvTitle = TextView(context).apply {
-            text = config.title
-            textSize = finalSize
-            typeface = Typeface.DEFAULT
-            setTextColor(finalColor)
-            gravity = Gravity.CENTER
-        }
-        parent.addView(tvTitle)
-    }
-
-    private fun renderMessageContentIfPresent(
-        parent: ViewGroup,
-        config: DialogConfig,
-        tokens: DesignTokenSet,
-    ) {
-        if (!config.content.isNullOrEmpty()) {
-            val finalColor = tokens.color.textColorSecondary
-            val finalSize = tokens.font.bold16.size
-            val tvContent = TextView(context).apply {
-                text = config.content
-                textSize = finalSize
-                setTextColor(finalColor)
-                gravity = Gravity.CENTER
-                val topPaddingPx = context.resources.getDimensionPixelSize(R.dimen.spacing_16)
-                setPadding(0, topPaddingPx, 0, 0)
-            }
-            parent.addView(tvContent)
-        }
-    }
-
-    private fun renderBottomSection(
-        root: LinearLayout,
-        config: DialogConfig,
-        dialog: Dialog?,
-        tokens: DesignTokenSet,
-    ) {
-        if (config.itemList.isNotEmpty()) {
-            renderVerticalListMode(root, config, dialog, tokens)
-        } else if (config.confirmConfig != null || config.cancelConfig != null) {
-            renderStandardButtonMode(root, config, dialog, tokens)
-        }
-    }
-
-    private fun renderVerticalListMode(
-        root: LinearLayout,
-        config: DialogConfig,
-        dialog: Dialog?,
-        tokens: DesignTokenSet,
-    ) {
-        if (config.itemList.isNotEmpty()) {
-            addHorizontalDivider(root, tokens.color)
-        }
-        config.itemList.forEachIndexed { index, itemConfig ->
-            if (index > 0) addHorizontalDivider(root, tokens.color)
-            val isLastItem = index == config.itemList.lastIndex
-            val itemBtn = createButtonView(
-                itemConfig,
-                tokens,
-                isBottomLeftRounded = isLastItem && gravity == AtomicPopover.PanelGravity.CENTER,
-                isBottomRightRounded = isLastItem && gravity == AtomicPopover.PanelGravity.CENTER
-            )
-            val heightPx = context.resources.getDimensionPixelSize(R.dimen.spacing_56)
-            itemBtn.layoutParams = LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                heightPx
-            )
-            itemBtn.setOnClickListener {
-                itemConfig.onClick?.invoke(dialog!!)
-                dialog?.dismiss()
-            }
-            root.addView(itemBtn)
-        }
-    }
-
-    private fun renderStandardButtonMode(
-        root: LinearLayout,
-        config: DialogConfig,
-        dialog: Dialog?,
-        tokens: DesignTokenSet,
-    ) {
-        addHorizontalDivider(root, tokens.color)
-
-        val heightPx = context.resources.getDimensionPixelSize(R.dimen.spacing_56)
-        val buttonContainer = LinearLayout(context).apply {
-            orientation = LinearLayout.HORIZONTAL
-            layoutParams = LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                heightPx
-            )
-        }
-        root.addView(buttonContainer)
-
-        val hasCancel = config.cancelConfig != null
-        val hasConfirm = config.confirmConfig != null
-        val isCenterGravity = gravity == AtomicPopover.PanelGravity.CENTER
-
-        config.cancelConfig?.let { btnConfig ->
-            val isOnlyCancel = !hasConfirm
-            originalCancelText = btnConfig.text
-            val displayText = if (config.countdownDuration > 0) {
-                "$originalCancelText (${config.countdownDuration})"
-            } else {
-                btnConfig.text
-            }
-            val displayConfig = ButtonConfig(displayText, btnConfig.type, btnConfig.onClick, btnConfig.isBold)
-            val btnView = createButtonView(
-                displayConfig,
-                tokens,
-                isBottomLeftRounded = isCenterGravity,
-                isBottomRightRounded = isOnlyCancel && isCenterGravity
-            )
-            cancelButtonView = btnView
-            btnView.setOnClickListener {
-                btnConfig.onClick?.invoke(dialog ?: return@setOnClickListener)
-                dialog?.dismiss()
-            }
-            buttonContainer.addView(
-                btnView,
-                LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
-            )
-        }
-
-        if (hasCancel && hasConfirm) {
-            addVerticalDivider(buttonContainer, tokens.color)
-        }
-
-        config.confirmConfig?.let { btnConfig ->
-            val isOnlyConfirm = !hasCancel
-            val btnView = createButtonView(
-                btnConfig,
-                tokens,
-                isBottomLeftRounded = isOnlyConfirm && isCenterGravity,
-                isBottomRightRounded = isCenterGravity
-            )
-            btnView.setOnClickListener {
-                btnConfig.onClick?.invoke(dialog ?: return@setOnClickListener)
-                dialog?.dismiss()
-            }
-            buttonContainer.addView(
-                btnView,
-                LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
-            )
-        }
-    }
-
-    private fun addHorizontalDivider(root: ViewGroup, colorTokens: ColorTokens) {
-        val view = View(context).apply {
-            layoutParams =
-                LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DIVIDER_THICKNESS_PX)
-            setBackgroundColor(colorTokens.strokeColorSecondary)
-        }
-        root.addView(view)
-    }
-
-    private fun addVerticalDivider(root: ViewGroup, colorTokens: ColorTokens) {
-        val view = View(context).apply {
-            layoutParams =
-                LinearLayout.LayoutParams(DIVIDER_THICKNESS_PX, ViewGroup.LayoutParams.MATCH_PARENT)
-            setBackgroundColor(colorTokens.strokeColorSecondary)
-        }
-        root.addView(view)
-    }
-
-    private fun createButtonView(
-        config: ButtonConfig,
-        tokens: DesignTokenSet,
-        isBottomLeftRounded: Boolean = false,
-        isBottomRightRounded: Boolean = false,
-    ): TextView {
-        val finalSize = tokens.font.bold16.size
-        val buttonView = TextView(context).apply {
-            text = config.text
-            textSize = finalSize
-            gravity = Gravity.CENTER
-            val finalColor = resolveButtonTextColor(config.type, tokens.color)
-            setTextColor(finalColor)
-            typeface = if (config.isBold) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
-        }
-
-        buttonView.setBackgroundColor(Color.TRANSPARENT)
-
-        if (isBottomLeftRounded || isBottomRightRounded) {
-            val cornerRadiusPx = context.resources.getDimension(R.dimen.radius_20)
-            val br = if (isBottomRightRounded) cornerRadiusPx else 0f
-            val bl = if (isBottomLeftRounded) cornerRadiusPx else 0f
-
-            val radii = floatArrayOf(
-                0f, 0f,
-                0f, 0f,
-                br, br,
-                bl, bl
-            )
-
-            val drawable = GradientDrawable().apply {
-                setColor(Color.TRANSPARENT)
-                cornerRadii = radii
-            }
-            buttonView.background = drawable
-        }
-
-        return buttonView
-    }
-
-    @ColorInt
-    private fun resolveButtonTextColor(type: TextColorPreset, colorTokens: ColorTokens): Int {
-        return when (type) {
-            TextColorPreset.RED -> colorTokens.textColorError
-            TextColorPreset.BLUE -> colorTokens.textColorLink
-            TextColorPreset.PRIMARY -> colorTokens.textColorPrimary
-            TextColorPreset.GREY -> colorTokens.textColorSecondary
-        }
-    }
-
-    private fun startCountdown(durationSeconds: Long) {
-        countdownJob?.cancel()
-        countdownJob = dialogScope?.launch {
-            var remaining = durationSeconds
-            while (remaining >= 1 && atomicPopover?.isShowing == true) {
-                cancelButtonView?.text = "$originalCancelText ($remaining)"
-                delay(1000)
-                remaining--
-            }
-            if (atomicPopover?.isShowing == true) {
-                atomicPopover?.dismiss()
-            }
-        }
-    }
-}
-
-fun AtomicAlertDialog.DialogConfig.init(
-    title: String,
-    content: String? = null,
-    iconView: View? = null,
-    autoDismiss: Boolean = false,
-) {
-    this.title = title
-    this.content = content
-    this.iconView = iconView
-    this.autoDismiss = autoDismiss
-}
-
-fun AtomicAlertDialog.DialogConfig.addItem(
-    text: String,
-    type: AtomicAlertDialog.TextColorPreset = AtomicAlertDialog.TextColorPreset.GREY,
-    isBold: Boolean = false,
-    onClick: ((Dialog) -> Unit)? = null,
-) {
-    val config = AtomicAlertDialog.ButtonConfig(text, type, onClick, isBold)
-    itemList.add(config)
-}
-
-fun AtomicAlertDialog.DialogConfig.items(
-    items: List<Pair<String, AtomicAlertDialog.TextColorPreset>>,
-    isBold: Boolean = false,
-    onClick: (Dialog, Int, String) -> Unit,
-) {
-    items.forEachIndexed { index, (text, type) ->
-        addItem(text, type, isBold) { dialog -> onClick(dialog, index, text) }
-    }
-}
-
-fun AtomicAlertDialog.DialogConfig.confirmButton(
-    text: String,
-    type: AtomicAlertDialog.TextColorPreset = AtomicAlertDialog.TextColorPreset.BLUE,
-    isBold: Boolean = false,
-    onClick: ((Dialog) -> Unit)? = null,
-) {
-    this.confirmConfig = AtomicAlertDialog.ButtonConfig(text, type, onClick, isBold)
-}
-
-fun AtomicAlertDialog.DialogConfig.cancelButton(
-    text: String,
-    type: AtomicAlertDialog.TextColorPreset = AtomicAlertDialog.TextColorPreset.GREY,
-    isBold: Boolean = false,
-    onClick: ((Dialog) -> Unit)? = null,
-) {
-    this.cancelConfig = AtomicAlertDialog.ButtonConfig(text, type, onClick, isBold)
-}

+ 0 - 124
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/alertdialog/README.md

@@ -1,124 +0,0 @@
-# AtomicAlertDialog 通用弹窗组件
-
-`AtomicAlertDialog` 是 AtomicX Android UIKit 中的标准弹窗组件,基于 Kotlin DSL 设计,支持链式配置,默认提供确认/取消按钮、列表项等多种呈现方式。内部复用 `AtomicPopover` 的容器能力,可选择居中或底部弹出(通过构造参数传入 `AtomicPopover.PanelGravity`)。
-
-## 文件结构
-
-```
-basicwidget/alertdialog/
-├── AtomicAlertDialog.kt   # 组件实现(DSL + 渲染逻辑)
-└── README.md              # 本文档
-```
-
-## 快速开始
-
-使用方式:**先创建实例并在 `init {}` 中配置内容,再调用 `show()` 展示**。
-
-### 1. 基础确认/取消弹窗
-
-```kotlin
-val dialog = AtomicAlertDialog(context)
-dialog.init {
-    init(
-        title = "退出登录",
-        content = "确定要退出当前账号吗?",
-        autoDismiss = true
-    )
-
-    cancelButton(text = "取消")
-
-    confirmButton(
-        text = "确定",
-        type = AtomicAlertDialog.TextColorPreset.RED
-    ) { dlg ->
-        logout()
-    }
-}
-dialog.show()
-```
-
-### 2. 列表选择弹窗
-
-```kotlin
-val dialog = AtomicAlertDialog(context)
-dialog.init {
-    init(title = "设置头像")
-
-    val options = listOf(
-        "拍摄" to AtomicAlertDialog.TextColorPreset.PRIMARY,
-        "从相册选择" to AtomicAlertDialog.TextColorPreset.PRIMARY,
-        "删除头像" to AtomicAlertDialog.TextColorPreset.RED
-    )
-
-    items(options) { dlg, index, text ->
-        when (index) {
-            0 -> takePhoto()
-            1 -> pickImage()
-            2 -> deleteAvatar()
-        }
-    }
-}
-dialog.show()
-```
-
-### 3. 带自定义图标 View 的弹窗
-
-```kotlin
-val dialog = AtomicAlertDialog(context)
-val avatarView = AvatarView(context).apply { loadUrl("https://example.com/avatar.png") }
-
-dialog.init {
-    init(
-        title = "账号风险",
-        content = "检测到异常登录行为,请及时处理",
-        iconView = avatarView
-    )
-
-    confirmButton(text = "去处理")
-    cancelButton(text = "忽略", type = AtomicAlertDialog.TextColorPreset.GREY)
-}
-dialog.show()
-```
-
-## 核心 API(DialogConfig DSL)
-
-`init {}` 闭包中的 `DialogConfig` 支持以下配置:
-
-### 基础配置 `init(...)`
-
-| 参数 | 类型 | 说明 | 默认值 |
-| :--- | :--- | :--- | :--- |
-| `title` | `String` | 弹窗标题 | 必填 |
-| `content` | `String?` | 正文内容 | `null` |
-| `iconView` | `View?` | 自定义图标/头像 | `null` |
-| `autoDismiss` | `Boolean` | 点击按钮或列表项后是否自动关闭 | `true` |
-
-### 按钮配置
-
-- `confirmButton(text, type, isBold, onClick)`:主操作按钮(默认蓝色、加粗)
-- `cancelButton(text, type, isBold, onClick)`:次操作按钮(默认灰色)
-
-### 列表项
-
-- `addItem(text, type, isBold, onClick)`:添加单个列表项
-- `items(list, isBold, onClick)`:批量添加列表项,回调提供 `(dialog, index, text)`
-
-### 颜色预设 `TextColorPreset`
-
-- `PRIMARY`:主文本色
-- `GREY`:次级文本色
-- `BLUE`:强调/链接色
-- `RED`:危险/警示操作
-
-## 动态主题与布局
-
-- 内建监听 `ThemeStore`,当主题发生变化时自动刷新背景、文字、分割线等样式。
-- 通过构造参数 `AtomicAlertDialog(context, gravity = AtomicPopover.PanelGravity.CENTER)` 可切换弹窗出现的位置(例如居中、底部)。
-- 与 `AtomicPopover` 共享容器能力,确保圆角、遮罩、动画等表现一致。
-
-## 注意事项
-
-1. **Context**:建议传入 `Activity` Context;如需在 `Fragment` 中使用请确保生命周期安全。
-2. **Icon View**:传入的 `iconView` 不能同时挂在其他父容器下,内部会自动移除再添加。
-3. **自动关闭**:`autoDismiss = true` 时,`confirmButton`、`cancelButton`、`itemList` 的点击都会在回调后自动关闭;若需要手动控制请设置为 `false` 并在回调中自行 `dismiss()`。
-4. **重复 show**:再次 `show()` 前会自动销毁上一次的 `AtomicPopover`,无需手动清理。

+ 0 - 483
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/avatar/AtomicAvatar.kt

@@ -1,483 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.basicwidget.avatar
-
-import android.content.Context
-import android.content.res.TypedArray
-import android.graphics.Canvas
-import android.graphics.Outline
-import android.graphics.Paint
-import android.graphics.RectF
-import android.graphics.Typeface
-import android.graphics.drawable.Drawable
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewOutlineProvider
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.annotation.DrawableRes
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.theme.ThemeStore
-import io.trtc.tuikit.atomicx.widget.utils.DisplayUtil.dp2px
-import io.trtc.tuikit.atomicx.widget.utils.ImageLoader
-import kotlin.math.ceil
-
-class AtomicAvatar @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0
-) : ViewGroup(context, attrs, defStyleAttr) {
-
-    companion object {
-        private const val SQRT2_OVER_2 = 0.70710678f
-        private const val BADGE_EXTRA_PADDING_DP = 2f
-    }
-
-    sealed class AvatarContent {
-        data class URL(val url: String, @DrawableRes val placeImage: Int) : AvatarContent()
-        data class Text(val name: String) : AvatarContent()
-        data class Icon(val drawable: Drawable) : AvatarContent()
-    }
-
-    sealed class AvatarBadge {
-        object None : AvatarBadge()
-        object Dot : AvatarBadge()
-        data class Text(val text: String) : AvatarBadge()
-    }
-
-    enum class AvatarSize(
-        val sizeDp: Float,
-        val textSizeSp: Float,
-        val borderRadiusDp: Float
-    ) {
-        XXS(16f, 10f, 4f),
-        XS(24f, 12f, 4f),
-        S(32f, 14f, 4f),
-        M(40f, 16f, 4f),
-        L(48f, 18f, 8f),
-        XL(64f, 28f, 12f),
-        XXL(96f, 36f, 12f)
-    }
-
-    enum class AvatarShape {
-        Round,
-        RoundRectangle,
-        Rectangle
-    }
-
-    private val themeStore = ThemeStore.shared(context)
-    private val colors get() = themeStore.themeState.value.currentTheme.tokens.color
-
-    private val avatarContainer = FrameLayout(context).apply {
-        clipChildren = true
-    }
-
-    private val imageView: ImageView = ImageView(context).apply {
-        scaleType = ImageView.ScaleType.FIT_CENTER
-        visibility = GONE
-    }
-
-    private val textView: TextView = TextView(context).apply {
-        gravity = Gravity.CENTER
-        visibility = GONE
-        setTextColor(colors.textColorPrimary)
-        maxLines = 1
-        ellipsize = android.text.TextUtils.TruncateAt.END
-    }
-
-    private var badgeView: Badge? = null
-
-    private var avatarSize: AvatarSize = AvatarSize.M
-    private var avatarShape: AvatarShape = AvatarShape.Round
-    private var avatarBadge: AvatarBadge = AvatarBadge.None
-
-    private var actualAvatarSizePx: Int = 0
-
-    init {
-        avatarContainer.addView(
-            imageView,
-            LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
-        )
-        avatarContainer.addView(
-            textView,
-            LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
-        )
-
-        addView(avatarContainer)
-
-        attrs?.let { parseAttributes(it) }
-
-        setSize(avatarSize)
-        applyShape()
-        applyBackgroundColor()
-    }
-
-    private fun parseAttributes(attrs: AttributeSet) {
-        val typedArray: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.AtomicAvatar)
-        
-        try {
-            val sizeOrdinal = typedArray.getInt(R.styleable.AtomicAvatar_avatarSize, AvatarSize.M.ordinal)
-            avatarSize = AvatarSize.values()[sizeOrdinal]
-            
-            val shapeOrdinal = typedArray.getInt(R.styleable.AtomicAvatar_avatarShape, AvatarShape.Round.ordinal)
-            avatarShape = AvatarShape.values()[shapeOrdinal]
-            
-        } finally {
-            typedArray.recycle()
-        }
-    }
-
-    fun setContent(content: AvatarContent) {
-        when (content) {
-            is AvatarContent.URL -> setImageContent(content.url, content.placeImage)
-            is AvatarContent.Text -> setTextContent(content.name)
-            is AvatarContent.Icon -> setIconContent(content.drawable)
-        }
-    }
-
-    fun setSize(size: AvatarSize) {
-        avatarSize = size
-        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, size.textSizeSp.toFloat())
-
-        applyShape()
-        requestLayout()
-        invalidate()
-    }
-
-    fun setShape(shape: AvatarShape) {
-        avatarShape = shape
-        applyShape()
-    }
-
-    fun setBadge(badge: AvatarBadge) {
-        avatarBadge = badge
-
-        badgeView?.let { removeView(it) }
-        badgeView = null
-
-        if (badge !is AvatarBadge.None) {
-            badgeView = Badge(context).apply {
-                when (badge) {
-                    is AvatarBadge.Dot -> setType(Badge.BadgeType.Dot)
-                    is AvatarBadge.Text -> {
-                        setText(badge.text)
-                        setType(Badge.BadgeType.Text)
-                    }
-
-                    else -> {}
-                }
-            }
-            addView(badgeView)
-        }
-
-        requestLayout()
-    }
-
-    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
-        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
-        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
-        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
-
-        val newAvatarSizePx = when {
-            widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY -> {
-                kotlin.math.min(widthSize, heightSize)
-            }
-
-            widthMode == MeasureSpec.EXACTLY -> {
-                widthSize
-            }
-
-            heightMode == MeasureSpec.EXACTLY -> {
-                heightSize
-            }
-
-            else -> {
-                dp2px(context, avatarSize.sizeDp)
-            }
-        }
-
-        if (actualAvatarSizePx != newAvatarSizePx) {
-            actualAvatarSizePx = newAvatarSizePx
-            applyShape()
-        }
-
-        val exactSpec = MeasureSpec.makeMeasureSpec(actualAvatarSizePx, MeasureSpec.EXACTLY)
-        avatarContainer.measure(exactSpec, exactSpec)
-
-        var containerWidth = actualAvatarSizePx
-        var containerHeight = actualAvatarSizePx
-
-        badgeView?.let { badge ->
-            badge.measure(
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
-            )
-            val badgeW = badge.measuredWidth
-            val badgeH = badge.measuredHeight
-
-            val (centerX, centerY) = calculateBadgeCenter(actualAvatarSizePx)
-
-            val badgeLeft = (centerX - badgeW / 2).toInt()
-            val badgeTop = (centerY - badgeH / 2).toInt()
-
-            val badgeActualRight = badgeLeft + badgeW
-            if (badgeActualRight > actualAvatarSizePx) {
-                containerWidth = badgeActualRight
-            }
-
-            val topOverflow = if (badgeTop < 0) -badgeTop else 0
-            containerHeight = actualAvatarSizePx + topOverflow
-        }
-
-        setMeasuredDimension(
-            resolveSize(containerWidth, widthMeasureSpec),
-            resolveSize(containerHeight, heightMeasureSpec)
-        )
-    }
-
-    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
-        val sizePx = actualAvatarSizePx
-        val (centerX, centerY) = calculateBadgeCenter(sizePx)
-
-        var topOffset = 0
-
-        badgeView?.let { badge ->
-            val badgeH = badge.measuredHeight
-            val rawBadgeTop = (centerY - badgeH / 2).toInt()
-            if (rawBadgeTop < 0) {
-                topOffset = -rawBadgeTop
-            }
-        }
-
-        avatarContainer.layout(0, topOffset, sizePx, topOffset + sizePx)
-
-        badgeView?.let { badge ->
-            val badgeW = badge.measuredWidth
-            val badgeH = badge.measuredHeight
-
-            val badgeLeft = (centerX - badgeW / 2).toInt()
-            val rawBadgeTop = (centerY - badgeH / 2).toInt()
-            val badgeTop = rawBadgeTop + topOffset
-
-            badge.layout(badgeLeft, badgeTop, badgeLeft + badgeW, badgeTop + badgeH)
-        }
-    }
-
-    private fun calculateBadgeCenter(avatarSizePx: Int): Pair<Float, Float> {
-        return when (avatarShape) {
-            AvatarShape.Round -> {
-                val radius = avatarSizePx / 2f
-                val extraPadding = dp2px(context, BADGE_EXTRA_PADDING_DP).toFloat()
-                val offset = (radius + extraPadding) * SQRT2_OVER_2
-                (radius + offset) to (radius - offset)
-            }
-
-            AvatarShape.RoundRectangle -> {
-                val borderRadiusPx = dp2px(context, avatarSize.borderRadiusDp).toFloat()
-                if (avatarBadge is AvatarBadge.Dot) {
-                    val offset = borderRadiusPx * (1 - SQRT2_OVER_2)
-                    (avatarSizePx - offset) to offset
-                } else {
-                    avatarSizePx.toFloat() to 0f
-                }
-            }
-
-            AvatarShape.Rectangle -> {
-                avatarSizePx.toFloat() to 0f
-            }
-        }
-    }
-
-    private fun setImageContent(url: String, @DrawableRes placeImage: Int) {
-        imageView.visibility = VISIBLE
-        textView.visibility = GONE
-        ImageLoader.load(context, imageView, url, placeImage)
-    }
-
-    private fun setTextContent(name: String) {
-        imageView.visibility = GONE
-        textView.visibility = VISIBLE
-        textView.text = name
-    }
-
-    private fun setIconContent(drawable: Drawable) {
-        imageView.visibility = VISIBLE
-        textView.visibility = GONE
-        imageView.setImageDrawable(drawable)
-    }
-
-    private fun applyShape() {
-        val sizePx = if (actualAvatarSizePx > 0) {
-            actualAvatarSizePx.toFloat()
-        } else {
-            dp2px(context, avatarSize.sizeDp).toFloat()
-        }
-
-        val clipProvider = when (avatarShape) {
-            AvatarShape.Round -> createRoundClipDrawable(sizePx / 2)
-            AvatarShape.RoundRectangle -> createRoundClipDrawable(
-                dp2px(
-                    context,
-                    avatarSize.borderRadiusDp
-                ).toFloat()
-            )
-            AvatarShape.Rectangle -> null
-        }
-
-        avatarContainer.clipToOutline = clipProvider != null
-        avatarContainer.outlineProvider = clipProvider
-    }
-
-    private fun applyBackgroundColor() {
-        avatarContainer.setBackgroundColor(colors.bgColorAvatar)
-    }
-
-    private fun createRoundClipDrawable(radius: Float): ViewOutlineProvider {
-        return object : ViewOutlineProvider() {
-            override fun getOutline(view: View, outline: Outline) {
-                val actualRadius = when (avatarShape) {
-                    AvatarShape.Round -> view.width / 2f
-                    AvatarShape.RoundRectangle -> dp2px(context, avatarSize.borderRadiusDp).toFloat()
-                    else -> radius
-                }
-                outline.setRoundRect(0, 0, view.width, view.height, actualRadius)
-            }
-        }
-    }
-}
-
-private class Badge @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0
-) : View(context, attrs, defStyleAttr) {
-
-    companion object {
-        private const val DOT_SIZE_DP = 8f
-        private const val TEXT_HEIGHT_DP = 16f
-        private const val TEXT_HORIZONTAL_PADDING_DP = 5f
-        private const val TEXT_CORNER_RADIUS_DP = 8f
-        private const val TEXT_SIZE_SP = 12f
-    }
-
-    enum class BadgeType {
-        Dot,
-        Text
-    }
-
-    private val themeStore = ThemeStore.shared(context)
-
-    private var backgroundColor: Int = 0
-    private var textColor: Int = 0
-
-    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
-        style = Paint.Style.FILL
-    }
-
-    private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
-        textAlign = Paint.Align.CENTER
-        textSize = TypedValue.applyDimension(
-            TypedValue.COMPLEX_UNIT_SP,
-            TEXT_SIZE_SP,
-            resources.displayMetrics
-        )
-        typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
-    }
-
-    private var badgeType: BadgeType = BadgeType.Text
-    private var badgeText: String = ""
-    private val rectF = RectF()
-
-    private var cachedDotSize: Int = 0
-    private var cachedTextHeight: Int = 0
-    private var cachedTextPadding: Int = 0
-    private var cachedCornerRadius: Float = 0f
-
-    init {
-        cachedDotSize = dp2px(context, DOT_SIZE_DP)
-        cachedTextHeight = dp2px(context, TEXT_HEIGHT_DP)
-        cachedTextPadding = dp2px(context, TEXT_HORIZONTAL_PADDING_DP * 2)
-        cachedCornerRadius = dp2px(context, TEXT_CORNER_RADIUS_DP).toFloat()
-
-        updateColors()
-    }
-
-    fun setType(type: BadgeType) {
-        if (badgeType != type) {
-            badgeType = type
-            requestLayout()
-            invalidate()
-        }
-    }
-
-    fun setText(text: String) {
-        if (badgeText != text) {
-            badgeText = text
-            requestLayout()
-            invalidate()
-        }
-    }
-
-    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-        when (badgeType) {
-            BadgeType.Dot -> {
-                setMeasuredDimension(cachedDotSize, cachedDotSize)
-            }
-
-            BadgeType.Text -> {
-                if (badgeText.isEmpty()) {
-                    setMeasuredDimension(0, 0)
-                } else {
-                    val textWidth = textPaint.measureText(badgeText)
-                    val width = (ceil(textWidth) + cachedTextPadding).toInt()
-                    setMeasuredDimension(width, cachedTextHeight)
-                }
-            }
-        }
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        super.onDraw(canvas)
-        when (badgeType) {
-            BadgeType.Dot -> drawDot(canvas)
-            BadgeType.Text -> {
-                if (badgeText.isNotEmpty()) {
-                    drawTextBadge(canvas)
-                }
-            }
-        }
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        updateColors()
-    }
-
-    private fun drawDot(canvas: Canvas) {
-        backgroundPaint.color = backgroundColor
-        val centerX = width / 2f
-        val centerY = height / 2f
-        val radius = width / 2f
-        canvas.drawCircle(centerX, centerY, radius, backgroundPaint)
-    }
-
-    private fun drawTextBadge(canvas: Canvas) {
-        backgroundPaint.color = backgroundColor
-        rectF.set(0f, 0f, width.toFloat(), height.toFloat())
-        canvas.drawRoundRect(rectF, cachedCornerRadius, cachedCornerRadius, backgroundPaint)
-
-        textPaint.color = textColor
-        val centerX = width / 2f
-        val textY = height / 2f - (textPaint.descent() + textPaint.ascent()) / 2
-        canvas.drawText(badgeText, centerX, textY, textPaint)
-    }
-
-    private fun updateColors() {
-        val colors = themeStore.themeState.value.currentTheme.tokens.color
-        backgroundColor = colors.textColorError
-        textColor = colors.textColorButton
-    }
-}

+ 0 - 460
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/avatar/README.md

@@ -1,460 +0,0 @@
-# AtomicAvatar 头像组件
-
-AtomicAvatar 是一个高度可定制的 Android 头像组件,基于 Kotlin 设计,支持 **XML 布局配置**和**代码动态设置**两种使用方式。它遵循 AtomicX 设计系统,提供了多种尺寸、形状和徽章样式,适用于用户头像、群组头像、图标展示等多种场景。
-
-## 文件结构
-
-```
-avatar/
-├── AtomicAvatar.kt              # 头像组件核心实现
-└── README.md                    # 本文件
-```
-
-## 快速开始
-
-### 方式一:XML 布局配置(推荐)
-
-直接在 XML 中配置所有属性,无需代码设置:
-
-```xml
-<!-- 用户头像:URL + 圆形 + 中等尺寸 + 红点徽章 -->
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:id="@+id/user_avatar"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    app:avatarSize="m"
-    app:avatarShape="round"
-    app:avatarUrl="https://example.com/avatar.jpg"
-    app:avatarPlaceholder="@drawable/ic_default_avatar"
-    app:badgeType="dot" />
-```
-
-### 方式二:代码动态设置
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:id="@+id/avatar"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content" />
-```
-
-在代码中配置:
-
-```kotlin
-val avatar = findViewById<AtomicAvatar>(R.id.avatar)
-
-// 设置头像内容(图片URL)
-avatar.setContent(
-    AtomicAvatar.AvatarContent.URL(
-        url = "https://example.com/avatar.jpg",
-        placeImage = R.drawable.default_avatar
-    )
-)
-
-// 设置尺寸
-avatar.setSize(AtomicAvatar.AvatarSize.L)
-
-// 设置形状
-avatar.setShape(AtomicAvatar.AvatarShape.Round)
-
-// 设置徽章
-avatar.setBadge(AtomicAvatar.AvatarBadge.Dot)
-```
-
-## XML 属性说明
-
-### 可用的 XML 属性
-
-| 属性名 | 类型 | 说明 | 可选值 |
-|--------|------|------|--------|
-| `app:avatarSize` | enum | 头像尺寸 | xxs/xs/s/m(默认)/l/xl/xxl |
-| `app:avatarShape` | enum | 头像形状 | round(默认)/roundRectangle/rectangle |
-| `app:avatarUrl` | string | 图片URL | 任意URL字符串 |
-| `app:avatarPlaceholder` | reference | 占位图 | @drawable/xxx |
-| `app:avatarText` | string | 文本内容 | 任意文本 |
-| `app:avatarIcon` | reference | 图标资源 | @drawable/xxx |
-| `app:badgeType` | enum | 徽章类型 | none(默认)/dot/text |
-| `app:badgeText` | string | 徽章文本 | 任意文本(配合badgeType="text"使用)|
-
-### 内容优先级
-
-当同时设置多个内容属性时,优先级为:**avatarUrl > avatarIcon > avatarText**
-
-### 完整 XML 示例
-
-#### 示例 1:用户头像(URL + 圆形 + 中等尺寸 + 红点)
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:id="@+id/user_avatar"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    app:avatarSize="m"
-    app:avatarShape="round"
-    app:avatarUrl="https://example.com/user_avatar.jpg"
-    app:avatarPlaceholder="@drawable/ic_default_avatar"
-    app:badgeType="dot" />
-```
-
-#### 示例 2:群组头像(文本 + 圆角矩形 + 大尺寸 + 数字徽章)
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:id="@+id/group_avatar"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    app:avatarSize="l"
-    app:avatarShape="roundRectangle"
-    app:avatarText="开发组"
-    app:badgeType="text"
-    app:badgeText="5" />
-```
-
-#### 示例 3:系统图标(图标 + 矩形 + 小尺寸)
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:id="@+id/system_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    app:avatarSize="s"
-    app:avatarShape="rectangle"
-    app:avatarIcon="@drawable/ic_notification" />
-```
-
-#### 示例 4:XXS 超小头像(带数字徽章)
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:id="@+id/mini_avatar"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    app:avatarSize="xxs"
-    app:avatarShape="round"
-    app:avatarText="A"
-    app:badgeType="text"
-    app:badgeText="99+" />
-```
-
-## 代码使用示例
-
-如果需要动态修改头像属性,可以使用代码方式:
-
-### 1. 基础图片头像
-
-```kotlin
-val avatar = findViewById<AtomicAvatar>(R.id.avatar)
-
-avatar.setContent(
-    AtomicAvatar.AvatarContent.URL(
-        url = "https://example.com/avatar.jpg",
-        placeImage = R.drawable.default_avatar
-    )
-)
-avatar.setSize(AtomicAvatar.AvatarSize.L)
-avatar.setShape(AtomicAvatar.AvatarShape.Round)
-```
-
-### 2. 文字头像
-
-适用于没有头像图片时,显示文本内容。
-
-```kotlin
-val avatar = findViewById<AtomicAvatar>(R.id.avatar)
-
-avatar.setContent(
-    AtomicAvatar.AvatarContent.Text(name = "张")
-)
-avatar.setSize(AtomicAvatar.AvatarSize.M)
-avatar.setShape(AtomicAvatar.AvatarShape.Round)
-```
-
-### 3. 图标头像
-
-使用 Drawable 资源作为头像内容。
-
-```kotlin
-val avatar = findViewById<AtomicAvatar>(R.id.avatar)
-
-val drawable = ContextCompat.getDrawable(context, R.drawable.ic_group)
-avatar.setContent(
-    AtomicAvatar.AvatarContent.Icon(drawable = drawable!!)
-)
-avatar.setSize(AtomicAvatar.AvatarSize.XL)
-avatar.setShape(AtomicAvatar.AvatarShape.RoundRectangle)
-```
-
-### 4. 带徽章的头像
-
-显示在线状态、未读消息数等信息。
-
-```kotlin
-val avatar = findViewById<AtomicAvatar>(R.id.avatar)
-
-// 设置头像内容
-avatar.setContent(
-    AtomicAvatar.AvatarContent.URL(
-        url = userAvatarUrl,
-        placeImage = R.drawable.default_avatar
-    )
-)
-
-// 添加红点徽章(在线状态)
-avatar.setBadge(AtomicAvatar.AvatarBadge.Dot)
-
-// 或添加数字徽章(未读消息数)
-avatar.setBadge(AtomicAvatar.AvatarBadge.Text("99+"))
-
-avatar.setSize(AtomicAvatar.AvatarSize.L)
-avatar.setShape(AtomicAvatar.AvatarShape.Round)
-```
-
-### 5. 带点击事件的头像
-
-```kotlin
-val avatar = findViewById<AtomicAvatar>(R.id.avatar)
-
-avatar.setContent(
-    AtomicAvatar.AvatarContent.URL(url = userAvatarUrl, placeImage = R.drawable.default_avatar)
-)
-
-// 设置点击监听器
-avatar.setOnAvatarClickListener {
-    // 跳转到用户详情页
-    navigateToUserProfile(userId)
-}
-```
-
-## 核心 API
-
-### AtomicAvatar.AvatarContent(头像内容)
-
-头像组件支持三种内容类型:
-
-#### 1. URL - 图片头像
-```kotlin
-AtomicAvatar.AvatarContent.URL(
-    url: String,              // 图片URL
-    placeImage: Int           // 占位图资源ID(@DrawableRes)
-)
-```
-
-#### 2. Text - 文字头像
-```kotlin
-AtomicAvatar.AvatarContent.Text(
-    name: String              // 显示的文本内容
-)
-```
-
-#### 3. Icon - 图标头像
-```kotlin
-AtomicAvatar.AvatarContent.Icon(
-    drawable: Drawable        // Drawable 资源
-)
-```
-
-### AtomicAvatar.AvatarSize(头像尺寸)
-
-提供 7 种预定义尺寸规格:
-
-| 尺寸 | 大小 (dp) | 文字大小 (sp) | 圆角半径 (dp) | 使用场景 |
-| :--- | :--- | :--- | :--- | :--- |
-| `XXS` | 16 | 10 | 4 | 超小标签、迷你图标 |
-| `XS` | 24 | 12 | 4 | 小型头像列表、标签 |
-| `S` | 32 | 14 | 4 | 评论区、聊天气泡 |
-| `M` | 40 | 16 | 4 | 会话列表(默认) |
-| `L` | 48 | 18 | 8 | 联系人列表 |
-| `XL` | 64 | 28 | 12 | 个人中心、用户卡片 |
-| `XXL` | 96 | 36 | 12 | 个人主页头部 |
-
-**XML 使用:**
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    app:avatarSize="l" />
-```
-
-**代码使用:**
-```kotlin
-avatar.setSize(AtomicAvatar.AvatarSize.XL)
-```
-
-### AtomicAvatar.AvatarShape(头像形状)
-
-支持 3 种形状样式:
-
-| 形状 | 说明 | 使用场景 |
-| :--- | :--- | :--- |
-| `Round` | 圆形 | 个人头像(最常用) |
-| `RoundRectangle` | 圆角矩形 | 群组头像、品牌Logo |
-| `Rectangle` | 直角矩形 | 封面图、卡片图 |
-
-**XML 使用:**
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    app:avatarShape="roundRectangle" />
-```
-
-**代码使用:**
-```kotlin
-avatar.setShape(AtomicAvatar.AvatarShape.Round)
-```
-
-### AtomicAvatar.AvatarBadge(徽章)
-
-支持 3 种徽章类型:
-
-#### 1. None - 无徽章
-
-**XML 使用:**
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    app:badgeType="none" />
-```
-
-**代码使用:**
-```kotlin
-avatar.setBadge(AtomicAvatar.AvatarBadge.None)
-```
-
-#### 2. Dot - 红点徽章
-
-适用于在线状态、新消息提示。
-
-**XML 使用:**
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    app:badgeType="dot" />
-```
-
-**代码使用:**
-```kotlin
-avatar.setBadge(AtomicAvatar.AvatarBadge.Dot)
-```
-
-#### 3. Text - 文本徽章
-
-适用于未读消息数、等级标识等。
-
-**XML 使用:**
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    app:badgeType="text"
-    app:badgeText="99+" />
-```
-
-**代码使用:**
-```kotlin
-avatar.setBadge(AtomicAvatar.AvatarBadge.Text("99+"))
-```
-
-### 徽章位置规则
-
-徽章会根据头像形状自动计算最佳位置:
-
-- **圆形头像**:徽章位于右上角 45° 位置(对角线方向)
-- **圆角矩形**:
-  - Dot 徽章:位于圆角内切位置
-  - Text 徽章:位于右上角顶点
-- **矩形**:徽章位于右上角顶点
-
-
-## 动态主题 (Dynamic Theming)
-
-`AtomicAvatar` 内置了对 `ThemeStore` 的支持。头像的背景色、文字颜色等会自动响应主题变化(例如切换深色模式),无需手动刷新。
-
-## 布局尺寸规则
-
-`AtomicAvatar` 支持灵活的尺寸设置方式,遵循 Android 标准的测量规范:
-
-### 1. 使用 `wrap_content`(推荐)
-
-由 `avatarSize` 属性决定实际尺寸:
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    app:avatarSize="l" />
-<!-- 实际显示: 48dp × 48dp -->
-```
-
-### 2. 明确指定尺寸
-
-直接控制头像大小,忽略 `avatarSize` 属性:
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:layout_width="60dp"
-    android:layout_height="60dp"
-    app:avatarSize="m" />
-<!-- 实际显示: 60dp × 60dp,avatarSize 被覆盖 -->
-```
-
-### 3. 自适应父布局
-
-支持 `match_parent` 和 ConstraintLayout 约束:
-
-```xml
-<!-- 填充父布局 -->
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" />
-
-<!-- ConstraintLayout 约束 -->
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:layout_width="0dp"
-    android:layout_height="0dp"
-    app:layout_constraintWidth_percent="0.2"
-    app:layout_constraintDimensionRatio="1:1" />
-```
-
-### 4. 宽高不一致时
-
-组件会取宽高中的较小值,确保头像保持正方形:
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.avatar.AtomicAvatar
-    android:layout_width="80dp"
-    android:layout_height="60dp" />
-<!-- 实际显示: 60dp × 60dp(取较小值)-->
-```
-
-### 优先级规则
-
-1. **明确指定的尺寸** (`layout_width`/`layout_height` 为具体数值) > **avatarSize 属性**
-2. **wrap_content** 时使用 **avatarSize 属性**
-3. 宽高不一致时,取**较小值**保持正方形
-
-## 注意事项
-
-1. **内容优先级**: 当在 XML 中同时设置多个内容属性时,优先级为:`avatarUrl` > `avatarIcon` > `avatarText`。
-
-2. **占位图**: 使用 `avatarUrl` 时,建议设置 `avatarPlaceholder` 作为加载失败时的占位图。
-
-3. **文字内容**: `AtomicAvatar.AvatarContent.Text` 直接显示传入的文本内容,请在外部处理后传入。
-
-4. **徽章文本长度**: 建议徽章文本不超过 3 个字符(如 "99+"),过长文本会影响美观。
-
-5. **Drawable 资源**: 使用 `AtomicAvatar.AvatarContent.Icon` 时,传入的 Drawable 不应为 null。
-
-6. **主题支持**: 头像的背景色和文本颜色会自动适配当前主题(`ThemeStore`)。
-
-7. **保持正方形**: 组件会自动保持正方形比例,如果宽高设置不一致,会取较小值。
-
-## 设计规范
-
-### 尺寸选择建议
-
-- **超小图标**:使用 XXS (16dp)
-- **列表场景**:优先使用 M (40dp) 或 S (32dp)
-- **详情场景**:优先使用 L (48dp) 或 XL (64dp)
-- **个人主页**:使用 XXL (96dp)
-- **小型标签**:使用 XS (24dp)
-
-### 形状选择建议
-
-- **个人用户**:Round(圆形)
-- **群组/品牌**:RoundRectangle(圆角矩形)
-- **封面图**:Rectangle(矩形)
-
-### 徽章使用建议
-
-- **在线状态**:使用 Dot
-- **未读消息**:使用 Text(显示数字)
-- **无提示**:使用 None

+ 0 - 579
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/button/AtomicButton.kt

@@ -1,579 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.basicwidget.button
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.graphics.Color
-import android.graphics.PorterDuff
-import android.graphics.Typeface
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.Gravity
-import android.view.MotionEvent
-import android.view.View
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.annotation.ColorInt
-import androidx.core.content.withStyledAttributes
-import androidx.core.graphics.drawable.DrawableCompat
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.theme.Theme
-import io.trtc.tuikit.atomicx.theme.ThemeStore
-import io.trtc.tuikit.atomicx.theme.tokens.DesignTokenSet
-import io.trtc.tuikit.atomicx.widget.utils.DisplayUtil
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-
-enum class ButtonVariant {
-    FILLED,
-    OUTLINED,
-    TEXT
-}
-
-enum class ButtonColorType {
-    PRIMARY,
-    SECONDARY,
-    DANGER
-}
-
-enum class ButtonIconPosition {
-    START,
-    END,
-    NONE
-}
-
-enum class ButtonSize(
-    val heightDp: Float,
-    val minWidthDp: Float,
-    val iconSizeDp: Float,
-) {
-    XS(24f, 48f, 14f),
-    S(32f, 64f, 16f),
-    M(40f, 80f, 20f),
-    L(48f, 96f, 20f);
-
-    fun getHeightPx(context: Context): Int = DisplayUtil.dp2px(context, heightDp)
-    fun getMinWidthPx(context: Context): Int = DisplayUtil.dp2px(context, minWidthDp)
-    fun getIconSizePx(context: Context): Int = DisplayUtil.dp2px(context, iconSizeDp)
-}
-
-class AtomicButton @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-) : FrameLayout(context, attrs, defStyleAttr) {
-
-    companion object {
-        private const val ICON_PADDING_DP = 4f
-    }
-
-    private var lastDesignConfig: ButtonDesignConfig? = null
-
-    private val contentContainer: LinearLayout
-    private val iconView: ImageView = ImageView(context).apply {
-        visibility = GONE
-        scaleType = ImageView.ScaleType.CENTER_INSIDE
-    }
-    private val textView: TextView = TextView(context).apply {
-        gravity = Gravity.CENTER
-        includeFontPadding = false
-    }
-
-    var variant: ButtonVariant = ButtonVariant.FILLED
-        set(value) {
-            field = value
-            updateAppearance()
-        }
-
-    var colorType: ButtonColorType = ButtonColorType.PRIMARY
-        set(value) {
-            field = value
-            updateAppearance()
-        }
-
-    var size: ButtonSize = ButtonSize.S
-        set(value) {
-            field = value
-            requestLayout()
-            updateAppearance()
-        }
-
-    var iconDrawable: Drawable? = null
-        set(value) {
-            field = value
-            updateIcon()
-        }
-
-    var iconPosition: ButtonIconPosition = ButtonIconPosition.NONE
-        set(value) {
-            field = value
-            updateIcon()
-        }
-
-    var text: CharSequence
-        get() = textView.text
-        set(value) {
-            textView.text = value
-        }
-
-    var isBold: Boolean = false
-        set(value) {
-            field = value
-            updateAppearance()
-        }
-
-    var customTextSizeSp: Float? = null
-        set(value) {
-            field = value
-            updateAppearance()
-        }
-
-    private var buttonScope: CoroutineScope? = null
-
-    init {
-
-        contentContainer = LinearLayout(context).apply {
-            orientation = LinearLayout.HORIZONTAL
-            gravity = Gravity.CENTER
-            addView(iconView)
-            addView(textView)
-        }
-
-        addView(
-            contentContainer, FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.WRAP_CONTENT,
-                FrameLayout.LayoutParams.WRAP_CONTENT
-            ).apply {
-                gravity = Gravity.CENTER
-            }
-        )
-
-        isClickable = true
-        isFocusable = true
-
-        if (attrs != null) {
-            context.withStyledAttributes(
-                attrs,
-                R.styleable.AtomicButton,
-                defStyleAttr,
-                0
-            ) {
-                variant = when (getInt(R.styleable.AtomicButton_buttonVariant, 0)) {
-                    1 -> ButtonVariant.OUTLINED
-                    2 -> ButtonVariant.TEXT
-                    else -> ButtonVariant.FILLED
-                }
-
-                colorType = when (getInt(R.styleable.AtomicButton_buttonColorType, 0)) {
-                    1 -> ButtonColorType.SECONDARY
-                    2 -> ButtonColorType.DANGER
-                    else -> ButtonColorType.PRIMARY
-                }
-
-                size = when (getInt(R.styleable.AtomicButton_customButtonSize, 1)) {
-                    0 -> ButtonSize.XS
-                    1 -> ButtonSize.S
-                    2 -> ButtonSize.M
-                    3 -> ButtonSize.L
-                    else -> ButtonSize.S
-                }
-
-                iconPosition = when (getInt(R.styleable.AtomicButton_buttonIconPosition, 2)) {
-                    0 -> ButtonIconPosition.START
-                    1 -> ButtonIconPosition.END
-                    else -> ButtonIconPosition.NONE
-                }
-
-                iconDrawable = getDrawable(R.styleable.AtomicButton_buttonIcon)
-
-                textView.text = getText(R.styleable.AtomicButton_android_text)
-
-                isBold = getBoolean(R.styleable.AtomicButton_textStyle, false)
-
-                if (hasValue(R.styleable.AtomicButton_buttonTextSize)) {
-                    val sizePx = getDimension(R.styleable.AtomicButton_buttonTextSize, 0f)
-                    val scaledDensity = resources.displayMetrics.scaledDensity
-                    customTextSizeSp = sizePx / scaledDensity
-                }
-            }
-        }
-
-        if (isInEditMode) {
-            updateAppearance(Theme.lightTheme(context).tokens)
-        }
-    }
-
-    override fun setEnabled(enabled: Boolean) {
-        super.setEnabled(enabled)
-        textView.isEnabled = enabled
-        iconView.isEnabled = enabled
-        updateAppearance()
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        bindTheme()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        buttonScope?.cancel()
-        buttonScope = null
-    }
-
-    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-        val heightPx = size.getHeightPx(context)
-        val minWidthPx = size.getMinWidthPx(context)
-
-        val finalHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightPx, MeasureSpec.EXACTLY)
-        super.onMeasure(widthMeasureSpec, finalHeightMeasureSpec)
-        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
-        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
-
-        val finalWidth = when (widthMode) {
-            MeasureSpec.EXACTLY -> {
-                widthSize
-            }
-            MeasureSpec.AT_MOST -> {
-                maxOf(measuredWidth, minWidthPx).coerceAtMost(widthSize)
-            }
-            MeasureSpec.UNSPECIFIED -> {
-                maxOf(measuredWidth, minWidthPx)
-            }
-            else -> measuredWidth
-        }
-        setMeasuredDimension(finalWidth, heightPx)
-    }
-
-    override fun onTouchEvent(event: MotionEvent): Boolean {
-        if (!isEnabled) {
-            return false
-        }
-
-        when (event.action) {
-            MotionEvent.ACTION_DOWN -> {
-                isPressed = true
-            }
-            MotionEvent.ACTION_MOVE -> {
-                val isInside = event.x >= 0 && event.x <= width &&
-                        event.y >= 0 && event.y <= height
-                isPressed = isInside
-            }
-            MotionEvent.ACTION_UP -> {
-                if (isPressed) {
-                    isPressed = false
-                    performClick()
-                }
-            }
-            MotionEvent.ACTION_CANCEL -> {
-                isPressed = false
-            }
-        }
-        return true
-    }
-
-    override fun performClick(): Boolean {
-        return super.performClick()
-    }
-
-    override fun setPressed(pressed: Boolean) {
-        val wasPressed = isPressed
-        super.setPressed(pressed)
-
-        if (wasPressed != pressed) {
-            updateAppearance()
-        }
-    }
-
-    private fun bindTheme() {
-        buttonScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
-        buttonScope?.launch {
-            ThemeStore.shared(context).themeState.collectLatest {
-                updateAppearance(it.currentTheme.tokens)
-            }
-        }
-    }
-
-    private fun getCurrentTokens(): DesignTokenSet {
-        return ThemeStore.shared(context).themeState.value.currentTheme.tokens
-    }
-
-    private fun updateAppearance(tokens: DesignTokenSet = getCurrentTokens()) {
-        val newConfig = getButtonDesignConfig(tokens)
-
-        textView.setTextColor(newConfig.textColor)
-        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, newConfig.fontSize)
-        textView.typeface = if (newConfig.isBold) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
-
-        if (shouldRecreateBackground(newConfig, lastDesignConfig)) {
-            background = createBackgroundDrawable(tokens, newConfig)
-        } else {
-            updateExistingBackground(newConfig)
-        }
-
-        updateIcon(newConfig.textColor)
-        lastDesignConfig = newConfig
-    }
-
-    private fun shouldRecreateBackground(
-        newConfig: ButtonDesignConfig,
-        oldConfig: ButtonDesignConfig?,
-    ): Boolean {
-        if (oldConfig == null) return true
-
-        if (newConfig.cornerRadiusDp != oldConfig.cornerRadiusDp ||
-            newConfig.borderWidth != oldConfig.borderWidth
-        ) {
-            return true
-        }
-        if ((newConfig.borderWidth == 0f && oldConfig.borderWidth != 0f) ||
-            (newConfig.borderWidth != 0f && oldConfig.borderWidth == 0f)
-        ) {
-            return true
-        }
-
-        return false
-    }
-
-    private fun updateExistingBackground(newConfig: ButtonDesignConfig) {
-        val currentBackground = background
-
-        if (currentBackground is GradientDrawable) {
-            currentBackground.setColor(newConfig.backgroundColor)
-            currentBackground.setStroke(
-                DisplayUtil.dp2px(context, newConfig.borderWidth),
-                newConfig.borderColor
-            )
-        }
-    }
-
-    private fun updateIcon(@ColorInt tintColor: Int = textView.currentTextColor) {
-        val icon = iconDrawable
-        val iconSizeDp = size.iconSizeDp
-        val iconSizePx = DisplayUtil.dp2px(context, iconSizeDp)
-        val iconPadding = DisplayUtil.dp2px(context, ICON_PADDING_DP)
-
-        if (icon == null || iconPosition == ButtonIconPosition.NONE) {
-            iconView.visibility = View.GONE
-            return
-        }
-
-        iconView.visibility = View.VISIBLE
-
-        val wrappedIcon = DrawableCompat.wrap(icon.mutate())
-        DrawableCompat.setTint(wrappedIcon, tintColor)
-        DrawableCompat.setTintMode(wrappedIcon, PorterDuff.Mode.SRC_IN)
-
-        iconView.setImageDrawable(wrappedIcon)
-
-        val iconLayoutParams = iconView.layoutParams as? LinearLayout.LayoutParams
-            ?: LinearLayout.LayoutParams(iconSizePx, iconSizePx)
-
-        iconLayoutParams.width = iconSizePx
-        iconLayoutParams.height = iconSizePx
-
-        when (iconPosition) {
-            ButtonIconPosition.START -> {
-                iconLayoutParams.setMargins(0, 0, iconPadding, 0)
-                contentContainer.removeAllViews()
-                contentContainer.addView(iconView)
-                contentContainer.addView(textView)
-            }
-
-            ButtonIconPosition.END -> {
-                iconLayoutParams.setMargins(iconPadding, 0, 0, 0)
-                contentContainer.removeAllViews()
-                contentContainer.addView(textView)
-                contentContainer.addView(iconView)
-            }
-
-            ButtonIconPosition.NONE -> {
-                iconView.visibility = View.GONE
-            }
-        }
-
-        iconView.layoutParams = iconLayoutParams
-    }
-
-    private data class ButtonDesignConfig(
-        @ColorInt val backgroundColor: Int,
-        @ColorInt val borderColor: Int,
-        @ColorInt val textColor: Int,
-        val borderWidth: Float,
-        val cornerRadiusDp: Float,
-        val fontSize: Float,
-        val isBold: Boolean,
-    )
-
-    private fun getButtonDesignConfig(
-        tokens: DesignTokenSet,
-        forcePressed: Boolean = false
-    ): ButtonDesignConfig {
-        val colors = tokens.color
-        val font = tokens.font
-
-        val isEnabledState = isEnabled
-        val isPressedState = forcePressed || isPressed
-
-        val primaryColorTokens = when (colorType) {
-            ButtonColorType.PRIMARY -> Triple(
-                colors.buttonColorPrimaryDefault,
-                colors.buttonColorPrimaryActive,
-                colors.buttonColorPrimaryDisabled
-            )
-
-            ButtonColorType.SECONDARY -> Triple(
-                colors.buttonColorSecondaryDefault,
-                colors.buttonColorSecondaryActive,
-                colors.buttonColorSecondaryDisabled
-            )
-
-            ButtonColorType.DANGER -> Triple(
-                colors.buttonColorHangupDefault,
-                colors.buttonColorHangupActive,
-                colors.buttonColorHangupDisabled
-            )
-        }
-
-        val textColors = when (colorType) {
-            ButtonColorType.PRIMARY -> Triple(
-                colors.textColorLink,
-                colors.textColorLinkActive,
-                colors.textColorLinkDisabled
-            )
-
-            ButtonColorType.SECONDARY -> Triple(
-                colors.textColorPrimary,
-                colors.textColorSecondary,
-                colors.textColorDisable
-            )
-
-            ButtonColorType.DANGER -> Triple(
-                colors.buttonColorHangupDefault,
-                colors.buttonColorHangupActive,
-                colors.buttonColorHangupDisabled
-            )
-        }
-
-        @ColorInt val defaultPrimaryColor = primaryColorTokens.first
-        @ColorInt val activePrimaryColor = primaryColorTokens.second
-        @ColorInt val disabledPrimaryColor = primaryColorTokens.third
-
-        @ColorInt val defaultTextColor = textColors.first
-        @ColorInt val activeTextColor = textColors.second
-        @ColorInt val disabledTextColor = textColors.third
-
-        @ColorInt var finalBg: Int
-        @ColorInt var finalBorder: Int
-        @ColorInt var finalText: Int
-        val borderWidth: Float
-
-        if (!isEnabledState) {
-            finalText = when (variant) {
-                ButtonVariant.FILLED -> colors.textColorButtonDisabled
-                ButtonVariant.OUTLINED, ButtonVariant.TEXT -> disabledTextColor
-            }
-
-            val primaryDisabledColor = disabledPrimaryColor
-
-            when (variant) {
-                ButtonVariant.FILLED -> {
-                    finalBg = primaryDisabledColor
-                    finalBorder = primaryDisabledColor
-                }
-
-                ButtonVariant.OUTLINED -> {
-                    finalBg = Color.TRANSPARENT
-                    finalBorder = primaryDisabledColor
-                }
-
-                ButtonVariant.TEXT -> {
-                    finalBg = Color.TRANSPARENT
-                    finalBorder = Color.TRANSPARENT
-                }
-            }
-
-            borderWidth = if (variant == ButtonVariant.OUTLINED) 1f else 0f
-        } else {
-            val currentPrimaryColor =
-                if (isPressedState) activePrimaryColor else defaultPrimaryColor
-            val currentTextColor = if (isPressedState) activeTextColor else defaultTextColor
-
-            when (variant) {
-                ButtonVariant.FILLED -> {
-                    finalBg = currentPrimaryColor
-                    finalBorder = currentPrimaryColor
-                    finalText = colors.textColorButton
-                    borderWidth = 0f
-                }
-
-                ButtonVariant.OUTLINED -> {
-                    finalBg = Color.TRANSPARENT
-                    finalBorder = currentPrimaryColor
-                    finalText = when (colorType) {
-                        ButtonColorType.SECONDARY -> {
-                            colors.textColorButton
-                        }
-                        ButtonColorType.PRIMARY,
-                        ButtonColorType.DANGER -> {
-                            currentPrimaryColor
-                        }
-                    }
-                    borderWidth = 1f
-                }
-
-                ButtonVariant.TEXT -> {
-                    finalBg = Color.TRANSPARENT
-                    finalBorder = Color.TRANSPARENT
-                    finalText = currentTextColor
-                    borderWidth = 0f
-                }
-            }
-        }
-
-        val defaultFontSize = when (size) {
-            ButtonSize.XS -> font.bold12.size
-            ButtonSize.S -> font.bold14.size
-            ButtonSize.M -> font.bold16.size
-            ButtonSize.L -> font.bold16.size
-        }
-
-        val finalFontSize = customTextSizeSp ?: defaultFontSize
-
-        val finalCornerRadiusDp = 9999f
-
-        return ButtonDesignConfig(
-            backgroundColor = finalBg,
-            borderColor = finalBorder,
-            textColor = finalText,
-            borderWidth = borderWidth,
-            cornerRadiusDp = finalCornerRadiusDp,
-            fontSize = finalFontSize,
-            isBold = isBold
-        )
-    }
-
-    private fun createBackgroundDrawable(
-        tokens: DesignTokenSet,
-        config: ButtonDesignConfig,
-    ): Drawable {
-        val cornerRadiusPx = DisplayUtil.dp2px(context, config.cornerRadiusDp).toFloat()
-        val borderWidthPx = DisplayUtil.dp2px(context, config.borderWidth)
-
-        val backgroundDrawable = GradientDrawable().apply {
-            shape = GradientDrawable.RECTANGLE
-            cornerRadius = cornerRadiusPx
-            setColor(config.backgroundColor)
-            setStroke(borderWidthPx, config.borderColor)
-        }
-
-        return backgroundDrawable
-    }
-}

+ 0 - 178
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/button/README.md

@@ -1,178 +0,0 @@
-# AtomicButton 基础组件
-
-AtomicX Android UIKit 通用按钮组件 `AtomicButton`,支持多种语义变体、尺寸档位和灵活的图文布局,并与主题系统深度集成。
-
-## 文件结构
-
-```
-button/
-├── AtomicButton.kt           # 按钮组件实现(变体、尺寸、布局、主题响应)
-└── README.md                 # 本文件
-```
-
-## 快速开始
-
-### 在 XML 布局中使用
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.button.AtomicButton
-    android:id="@+id/btnSubmit"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:text="提交"
-    app:buttonVariant="filled"
-    app:buttonColorType="primary"
-    app:customButtonSize="m" />
-```
-
-### XML 属性一览
-
-| 属性名 | 类型 | 可选值 | 说明 |
-| :--- | :--- | :--- | :--- |
-| `android:text` | string | - | 按钮文本 |
-| `buttonVariant` | enum | `filled` / `outlined` / `text` | 按钮变体样式 |
-| `buttonColorType` | enum | `primary` / `secondary` / `danger` | 按钮语义颜色 |
-| `customButtonSize` | enum | `xs` / `s` / `m` / `l` | 按钮尺寸档位 |
-| `buttonIconPosition` | enum | `start` / `end` / `none` | 图标位置 |
-| `buttonIcon` | reference | drawable 资源 | 按钮图标 |
-| `textStyle` | boolean | `true` / `false` | 是否加粗 |
-| `buttonTextSize` | dimension | 如 `14sp` | 自定义文字大小 |
-
-### 在代码中配置
-
-```kotlin
-// 1. 创建主操作按钮(默认:FILLED + PRIMARY + S)
-val btnSubmit = findViewById<AtomicButton>(R.id.btnSubmit).apply {
-    variant = ButtonVariant.FILLED
-    colorType = ButtonColorType.PRIMARY
-    size = ButtonSize.M
-}
-
-// 2. 创建次级按钮(描边样式)
-val btnCancel = AtomicButton(context).apply {
-    text = "取消"
-    variant = ButtonVariant.OUTLINED
-    colorType = ButtonColorType.SECONDARY
-    size = ButtonSize.M
-}
-
-// 3. 创建危险操作按钮(带图标)
-val btnDelete = AtomicButton(context).apply {
-    text = "删除"
-    variant = ButtonVariant.FILLED
-    colorType = ButtonColorType.DANGER
-    size = ButtonSize.S
-
-    iconDrawable = ContextCompat.getDrawable(context, R.drawable.ic_delete)
-    iconPosition = ButtonIconPosition.START
-}
-
-// 4. 注册点击事件
-btnSubmit.setOnClickListener {
-    // 处理点击逻辑
-}
-```
-
-## 尺寸优先级说明
-
-`AtomicButton` 支持两种方式控制尺寸,优先级如下:
-
-1. **XML 布局属性优先**:如果在 XML 中显式设置了 `layout_height` 或 `layout_width`(非 `wrap_content`),则以 XML 设置的值为准。
-2. **customButtonSize 属性次之**:当 `layout_height="wrap_content"` 时,组件会根据 `customButtonSize`(`xs` / `s` / `m` / `l`)自动计算高度与最小宽度。
-
-> **建议**:一般情况下将 `layout_height` 设为 `wrap_content`,通过 `customButtonSize` 控制尺寸;如有特殊需求可直接指定固定高度覆盖。
-
-## 主要特性
-
-### 1. 语义变体与视觉样式 (`ButtonVariant`)
-
-- `FILLED` (默认): 实心背景,适合主要操作
-- `OUTLINED`: 描边样式,背景透明,适合次级操作
-- `TEXT`: 纯文本样式,无边框无背景,适合轻量级操作
-
-### 2. 颜色语义 (`ButtonColorType`)
-
-- `PRIMARY` (默认): 主题主色,强调操作
-- `SECONDARY`: 中性配色,用于默认/次要操作
-- `DANGER`: 危险/警示操作(通常为红色)
-
-### 3. 四种尺寸档位 (`ButtonSize` / `customButtonSize`)
-
-自动适配高度、最小宽度、图标大小和字体大小。
-
-| 尺寸 | XML 值 | 高度 (dp) | 最小宽度 (dp) | 图标大小 (dp) | 字体大小 |
-| :--- | :--- | :--- | :--- | :--- | :--- |
-| `XS` | `xs` | 24 | 48 | 14 | 12sp |
-| `S` | `s` | 32 | 64 | 16 | 14sp |
-| `M` | `m` | 40 | 80 | 20 | 16sp |
-| `L` | `l` | 48 | 96 | 20 | 16sp |
-
-### 4. 灵活的图文布局
-
-通过 `buttonIcon` 和 `buttonIconPosition` 属性控制:
-
-- `start`: 左图右文
-- `end`: 左文右图
-- `none`: 仅文本(默认)
-
-> 图标会自动进行着色(`tint`)以匹配当前按钮的文本颜色。
-
-### 5. 主题系统集成
-
-- **自动响应**:`AtomicButton` 内部监听 `ThemeStore` 的状态变化。当应用切换主题(如深色模式)时,按钮会自动更新背景色、边框色、文字颜色和水波纹效果,无需手动刷新。
-- **生命周期管理**:组件自动处理协程作用域,在 View `onDetachedFromWindow` 时取消监听,防止内存泄漏。
-
-### 6. 自动状态管理
-
-组件根据 `isEnabled` 和 `isPressed` 状态自动切换样式:
-
-- **Normal**: 默认状态,使用标准色。
-- **Pressed**: 按下状态,背景变深或显示 Ripple 水波纹。
-- **Disabled**: 禁用状态,自动置灰背景、边框和文字,且不响应点击。
-
-## 样式规范细节
-
-组件内部根据 `ButtonVariant` 和 `ButtonColorType` 组合计算最终样式:
-
-- **FILLED 模式**:
-    - 背景色:填充对应 ColorType 的颜色
-    - 边框:无
-    - 文字:通常为反白 (White)
-- **OUTLINED 模式**:
-    - 背景色:透明
-    - 边框:1dp 实线,颜色跟随 ColorType
-    - 文字:颜色跟随 ColorType
-- **TEXT 模式**:
-    - 背景色:透明
-    - 边框:无
-    - 文字:颜色跟随 ColorType
-
-> 圆角默认根据高度自适应为全圆角(Capsule 样式)。
-
-## API 参考
-
-### 核心属性
-
-| 属性 | 类型 | 说明 |
-| :--- | :--- | :--- |
-| `variant` | `ButtonVariant` | 按钮变体样式 |
-| `colorType` | `ButtonColorType` | 按钮语义颜色 |
-| `size` | `ButtonSize` | 按钮尺寸 |
-| `iconDrawable` | `Drawable?` | 图标资源 |
-| `iconPosition` | `ButtonIconPosition` | 图标位置 |
-| `text` | `CharSequence` | 按钮文本 |
-| `isBold` | `Boolean` | 是否加粗 |
-| `customTextSizeSp` | `Float?` | 自定义文字大小(sp) |
-
-### 继承属性
-
-继承自 `FrameLayout`,支持所有标准属性:
-- `isEnabled`
-- `setOnClickListener`
-- ...
-
-## 注意事项
-
-1. **EditMode 支持**:在 Android Studio 预览布局中,组件会尝试加载默认主题以展示大致效果。
-2. **布局参数**:设置 `customButtonSize` 属性会自动调整高度和最小宽度,通常建议将 XML 中的高度设为 `wrap_content`。
-3. **图标着色**:组件会强制接管图标的 `tint` 颜色。如果需要显示图标原色,请确保 `iconDrawable` 为 `null` 并自行处理(或扩展组件)。

+ 0 - 242
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/label/AtomicLabel.kt

@@ -1,242 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.basicwidget.label
-
-import android.content.Context
-import android.content.res.TypedArray
-import android.graphics.Color
-import android.graphics.Typeface
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.util.AttributeSet
-import android.util.Size
-import android.util.TypedValue
-import android.view.Gravity
-import androidx.annotation.ColorInt
-import androidx.appcompat.widget.AppCompatTextView
-import androidx.core.content.withStyledAttributes
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.theme.Theme
-import io.trtc.tuikit.atomicx.theme.ThemeStore
-import io.trtc.tuikit.atomicx.theme.tokens.ColorTokens
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.launch
-
-class AtomicLabel @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0
-) : AppCompatTextView(context, attrs, defStyleAttr) {
-
-    data class LabelAppearance(
-        @ColorInt val textColor: Int,
-        @ColorInt val backgroundColor: Int,
-        val textSize: Float,
-        val textWeight: Int,
-        val cornerRadius: Float
-    ) {
-        companion object {
-            fun defaultAppearance(theme: Theme): LabelAppearance {
-                val defaultFont = theme.tokens.font.regular14
-                return LabelAppearance(
-                    textColor = theme.tokens.color.textColorPrimary,
-                    backgroundColor = Color.TRANSPARENT,
-                    textSize = defaultFont.size,
-                    textWeight = defaultFont.weight,
-                    cornerRadius = 0f
-                )
-            }
-        }
-    }
-
-    data class IconConfiguration(
-        val drawable: Drawable?,
-        val position: Position = Position.LEFT,
-        val spacing: Float = 4f,
-        val size: Size? = null
-    ) {
-        enum class Position { LEFT, RIGHT }
-    }
-
-    fun interface AppearanceProvider {
-        fun provide(theme: Theme): LabelAppearance
-    }
-
-    var iconConfiguration: IconConfiguration? = null
-        set(value) {
-            field = value
-            applyIconConfiguration()
-        }
-
-    private var appearanceProvider: AppearanceProvider? = null
-    private val themeBackgroundDrawable by lazy { GradientDrawable() }
-    private var isBackgroundManaged = false
-    private var cornerRadiusOverride: Float? = null
-    private var textColorTokenKey: String? = null
-    private var backgroundColorTokenKey: String? = null
-    private var backgroundColorStatic: Int? = null
-    private var viewScope: CoroutineScope? = null
-
-    init {
-        if (gravity == 0) {
-            gravity = Gravity.CENTER_VERTICAL
-        }
-
-        parseAttributes(attrs)
-        applyStaticFallback()
-    }
-
-    fun setAppearanceProvider(provider: AppearanceProvider) {
-        this.appearanceProvider = provider
-        if (isAttachedToWindow) {
-            refreshAppearance()
-        }
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        startThemeObservation()
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        stopThemeObservation()
-    }
-
-    private fun startThemeObservation() {
-        if (viewScope != null) return
-
-        viewScope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
-        viewScope?.launch {
-            ThemeStore.shared(context).themeState.collect { state ->
-                applyTheme(state.currentTheme)
-            }
-        }
-    }
-
-    private fun stopThemeObservation() {
-        viewScope?.cancel()
-        viewScope = null
-    }
-
-    private fun refreshAppearance() {
-        val currentTheme = ThemeStore.shared(context).themeState.value.currentTheme
-        applyTheme(currentTheme)
-    }
-
-    private fun applyTheme(theme: Theme) {
-        val provider = appearanceProvider
-
-        if (provider != null) {
-            val appearance = provider.provide(theme)
-            applyAppearance(appearance)
-        } else {
-            applyAtomicProperties(theme)
-        }
-    }
-
-    private fun applyAppearance(appearance: LabelAppearance) {
-        setTextColor(appearance.textColor)
-        setTextSize(TypedValue.COMPLEX_UNIT_SP, appearance.textSize)
-        applyTypeface(appearance.textWeight)
-        updateBackgroundState(appearance.backgroundColor, appearance.cornerRadius)
-    }
-
-    private fun applyAtomicProperties(theme: Theme) {
-        textColorTokenKey?.let { key ->
-            val color = theme.tokens.color[key]
-            if (color != 0) {
-                setTextColor(color)
-            }
-        }
-
-        val bgColor = when {
-            backgroundColorTokenKey != null -> theme.tokens.color[backgroundColorTokenKey!!]
-            backgroundColorStatic != null -> backgroundColorStatic!!
-            else -> null
-        }
-
-        if (bgColor != null) {
-            updateBackgroundState(bgColor, cornerRadiusOverride ?: 0f)
-        }
-    }
-
-    private fun updateBackgroundState(@ColorInt color: Int, radius: Float) {
-        if (background != themeBackgroundDrawable) {
-            background = themeBackgroundDrawable
-            isBackgroundManaged = true
-        }
-
-        themeBackgroundDrawable.setColor(color)
-        themeBackgroundDrawable.cornerRadius = radius
-    }
-
-    private fun applyTypeface(weight: Int) {
-        val targetStyle = if (weight == Typeface.BOLD) Typeface.BOLD else Typeface.NORMAL
-        if (typeface == null || typeface.style != targetStyle) {
-            setTypeface(Typeface.create(Typeface.DEFAULT, targetStyle))
-        }
-    }
-
-    private fun applyStaticFallback() {
-        if (backgroundColorStatic != null && backgroundColorTokenKey == null) {
-            updateBackgroundState(backgroundColorStatic!!, cornerRadiusOverride ?: 0f)
-        }
-    }
-
-    private fun parseAttributes(attrs: AttributeSet?) {
-        textColorTokenKey = ColorTokens.parseColorAttribute(
-            attrs = attrs,
-            attrId = android.R.attr.textColor,
-            attrName = "textColor"
-        )
-
-        context.withStyledAttributes(attrs, R.styleable.AtomicLabel) {
-            if (hasValue(R.styleable.AtomicLabel_labelCornerRadius)) {
-                cornerRadiusOverride = getDimension(R.styleable.AtomicLabel_labelCornerRadius, 0f)
-            }
-
-            if (hasValue(R.styleable.AtomicLabel_labelBackgroundColor)) {
-                val (tokenKey, staticColor) = parseColorTokenAttributeWithFallback(
-                    R.styleable.AtomicLabel_labelBackgroundColor
-                )
-                backgroundColorTokenKey = tokenKey
-                backgroundColorStatic = staticColor
-            }
-        }
-    }
-
-    private fun TypedArray.parseColorTokenAttributeWithFallback(index: Int): Pair<String?, Int?> {
-        val resourceId = getResourceId(index, -1)
-        if (resourceId != -1) {
-            val tokenKey = ColorTokens.getTokenKeyFromColorResId(resourceId)
-            if (tokenKey != null) {
-                return Pair(tokenKey, null)
-            }
-        }
-        return Pair(null, getColor(index, 0))
-    }
-
-    private fun applyIconConfiguration() {
-        val config = iconConfiguration
-
-        if (config?.drawable == null) {
-            setCompoundDrawablesRelative(null, null, null, null)
-            return
-        }
-
-        val drawable = config.drawable.mutate()
-        val width = config.size?.width ?: drawable.intrinsicWidth
-        val height = config.size?.height ?: drawable.intrinsicHeight
-
-        drawable.setBounds(0, 0, width, height)
-        compoundDrawablePadding = config.spacing.toInt()
-
-        if (config.position == IconConfiguration.Position.LEFT) {
-            setCompoundDrawablesRelative(drawable, null, null, null)
-        } else {
-            setCompoundDrawablesRelative(null, null, drawable, null)
-        }
-    }
-}

+ 0 - 523
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/label/README.md

@@ -1,523 +0,0 @@
-# AtomicLabel 文本标签组件
-
-AtomicLabel 是一个功能丰富的 Android 文本标签组件,基于 AppCompatTextView 封装,提供了图文混排、主题化、颜色 Token 和自定义外观等特性。它遵循 AtomicX 设计系统,支持动态主题切换和多种样式配置。
-
-## 文件结构
-
-```
-label/
-├── AtomicLabel.kt          # Label 组件核心实现
-└── README.md               # 本文件
-```
-
-## 快速开始
-
-AtomicLabel 支持代码创建和 XML 布局两种使用方式。
-
-### 1. 纯文本标签
-
-最简单的文本标签,无图标,使用默认样式。
-
-```kotlin
-val label = AtomicLabel(context)
-label.text = "这是一个文本标签"
-```
-
-### 2. 带图标的标签
-
-支持在文本左侧或右侧添加图标。
-
-```kotlin
-val label = AtomicLabel(context)
-label.text = "带图标的标签"
-label.iconConfiguration = AtomicLabel.IconConfiguration(
-    drawable = ContextCompat.getDrawable(context, R.drawable.ic_star),
-    position = AtomicLabel.IconConfiguration.Position.LEFT,
-    spacing = 8f,
-    size = Size(20, 20)
-)
-```
-
-### 3. 图标在右侧
-
-适用于导航、展开/收起等场景。
-
-```kotlin
-label.iconConfiguration = AtomicLabel.IconConfiguration(
-    drawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_right),
-    position = AtomicLabel.IconConfiguration.Position.RIGHT,
-    spacing = 4f
-)
-```
-
-### 4. 自定义外观
-
-通过 AppearanceProvider 自定义颜色、字体、圆角等样式。
-
-```kotlin
-val label = AtomicLabel(context)
-label.text = "自定义样式"
-label.setAppearanceProvider { theme ->
-    AtomicLabel.LabelAppearance(
-        textColor = theme.tokens.color.textColorSecondary,
-        backgroundColor = theme.tokens.color.buttonColorPrimaryDefault,
-        textSize = theme.tokens.font.bold16.size,
-        textWeight = Typeface.BOLD,
-        cornerRadius = 8f
-    )
-}
-```
-
-### 5. 圆角背景
-
-支持设置圆角背景,适用于标签、徽章等场景。
-
-```kotlin
-val label = AtomicLabel(context)
-label.text = "圆角标签"
-label.setAppearanceProvider { theme ->
-    AtomicLabel.LabelAppearance(
-        textColor = Color.WHITE,
-        backgroundColor = theme.tokens.color.buttonColorPrimaryDefault,
-        textSize = 14f,
-        textWeight = Typeface.NORMAL,
-        cornerRadius = 12f
-    )
-}
-label.setPadding(16, 8, 16, 8)
-```
-
-### 6. XML 布局使用(支持颜色 Token)
-
-在 XML 中直接使用 AtomicLabel,支持 `android:textColor` 和自定义属性。
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.label.AtomicLabel
-    android:id="@+id/myLabel"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:text="XML 中的标签"
-    android:textColor="@color/text_color_primary"
-    app:labelBackgroundColor="@color/bg_color_function"
-    app:labelCornerRadius="8dp"
-    android:padding="12dp" />
-```
-
-然后在代码中配置:
-
-```kotlin
-val label = findViewById<AtomicLabel>(R.id.myLabel)
-label.iconConfiguration = AtomicLabel.IconConfiguration(
-    drawable = ContextCompat.getDrawable(this, R.drawable.ic_star),
-    position = AtomicLabel.IconConfiguration.Position.LEFT
-)
-```
-
-## 核心 API
-
-### `AtomicLabel` 类
-
-#### 构造函数
-
-```kotlin
-AtomicLabel(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
-```
-
-| 参数名 | 类型 | 说明 | 默认值 |
-| :--- | :--- | :--- | :--- |
-| `context` | Context | 上下文对象 | (必填) |
-| `attrs` | AttributeSet? | XML 属性集 | null |
-| `defStyleAttr` | Int | 默认样式属性 | 0 |
-
-#### 属性
-
-| 属性名 | 类型 | 说明 |
-| :--- | :--- | :--- |
-| `text` | CharSequence? | 文本内容(继承自 TextView) |
-| `iconConfiguration` | IconConfiguration? | 图标配置,设置后自动更新 |
-
-#### 方法
-
-| 方法名 | 说明 |
-| :--- | :--- |
-| `setAppearanceProvider(provider: AppearanceProvider)` | 设置外观提供者,会在 View attached 时立即刷新样式 |
-
-#### XML 自定义属性
-
-| 属性名 | 类型 | 说明 |
-| :--- | :--- | :--- |
-| `android:textColor` | reference\|color | 文本颜色,支持颜色 Token(如 `@color/text_color_primary`)或静态颜色 |
-| `app:labelBackgroundColor` | reference\|color | 背景颜色,支持颜色 Token 或静态颜色 |
-| `app:labelCornerRadius` | dimension | 圆角半径(如 `8dp`) |
-
-### `LabelAppearance` 数据类
-
-定义 Label 的视觉样式。
-
-| 属性名 | 类型 | 说明 |
-| :--- | :--- | :--- |
-| `textColor` | Int | 文本颜色(Color Int) |
-| `backgroundColor` | Int | 背景颜色(Color Int) |
-| `textSize` | Float | 字体大小(单位:SP) |
-| `textWeight` | Int | 字体粗细(Typeface.BOLD 或 Typeface.NORMAL) |
-| `cornerRadius` | Float | 圆角半径(单位:DP) |
-
-#### 默认外观
-
-```kotlin
-LabelAppearance.defaultAppearance(theme: Theme): LabelAppearance
-```
-
-返回基于当前主题的默认外观配置。
-
-### `IconConfiguration` 数据类
-
-定义图标的配置参数。
-
-| 属性名 | 类型 | 说明 | 默认值 |
-| :--- | :--- | :--- | :--- |
-| `drawable` | Drawable? | 图标 Drawable 对象 | (必填) |
-| `position` | Position | 图标位置(LEFT/RIGHT) | LEFT |
-| `spacing` | Float | 图标与文本的间距(单位:px) | 4f |
-| `size` | Size? | 图标尺寸,null 则使用原始尺寸 | null |
-
-#### Position 枚举
-
-| 枚举值 | 说明 |
-| :--- | :--- |
-| `LEFT` | 图标在文本左侧 |
-| `RIGHT` | 图标在文本右侧 |
-
-### `AppearanceProvider` 函数接口
-
-用于根据主题动态提供外观配置。
-
-```kotlin
-fun interface AppearanceProvider {
-    fun provide(theme: Theme): LabelAppearance
-}
-```
-
-## 动态主题 (Dynamic Theming)
-
-`AtomicLabel` 内置了对 `ThemeStore` 的支持,会自动监听主题变化并更新样式:
-
-- **自动刷新**:主题切换时,所有 Label 会自动应用新主题
-- **协程管理**:使用基于 View 的 CoroutineScope 监听主题状态,自动处理生命周期
-- **性能优化**:仅在 View attach 时订阅,detach 时自动取消
-- **颜色 Token**:支持在 XML 中使用颜色 Token,自动跟随主题切换
-
-### 方式 1:使用 AppearanceProvider
-
-```kotlin
-val label = AtomicLabel(context)
-label.text = "主题化标签"
-label.setAppearanceProvider { theme ->
-    AtomicLabel.LabelAppearance(
-        textColor = theme.tokens.color.textColorPrimary,
-        backgroundColor = theme.tokens.color.bgColorDefault,
-        textSize = theme.tokens.font.regular14.size,
-        textWeight = theme.tokens.font.regular14.weight,
-        cornerRadius = 4f
-    )
-}
-```
-
-### 方式 2:使用 XML 颜色 Token
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.label.AtomicLabel
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:text="主题化标签"
-    android:textColor="@color/text_color_primary"
-    app:labelBackgroundColor="@color/bg_color_function"
-    app:labelCornerRadius="4dp" />
-```
-
-当用户切换主题时:
-
-```kotlin
-ThemeStore.shared(context).setTheme(Theme.darkTheme(context))
-```
-
-所有使用主题 Token 或 AppearanceProvider 的 Label 会自动更新样式。
-
-## 使用场景
-
-### 1. 状态标签
-
-显示用户状态、订单状态等信息。
-
-```kotlin
-val statusLabel = AtomicLabel(context)
-statusLabel.text = "已完成"
-statusLabel.setAppearanceProvider { theme ->
-    AtomicLabel.LabelAppearance(
-        textColor = Color.WHITE,
-        backgroundColor = theme.tokens.color.toastColorSuccess,
-        textSize = 12f,
-        textWeight = Typeface.NORMAL,
-        cornerRadius = 4f
-    )
-}
-statusLabel.setPadding(12, 4, 12, 4)
-```
-
-### 2. 导航菜单项
-
-带箭头的导航项。
-
-```kotlin
-val menuItem = AtomicLabel(context)
-menuItem.text = "个人设置"
-menuItem.iconConfiguration = AtomicLabel.IconConfiguration(
-    drawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_right),
-    position = AtomicLabel.IconConfiguration.Position.RIGHT,
-    spacing = 8f,
-    size = Size(16, 16)
-)
-```
-
-### 3. 标签组
-
-多个标签组合展示。
-
-```kotlin
-val tags = listOf("Android", "Kotlin", "AtomicX")
-val tagContainer = LinearLayout(context).apply {
-    orientation = LinearLayout.HORIZONTAL
-}
-
-tags.forEach { tagText ->
-    val tag = AtomicLabel(context)
-    tag.text = tagText
-    tag.setAppearanceProvider { theme ->
-        AtomicLabel.LabelAppearance(
-            textColor = theme.tokens.color.textColorLink,
-            backgroundColor = theme.tokens.color.bgColorFunction,
-            textSize = 12f,
-            textWeight = Typeface.NORMAL,
-            cornerRadius = 8f
-        )
-    }
-    tag.setPadding(16, 6, 16, 6)
-    tagContainer.addView(tag)
-}
-```
-
-### 4. 信息提示
-
-带图标的信息提示。
-
-```kotlin
-val infoLabel = AtomicLabel(context)
-infoLabel.text = "点击查看详情"
-infoLabel.iconConfiguration = AtomicLabel.IconConfiguration(
-    drawable = ContextCompat.getDrawable(context, R.drawable.ic_info),
-    position = AtomicLabel.IconConfiguration.Position.LEFT,
-    spacing = 4f,
-    size = Size(16, 16)
-)
-```
-
-## 注意事项
-
-1. **Context 选择**:建议传入 Activity Context,避免 Application Context 导致的主题问题。
-
-2. **图标尺寸**:图标尺寸建议不超过字体行高的 1.5 倍,以保持视觉平衡。
-
-3. **间距单位**:`IconConfiguration.spacing` 的单位是 **px(像素)**,不是 dp。如需使用 dp,请使用 `dp2px` 工具方法转换。
-
-```kotlin
-val spacingPx = dp2px(context, 8f)
-```
-
-4. **生命周期管理**:组件内部使用基于 View 的 CoroutineScope 自动处理主题监听的生命周期,无需手动取消订阅。
-
-5. **性能优化**:使用 `CompoundDrawables` 实现图标,性能优于 ImageSpan 方案。
-
-6. **背景绘制**:圆角背景通过 `GradientDrawable` 实现,支持硬件加速,使用 lazy 延迟初始化。
-
-7. **文本更新**:修改 `text` 属性会立即刷新显示,无需手动调用 `invalidate()`。
-
-8. **颜色 Token**:
-   - XML 中引用的颜色资源(如 `@color/text_color_primary`)会自动识别为 Token 并跟随主题切换
-   - 直接使用颜色值(如 `#FF000000`)则为静态颜色,不跟随主题
-   - AppearanceProvider 优先级高于 XML 属性
-
-9. **协程调度**:使用 `Dispatchers.Main.immediate` 确保主题更新在 UI 线程立即执行。
-
-## 设计规范
-
-AtomicLabel 遵循以下设计规范:
-
-- **默认字体**:14sp,Regular(400)
-- **图标间距**:4px(约 2dp)
-- **圆角半径**:0-12dp(根据场景调整)
-- **内边距**:建议 8-16dp(水平)、4-8dp(垂直)
-- **图标尺寸**:12-24dp(根据字体大小调整)
-
-## 常见问题
-
-**Q: 如何动态修改文本内容?**  
-A: 直接设置 `text` 属性即可:
-
-```kotlin
-label.text = "新的文本内容"
-```
-
-**Q: 如何移除图标?**  
-A: 将 `iconConfiguration` 设置为 `null`:
-
-```kotlin
-label.iconConfiguration = null
-```
-
-**Q: 如何实现点击效果?**  
-A: 添加点击监听器并使用不同的 Appearance:
-
-```kotlin
-label.setOnClickListener {
-    label.setAppearanceProvider { theme ->
-        AtomicLabel.LabelAppearance(
-            textColor = Color.WHITE,
-            backgroundColor = theme.tokens.color.buttonColorPrimaryActive,
-            textSize = 14f,
-            textWeight = Typeface.NORMAL,
-            cornerRadius = 4f
-        )
-    }
-}
-```
-
-**Q: 如何禁用主题自动更新?**  
-A: 方式 1 - 使用固定的颜色值代替主题 tokens:
-
-```kotlin
-val label = AtomicLabel(context)
-label.text = "固定样式"
-label.setAppearanceProvider { _ ->
-    AtomicLabel.LabelAppearance(
-        textColor = Color.BLACK,
-        backgroundColor = Color.LTGRAY,
-        textSize = 14f,
-        textWeight = Typeface.NORMAL,
-        cornerRadius = 4f
-    )
-}
-```
-
-方式 2 - XML 中使用静态颜色:
-
-```xml
-<io.trtc.tuikit.atomicx.widget.basicwidget.label.AtomicLabel
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:text="固定样式"
-    android:textColor="#FF000000"
-    app:labelBackgroundColor="#FFDDDDDD"
-    app:labelCornerRadius="4dp" />
-```
-
-**Q: 图标模糊或变形怎么办?**  
-A: 使用矢量图(SVG)或提供 @2x/@3x 的 PNG 资源,并确保设置了正确的 `size`。
-
-**Q: XML 中设置的颜色没有跟随主题切换?**  
-A: 检查是否使用了颜色 Token 资源(如 `@color/text_color_primary`)而非静态颜色值(如 `#FF000000`)。只有 Token 颜色会跟随主题切换。
-
-**Q: 如何在 XML 中同时支持 Token 和静态颜色?**  
-A: 组件会自动识别:
-- `@color/text_color_primary` → 识别为 Token,跟随主题
-- `#FF000000` 或 `@color/custom_static_color` → 识别为静态颜色,不跟随主题
-
-## 完整示例
-
-### 代码方式
-
-```kotlin
-class ExampleActivity : AppCompatActivity() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        
-        val container = LinearLayout(this).apply {
-            orientation = LinearLayout.VERTICAL
-            setPadding(24, 24, 24, 24)
-        }
-        
-        // 1. 基础标签
-        val basicLabel = AtomicLabel(this)
-        basicLabel.text = "基础标签"
-        container.addView(basicLabel)
-        
-        // 2. 带图标的标签
-        val iconLabel = AtomicLabel(this)
-        iconLabel.text = "带图标"
-        iconLabel.iconConfiguration = AtomicLabel.IconConfiguration(
-            drawable = ContextCompat.getDrawable(this, R.drawable.ic_star),
-            position = AtomicLabel.IconConfiguration.Position.LEFT,
-            spacing = 8f,
-            size = Size(20, 20)
-        )
-        container.addView(iconLabel)
-        
-        // 3. 自定义样式标签
-        val customLabel = AtomicLabel(this)
-        customLabel.text = "自定义"
-        customLabel.setAppearanceProvider { theme ->
-            AtomicLabel.LabelAppearance(
-                textColor = Color.WHITE,
-                backgroundColor = theme.tokens.color.buttonColorPrimaryDefault,
-                textSize = theme.tokens.font.bold16.size,
-                textWeight = Typeface.BOLD,
-                cornerRadius = 8f
-            )
-        }
-        customLabel.setPadding(16, 8, 16, 8)
-        container.addView(customLabel)
-        
-        setContentView(container)
-    }
-}
-```
-
-### XML 方式
-
-```xml
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout 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"
-    android:padding="24dp">
-
-    <!-- 基础标签 -->
-    <io.trtc.tuikit.atomicx.widget.basicwidget.label.AtomicLabel
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="基础标签" />
-
-    <!-- 使用颜色 Token(跟随主题) -->
-    <io.trtc.tuikit.atomicx.widget.basicwidget.label.AtomicLabel
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="主题化标签"
-        android:textColor="@color/text_color_primary"
-        app:labelBackgroundColor="@color/bg_color_function"
-        app:labelCornerRadius="8dp"
-        android:padding="12dp" />
-
-    <!-- 使用静态颜色(不跟随主题) -->
-    <io.trtc.tuikit.atomicx.widget.basicwidget.label.AtomicLabel
-        android:id="@+id/staticLabel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="静态样式标签"
-        android:textColor="#FFFFFFFF"
-        app:labelBackgroundColor="#FF4086FF"
-        app:labelCornerRadius="12dp"
-        android:padding="16dp" />
-
-</LinearLayout>
-```

+ 0 - 441
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/popover/AtomicPopover.kt

@@ -1,441 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.basicwidget.popover
-
-import android.animation.Animator
-import android.animation.ValueAnimator
-import android.app.Activity
-import android.app.Dialog
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.graphics.drawable.GradientDrawable
-import android.os.Bundle
-import android.view.ContextThemeWrapper
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.view.Window
-import android.view.WindowManager
-import android.view.animation.DecelerateInterpolator
-import android.widget.FrameLayout
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.pictureinpicture.PictureInPictureStore
-import io.trtc.tuikit.atomicx.theme.ThemeStore
-import io.trtc.tuikit.atomicx.theme.tokens.DesignTokenSet
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlin.math.roundToInt
-
-open class AtomicPopover(
-    context: Context,
-    private val panelGravity: PanelGravity = PanelGravity.BOTTOM,
-) : Dialog(
-    context,
-    when (panelGravity) {
-        PanelGravity.BOTTOM -> R.style.dialogStyleFromBottom
-        PanelGravity.CENTER -> R.style.dialogStyleCenter
-    }
-) {
-    private val DIALOG_WIDTH_RATIO = 0.80
-
-    private val dialogScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
-    private val rootContainer: FrameLayout
-    private val contentContainer: MaxHeightFrameLayout
-
-    private var themeJob: Job? = null
-    private var panelHeight: PanelHeight = PanelHeight.WrapContent
-    private var isAnimating = false
-    private val showAnimation: Boolean = panelGravity == PanelGravity.BOTTOM
-    private var useTransparentBackground: Boolean = false
-    private var showMask: Boolean = true
-
-    enum class PanelGravity {
-        BOTTOM, CENTER
-    }
-
-    sealed class PanelHeight {
-        object WrapContent : PanelHeight()
-        data class Ratio(val value: Float) : PanelHeight()
-    }
-
-    init {
-        contentContainer = MaxHeightFrameLayout(context).apply {
-            val screenHeight = context.resources.displayMetrics.heightPixels
-            maxHeight = if (panelGravity == PanelGravity.BOTTOM) {
-                (screenHeight * 0.9f).toInt()
-            } else {
-                0
-            }
-
-            layoutParams = when (panelGravity) {
-                PanelGravity.BOTTOM -> FrameLayout.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT
-                ).apply { gravity = Gravity.BOTTOM }
-
-                PanelGravity.CENTER -> FrameLayout.LayoutParams(
-                    (context.resources.displayMetrics.widthPixels * DIALOG_WIDTH_RATIO).toInt(),
-                    ViewGroup.LayoutParams.WRAP_CONTENT
-                ).apply { gravity = Gravity.CENTER }
-            }
-            isClickable = true
-        }
-
-        rootContainer = FrameLayout(context).apply {
-            layoutParams = when (panelGravity) {
-                PanelGravity.BOTTOM -> ViewGroup.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.MATCH_PARENT
-                )
-
-                PanelGravity.CENTER -> ViewGroup.LayoutParams(
-                    ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT
-                )
-            }
-            setOnClickListener {
-                dismiss()
-            }
-            setBackgroundColor(Color.TRANSPARENT)
-            addView(contentContainer)
-        }
-    }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        window?.requestFeature(Window.FEATURE_NO_TITLE)
-
-        super.onCreate(savedInstanceState)
-        setContentView(rootContainer)
-
-        window?.apply {
-            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
-            decorView.setBackgroundColor(Color.TRANSPARENT)
-            setWindowAnimations(0)
-            setGravity(panelGravity.toAndroidGravity())
-
-            when (panelGravity) {
-                PanelGravity.BOTTOM -> {
-                    setLayout(
-                        ViewGroup.LayoutParams.MATCH_PARENT,
-                        ViewGroup.LayoutParams.MATCH_PARENT
-                    )
-                }
-
-                PanelGravity.CENTER -> {
-                    setLayout(
-                        ViewGroup.LayoutParams.WRAP_CONTENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT
-                    )
-                }
-            }
-
-            if (showMask) {
-                addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
-            }
-            addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
-        }
-
-        setPanelBackground()
-        setMaskColor()
-    }
-
-
-    override fun onStart() {
-        super.onStart()
-
-        themeJob?.cancel()
-        themeJob = dialogScope.launch {
-            launch {
-                ThemeStore.shared(context).themeState.collectLatest {
-                    setPanelBackground()
-                    setMaskColor()
-                }
-
-            }
-
-            launch {
-                PictureInPictureStore.shared.state.isPictureInPictureMode.collectLatest {
-                    if (it) {
-                        dismiss()
-                    }
-                }
-            }
-
-        }
-    }
-
-    override fun onStop() {
-        themeJob?.cancel()
-        themeJob = null
-        super.onStop()
-    }
-
-    override fun show() {
-        super.show()
-
-        if (showAnimation) {
-            contentContainer.translationY = contentContainer.height.toFloat()
-            contentContainer.visibility = View.INVISIBLE
-
-            contentContainer.post {
-                updatePanelHeight()
-
-                contentContainer.postDelayed({
-                    showWithAnimation()
-                }, 16)
-            }
-        } else {
-            contentContainer.visibility = View.VISIBLE
-        }
-    }
-
-    override fun dismiss() {
-        if (isAnimating) return
-        if (!isActivityValid()) {
-            return
-        }
-        if (showAnimation) {
-            dismissWithAnimation()
-        } else {
-            super.dismiss()
-        }
-    }
-
-    fun setContent(view: View) {
-        (view.parent as? ViewGroup)?.removeView(view)
-        contentContainer.removeAllViews()
-        contentContainer.addView(
-            view,
-            ViewGroup.LayoutParams.MATCH_PARENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-        )
-
-        if (isShowing) {
-            contentContainer.post {
-                updatePanelHeight()
-            }
-        }
-    }
-
-    fun setPanelHeight(value: PanelHeight) {
-        this.panelHeight = value
-        if (isShowing) {
-            updatePanelHeight()
-        }
-    }
-
-    fun setTransparentBackground(transparent: Boolean) {
-        useTransparentBackground = transparent
-        if (isShowing) {
-            setPanelBackground()
-        }
-    }
-
-    fun setShowMask(show: Boolean) {
-        showMask = show
-        if (isShowing) {
-            setMaskColor()
-        }
-    }
-
-    private fun showWithAnimation() {
-        contentContainer.visibility = View.VISIBLE
-
-        val startY = contentContainer.height.toFloat()
-        val endY = 0f
-
-        ValueAnimator.ofFloat(startY, endY).apply {
-            duration = 250
-            interpolator = DecelerateInterpolator()
-
-            addUpdateListener { animator ->
-                contentContainer.translationY = animator.animatedValue as Float
-            }
-
-            start()
-        }
-    }
-
-    private fun dismissWithAnimation() {
-        isAnimating = true
-
-        val startY = contentContainer.translationY
-        val endY = contentContainer.height.toFloat()
-
-        ValueAnimator.ofFloat(startY, endY).apply {
-            duration = 200
-            interpolator = DecelerateInterpolator()
-
-            addUpdateListener { animator ->
-                contentContainer.translationY = animator.animatedValue as Float
-            }
-
-            doOnEnd {
-                isAnimating = false
-                if (isActivityValid()) {
-                    super@AtomicPopover.dismiss()
-                }
-            }
-
-            start()
-        }
-    }
-
-    private fun updatePanelHeight() {
-        if (panelGravity == PanelGravity.CENTER) {
-            return
-        }
-
-        val screenHeight = context.resources.displayMetrics.heightPixels
-        val screenWidth = context.resources.displayMetrics.widthPixels
-        val maxHeight = (screenHeight * 0.9f).toInt()
-
-        contentContainer.maxHeight = maxHeight
-
-        val layoutParams = contentContainer.layoutParams as FrameLayout.LayoutParams
-        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
-
-        when (panelHeight) {
-            is PanelHeight.WrapContent -> {
-                layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
-
-                val widthSpec =
-                    View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.EXACTLY)
-                val heightSpec =
-                    View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
-
-                contentContainer.measure(widthSpec, heightSpec)
-            }
-
-            is PanelHeight.Ratio -> {
-                val ratio = (panelHeight as PanelHeight.Ratio).value.coerceIn(0f, 1f)
-                layoutParams.height = (screenHeight * ratio).roundToInt()
-            }
-        }
-
-        contentContainer.requestLayout()
-    }
-
-    private fun getCurrentTokens(context: Context): DesignTokenSet {
-        return ThemeStore.shared(context).themeState.value.currentTheme.tokens
-    }
-
-    private fun setMaskColor() {
-        if (!showMask) {
-            window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
-            window?.setDimAmount(0f)
-            return
-        }
-        window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
-        val maskColor = getCurrentTokens(context).color.bgColorMask
-        val dimAmount = Color.alpha(maskColor) / 255f
-        window?.setDimAmount(dimAmount)
-    }
-
-    private fun setPanelBackground() {
-        if (useTransparentBackground) {
-            contentContainer.background = null
-            return
-        }
-
-        val finalBgColor = getCurrentTokens(context).color.bgColorDialog
-        val bottomCornerRadiusPx = context.resources.getDimension(R.dimen.radius_20)
-        val centerCornerRadiusPx = context.resources.getDimension(R.dimen.radius_12)
-
-        val radii = when (panelGravity) {
-            PanelGravity.BOTTOM -> {
-                floatArrayOf(
-                    bottomCornerRadiusPx, bottomCornerRadiusPx,
-                    bottomCornerRadiusPx, bottomCornerRadiusPx,
-                    0f, 0f,
-                    0f, 0f
-                )
-            }
-
-            PanelGravity.CENTER -> {
-                floatArrayOf(
-                    centerCornerRadiusPx, centerCornerRadiusPx,
-                    centerCornerRadiusPx, centerCornerRadiusPx,
-                    centerCornerRadiusPx, centerCornerRadiusPx,
-                    centerCornerRadiusPx, centerCornerRadiusPx
-                )
-            }
-        }
-
-        val drawable = GradientDrawable().apply {
-            setColor(finalBgColor)
-            cornerRadii = radii
-        }
-        contentContainer.background = drawable
-    }
-
-    private fun PanelGravity.toAndroidGravity(): Int {
-        return when (this) {
-            PanelGravity.BOTTOM -> Gravity.BOTTOM
-            PanelGravity.CENTER -> Gravity.CENTER
-        }
-    }
-
-    private fun isActivityValid(): Boolean {
-        val currentContext = context
-        val currentActivity = if (currentContext is ContextThemeWrapper) {
-            currentContext.baseContext
-        } else {
-            currentContext
-        }
-        if (currentActivity is Activity) {
-            if (currentActivity.isFinishing || currentActivity.isDestroyed) {
-                return false
-            }
-        }
-        return true
-    }
-
-    private class MaxHeightFrameLayout(context: Context) : FrameLayout(context) {
-        var maxHeight: Int = 0
-
-        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-            var newHeightMeasureSpec = heightMeasureSpec
-
-            if (maxHeight > 0) {
-                val heightSize = MeasureSpec.getSize(heightMeasureSpec)
-                val heightMode = MeasureSpec.getMode(heightMeasureSpec)
-
-                newHeightMeasureSpec = when (heightMode) {
-                    MeasureSpec.EXACTLY -> MeasureSpec.makeMeasureSpec(
-                        minOf(heightSize, maxHeight),
-                        MeasureSpec.EXACTLY
-                    )
-
-                    MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec(
-                        minOf(heightSize, maxHeight),
-                        MeasureSpec.AT_MOST
-                    )
-
-                    else -> MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)
-                }
-            }
-
-            super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
-
-            if (maxHeight > 0 && measuredHeight > maxHeight) {
-                setMeasuredDimension(measuredWidth, maxHeight)
-            }
-        }
-    }
-}
-
-private fun ValueAnimator.doOnEnd(action: () -> Unit) {
-    addListener(object : Animator.AnimatorListener {
-        override fun onAnimationStart(animation: Animator) {}
-        override fun onAnimationEnd(animation: Animator) {
-            action()
-        }
-
-        override fun onAnimationCancel(animation: Animator) {}
-        override fun onAnimationRepeat(animation: Animator) {}
-    })
-}

+ 0 - 94
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/popover/README.md

@@ -1,94 +0,0 @@
-# AtomicPopover 弹窗容器
-
-`AtomicPopover` 是 AtomicX Android UIKit 的统一弹窗容器,内部整合了 **底部弹出面板** 与 **居中弹出对话框** 两种形态。开发者只需指定位置(`PanelGravity.BOTTOM` / `PanelGravity.CENTER`)即可获得一致的蒙层、主题与动画体验。
-
-## 文件结构
-
-```
-basicwidget/popover/
-├── AtomicPopover.kt        # 弹窗容器实现(本文档对应文件)
-└── ...                     # 相关工具类
-```
-
-## 快速开始
-
-### 1. 底部弹出(Bottom Sheet)
-
-```kotlin
-val bottomPopover = AtomicPopover(
-    context = context,
-    panelGravity = AtomicPopover.PanelGravity.BOTTOM
-)
-
-bottomPopover.setContent(AudioEffectView(context).apply { init(roomId = "12345") })
-bottomPopover.setPanelHeight(AtomicPopover.PanelHeight.Ratio(0.5f))
-
-bottomPopover.show()
-```
-
-### 2. 居中弹出(Center Dialog)
-
-```kotlin
-val centerPopover = AtomicPopover(
-    context = context,
-    panelGravity = AtomicPopover.PanelGravity.CENTER
-)
-
-val dialogLayout = LayoutInflater.from(context).inflate(R.layout.dialog_logout, null)
-centerPopover.setContent(dialogLayout)
-
-centerPopover.show()
-```
-
-> 两种模式均支持 `setContent` 注入任意自定义 View,`AtomicPopover` 仅负责容器、蒙层、圆角和动画。
-
-## 主要特性
-
-1. **双布局模式**:
-   - `PanelGravity.BOTTOM`:底部滑入,支持高度比设置,常用于操作面板、功能列表。
-   - `PanelGravity.CENTER`:居中弹出,宽度默认占屏幕 80%,适合提示类对话框。
-2. **灵活高度**:
-   - `PanelHeight.WrapContent`:内容自适应,最高不超过屏幕 90%。
-   - `PanelHeight.Ratio(value)`:以屏幕高度比例显示(0~1)。
-3. **主题联动**:自动监听 `ThemeStore`,背景、蒙层、圆角在主题切换时实时更新。
-4. **点击蒙层关闭**:默认点击外层区域即 `dismiss()`。
-5. **动画区分**:底部模式具备滑入/滑出动画,居中模式无位移动画,直接渐变呈现。
-
-## API 概览
-
-| 方法 | 说明 |
-| --- | --- |
-| `setContent(view: View)` | 设置弹窗内部显示的内容 View(会自动移除原父容器) |
-| `setPanelHeight(height: PanelHeight)` | 设置高度策略(仅当 `panelGravity = BOTTOM` 时有效) |
-| `show()` | 展示弹窗,自动应用对应动画与蒙层 |
-| `dismiss()` | 关闭弹窗;底部模式会播放下滑动画 |
-
-### PanelGravity
-
-```kotlin
-enum class PanelGravity {
-    BOTTOM,   // 底部弹出
-    CENTER    // 居中弹出
-}
-```
-
-### PanelHeight
-
-```kotlin
-sealed class PanelHeight {
-    object WrapContent : PanelHeight()               // 自适应内容高度
-    data class Ratio(val value: Float) : PanelHeight() // 按屏幕高度比例 (0.0 ~ 1.0)
-}
-```
-
-## 使用建议
-
-1. **内容布局**:`setContent` 会将子 View 宽度强制为容器宽度;居中模式下宽度为 80% 屏宽,可在子布局中自行设置内边距与圆角。
-2. **高度策略**:
-   - 表单、键盘场景建议 `WrapContent`。
-   - 功能面板/音效面板等强调展示区域可使用 `Ratio(0.5f)` 等固定比例。
-3. **复用 View**:若内容 View 需要复用,请确保在传入前与旧父容器解绑(`setContent` 会自动移除父容器,但仍建议避免状态冲突)。
-4. **主题切换**:无需手动刷新,`AtomicPopover` 会响应 `ThemeStore` 的变更自动更新背景与蒙层。
-
----
-如需更复杂的按钮 DSL、列表项等能力,可将自定义布局交由 `AtomicPopover` 承载,或结合 `AtomicAlertDialog` 等上层封装一起使用。

+ 0 - 190
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/toast/AtomicToast.kt

@@ -1,190 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.basicwidget.toast
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.graphics.drawable.GradientDrawable
-import android.os.Handler
-import android.os.Looper
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.ImageView
-import android.widget.TextView
-import android.widget.Toast
-import androidx.annotation.DrawableRes
-import com.tencent.imsdk.v2.V2TIMManager
-import io.trtc.tuikit.atomicx.R
-import io.trtc.tuikit.atomicx.theme.ThemeStore
-import io.trtc.tuikit.atomicx.theme.tokens.DesignTokenSet
-import org.json.JSONObject
-import java.lang.ref.WeakReference
-
-private const val ATOMIC_EVENT_ID = 100011
-private const val FRAMEWORK_NAME = "AtomicXCore"
-
-object AtomicToast {
-
-    enum class Style {
-        TEXT,
-        INFO,
-        HELP,
-        LOADING,
-        SUCCESS,
-        WARNING,
-        ERROR
-    }
-
-    enum class Position {
-        TOP,
-        CENTER,
-        BOTTOM
-    }
-
-    enum class Duration(val value: Int) {
-        SHORT(Toast.LENGTH_SHORT),
-        LONG(Toast.LENGTH_LONG);
-
-        companion object {
-            @JvmStatic
-            fun map(duration: Int): Duration {
-                return entries.find { it.value == duration } ?: SHORT
-            }
-        }
-    }
-
-    private var toastRef: WeakReference<Toast>? = null
-    private var layoutRef: WeakReference<View>? = null
-
-    private val mainHandler = Handler(Looper.getMainLooper())
-    private var pendingTask: Runnable? = null
-
-    fun show(
-        context: Context,
-        text: String,
-        style: Style = Style.TEXT,
-        position: Position = Position.CENTER,
-        @DrawableRes customIcon: Int? = null,
-        duration: Duration = Duration.SHORT,
-        code: Int? = null,
-        extensionInfo: Map<String, Any>? = null
-    ) {
-        if (text.isBlank()) return
-
-        pendingTask?.let {
-            mainHandler.removeCallbacks(it)
-            pendingTask = null
-        }
-
-        val appContext = context.applicationContext
-
-        val task = object : Runnable {
-            override fun run() {
-                executeRealShow(appContext, text, style, position, customIcon, duration)
-                if (code != null) {
-                    reportAtomicEvent(code, text, extensionInfo)
-                }
-                if (pendingTask === this) {
-                    pendingTask = null
-                }
-            }
-        }
-        pendingTask = task
-        mainHandler.postDelayed(task, 50)
-    }
-
-    private fun executeRealShow(
-        appContext: Context,
-        text: String,
-        style: Style,
-        position: Position,
-        @DrawableRes customIcon: Int?,
-        duration: Duration,
-    ) {
-        val themeStore = ThemeStore.shared(appContext)
-        val tokens = themeStore.themeState.value.currentTheme.tokens
-
-        var toast = toastRef?.get()
-        var layout = layoutRef?.get()
-
-        if (toast == null || layout == null) {
-            layout = LayoutInflater.from(appContext).inflate(R.layout.layout_atomic_toast, null)
-            toast = Toast(appContext)
-
-            @Suppress("DEPRECATION")
-            toast.view = layout
-
-            layoutRef = WeakReference(layout)
-            toastRef = WeakReference(toast)
-        }
-
-        applyDesignTokens(appContext, layout!!, text, style, customIcon, tokens)
-
-        toast.apply {
-            this.duration = duration.value
-            val gravity = when (position) {
-                Position.TOP -> Gravity.TOP or Gravity.CENTER_HORIZONTAL
-                Position.CENTER -> Gravity.CENTER
-                Position.BOTTOM -> Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
-            }
-            val yOffset = appContext.resources.getDimension(R.dimen.spacing_20).toInt()
-            setGravity(gravity, 0, if (position == Position.CENTER) 0 else yOffset)
-            show()
-        }
-    }
-
-    @SuppressLint("SetTextI18n")
-    private fun applyDesignTokens(
-        context: Context,
-        container: View,
-        text: String,
-        style: Style,
-        @DrawableRes customIcon: Int?,
-        tokens: DesignTokenSet
-    ) {
-        val iconView = container.findViewById<ImageView>(R.id.image_icon)
-        val textView = container.findViewById<TextView>(R.id.text_toast)
-
-        val backgroundDrawable = GradientDrawable().apply {
-            shape = GradientDrawable.RECTANGLE
-            cornerRadius = context.resources.getDimension(R.dimen.radius_6)
-            setColor(tokens.color.bgColorOperate)
-        }
-
-        container.background = backgroundDrawable
-
-        textView.text = text
-        textView.setTextColor(tokens.color.textColorPrimary)
-        textView.textSize = tokens.font.regular14.size
-
-        iconView.visibility = View.GONE
-        val iconRes = customIcon ?: resolveIconRes(style)
-        if (iconRes != null) {
-            iconView.visibility = View.VISIBLE
-            iconView.setImageResource(iconRes)
-        }
-    }
-
-    @DrawableRes
-    private fun resolveIconRes(style: Style): Int? {
-        return when (style) {
-            Style.INFO -> R.drawable.ic_atomic_toast_info
-            Style.HELP -> R.drawable.ic_atomic_toast_help
-            Style.SUCCESS -> R.drawable.ic_atomic_toast_success
-            Style.WARNING -> R.drawable.ic_atomic_toast_warning
-            Style.ERROR -> R.drawable.ic_atomic_toast_error
-            Style.LOADING -> R.drawable.ic_atomic_toast_loading
-            else -> null
-        }
-    }
-
-    private fun reportAtomicEvent(code: Int, message: String?, extensionInfo: Map<String, Any>?) {
-        val params = JSONObject().apply {
-            put("event_id", ATOMIC_EVENT_ID)
-            put("event_code", code)
-            put("event_message", message)
-            put("more_message", FRAMEWORK_NAME)
-            put("extension_message", extensionInfo)
-        }.toString()
-        V2TIMManager.getInstance().callExperimentalAPI("reportRoomEngineEvent", params, null)
-    }
-}

+ 0 - 237
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/basicwidget/toast/README.md

@@ -1,237 +0,0 @@
-# AtomicToast 轻量级提示组件
-
-AtomicToast 是一个轻量级的 Android Toast 提示组件,基于系统 Toast 封装,提供了丰富的语义化样式和自定义选项。它遵循 AtomicX 设计系统,支持多种位置、样式和图标配置。
-
-## 文件结构
-
-```
-toast/
-├── AtomicToast.kt          # Toast 组件核心实现
-└── README.md               # 本文件
-```
-
-## 快速开始
-
-AtomicToast 采用单例模式设计,通过静态方法直接调用,无需创建实例。
-
-### 1. 基础文本提示
-
-最简单的文本提示,无图标。
-
-```kotlin
-AtomicToast.show(
-    context = this,
-    text = "这是一条提示信息"
-)
-```
-
-### 2. 成功提示
-
-带有成功图标的提示,适用于操作完成场景。
-
-```kotlin
-AtomicToast.show(
-    context = this,
-    text = "操作成功!",
-    style = AtomicToast.Style.SUCCESS
-)
-```
-
-### 3. 警告和错误提示
-
-用于警告或错误场景,提供视觉上的区分。
-
-```kotlin
-// 警告
-AtomicToast.show(
-    context = this,
-    text = "请注意:这是警告信息",
-    style = AtomicToast.Style.WARNING
-)
-
-// 错误
-AtomicToast.show(
-    context = this,
-    text = "操作失败,请重试",
-    style = AtomicToast.Style.ERROR
-)
-```
-
-### 4. 加载提示
-
-用于异步操作时显示加载状态。
-
-```kotlin
-// 显示加载
-AtomicToast.show(
-    context = this,
-    text = "正在加载...",
-    style = AtomicToast.Style.LOADING,
-    position = AtomicToast.Position.CENTER,
-    duration = AtomicToast.Duration.LONG
-)
-```
-
-### 5. 不同位置显示
-
-支持顶部、居中、底部三种位置。
-
-```kotlin
-// 顶部
-AtomicToast.show(
-    context = this,
-    text = "顶部显示的 Toast",
-    position = AtomicToast.Position.TOP
-)
-
-// 居中
-AtomicToast.show(
-    context = this,
-    text = "居中显示的 Toast",
-    position = AtomicToast.Position.CENTER
-)
-
-// 底部
-AtomicToast.show(
-    context = this,
-    text = "底部显示的 Toast",
-    position = AtomicToast.Position.BOTTOM
-)
-```
-
-### 6. 自定义图标
-
-支持传入自定义图标资源。
-
-```kotlin
-AtomicToast.show(
-    context = this,
-    text = "自定义图标示例",
-    style = AtomicToast.Style.TEXT,
-    customIcon = R.drawable.ic_custom_icon
-)
-```
-
-## 核心 API
-
-### `show()` 方法参数
-
-| 参数名 | 类型 | 说明 | 默认值 |
-| :--- | :--- | :--- | :--- |
-| `context` | Context | 上下文(建议使用 Application Context) | (必填) |
-| `text` | String | 显示的文本内容 | (必填) |
-| `style` | Style | 语义化样式(见下方枚举) | Style.TEXT |
-| `position` | Position | 显示位置(见下方枚举) | Position.CENTER |
-| `customIcon` | Int? | 自定义图标资源 ID | null |
-| `duration` | Duration | 显示时长(见下方枚举) | Duration.SHORT |
-
-### 样式枚举 (Style)
-
-| 枚举值 | 说明 | 默认图标 |
-| :--- | :--- | :--- |
-| `TEXT` | 纯文本,无图标 | 无 |
-| `INFO` | 信息提示 | 信息图标 |
-| `HELP` | 帮助提示 | 帮助图标 |
-| `LOADING` | 加载中 | 加载动画图标 |
-| `SUCCESS` | 成功提示 | 成功图标 |
-| `WARNING` | 警告提示 | 警告图标 |
-| `ERROR` | 错误提示 | 错误图标 |
-
-### 位置枚举 (Position)
-
-| 枚举值 | 说明 | 适用场景 |
-| :--- | :--- | :--- |
-| `TOP` | 顶部显示 | 通知类消息 |
-| `CENTER` | 居中显示 | 加载、重要提示 |
-| `BOTTOM` | 底部显示 | 常规反馈 |
-
-> **注意**:Android 11 (API 30+) 对自定义 Toast 的位置做了限制,在部分设备上可能强制显示在底部。
-
-### 时长枚举 (Duration)
-
-| 枚举值 | 说明 | 对应系统值 |
-| :--- | :--- | :--- |
-| `SHORT` | 短时显示 | Toast.LENGTH_SHORT (约 2 秒) |
-| `LONG` | 长时显示 | Toast.LENGTH_LONG (约 3.5 秒) |
-
-
-## 动态主题 (Dynamic Theming)
-
-`AtomicToast` 内置了对 `ThemeStore` 的支持,会自动应用当前主题的配置:
-
-- **背景颜色**:使用 `tokens.color.bgColorOperate`
-- **文本颜色**:使用 `tokens.color.textColorPrimary`
-- **字体样式**:使用 `tokens.font.regular14`
-- **圆角大小**:使用 `@dimen/radius_6`
-
-当应用主题切换时,新创建的 Toast 会自动应用新主题样式。
-
-## 使用场景
-
-### 1. 表单提交反馈
-
-```kotlin
-viewModel.submitForm().observe(this) { result ->
-    when (result) {
-        is Success -> AtomicToast.show(this, "提交成功!", AtomicToast.Style.SUCCESS)
-        is Error -> AtomicToast.show(this, result.message, AtomicToast.Style.ERROR)
-    }
-}
-```
-
-### 2. 网络请求状态
-
-```kotlin
-// 开始请求
-AtomicToast.show(
-    context = this,
-    text = "正在加载数据...",
-    style = AtomicToast.Style.LOADING,
-    duration = AtomicToast.Duration.LONG
-)
-
-// 请求完成
-api.fetchData().onComplete {
-    AtomicToast.show(this, "加载完成", AtomicToast.Style.SUCCESS)
-}
-```
-
-### 3. 复制到剪贴板
-
-```kotlin
-button.setOnClickListener {
-    copyToClipboard(text)
-    AtomicToast.show(this, "已复制到剪贴板", AtomicToast.Style.SUCCESS)
-}
-```
-
-## 注意事项
-
-1. **Context 选择**:建议传入 `Application Context` 以避免内存泄漏,特别是在长时间显示的场景。
-
-2. **自动去重**:组件内部会自动取消上一个 Toast,避免 Toast 队列堆积。
-
-3. **文本验证**:如果传入的文本为空(`isBlank()`),Toast 不会显示。
-
-4. **API 30+ 限制**:Android 11 及以上版本对自定义 Toast 的 `setView()` 方法标记为 deprecated,但对前台应用目前仍然有效。组件内部已添加 `@Suppress("DEPRECATION")` 注解处理。
-
-5. **线程安全**:Toast 必须在主线程调用,如果在子线程使用,需要切换到主线程:
-
-```kotlin
-withContext(Dispatchers.Main) {
-    AtomicToast.show(context, "提示信息")
-}
-```
-
-## 设计规范
-
-AtomicToast 遵循以下设计规范:
-
-- **最大宽度**:340dp
-- **最大行数**:2 行,超出部分显示省略号
-- **内边距**:水平 16dp,垂直 8dp
-- **最小高度**:40dp
-- **图标尺寸**:16dp × 16dp
-- **图标间距**:图标与文字间距 4dp
-- **圆角大小**:6dp
-- **字体大小**:14sp

+ 0 - 337
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/BlurUtils.kt

@@ -1,337 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.utils
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.os.Build
-import android.renderscript.Allocation
-import android.renderscript.Element
-import android.renderscript.RenderScript
-import android.renderscript.ScriptIntrinsicBlur
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.min
-
-object BlurUtils {
-    private const val SAMPLING = 10f
-
-    @JvmStatic
-    fun blur(context: Context, pool: BitmapPool, toTransform: Bitmap, radius: Int): Bitmap {
-        val width = toTransform.width
-        val height = toTransform.height
-        val scaleWidth = (width / SAMPLING).toInt()
-        val scaleHeight = (height / SAMPLING).toInt()
-
-        val bitmap = pool.get(scaleWidth, scaleHeight, Bitmap.Config.ARGB_8888)
-        bitmap.density = toTransform.density
-
-        val canvas = Canvas(bitmap)
-        canvas.scale(1 / SAMPLING, 1 / SAMPLING)
-        val paint = Paint()
-        paint.flags = Paint.FILTER_BITMAP_FLAG
-        canvas.drawBitmap(toTransform, 0f, 0f, paint)
-
-        var rs: RenderScript? = null
-        var input: Allocation? = null
-        var output: Allocation? = null
-        var blur: ScriptIntrinsicBlur? = null
-
-        try {
-            rs = RenderScript.create(context)
-            rs.messageHandler = RenderScript.RSMessageHandler()
-            input = Allocation.createFromBitmap(
-                rs, bitmap,
-                Allocation.MipmapControl.MIPMAP_NONE,
-                Allocation.USAGE_SCRIPT
-            )
-            output = Allocation.createTyped(rs, input.type)
-            blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
-            blur.setRadius(radius.toFloat())
-            blur.setInput(input)
-            blur.forEach(output)
-            output.copyTo(bitmap)
-        } catch (e: Exception) {
-            e.printStackTrace()
-            fastBlur(bitmap, radius, true)
-        } finally {
-            rs?.let {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                    RenderScript.releaseAllContexts()
-                } else {
-                    @Suppress("DEPRECATION")
-                    it.destroy()
-                }
-            }
-            input?.destroy()
-            output?.destroy()
-            blur?.destroy()
-        }
-
-        return bitmap
-    }
-
-    @JvmStatic
-    fun blur(context: Context, bitmap: Bitmap, radius: Int): Bitmap {
-        var rs: RenderScript? = null
-        var input: Allocation? = null
-        var output: Allocation? = null
-        var blur: ScriptIntrinsicBlur? = null
-
-        try {
-            rs = RenderScript.create(context)
-            rs.messageHandler = RenderScript.RSMessageHandler()
-            input = Allocation.createFromBitmap(
-                rs, bitmap,
-                Allocation.MipmapControl.MIPMAP_NONE,
-                Allocation.USAGE_SCRIPT
-            )
-            output = Allocation.createTyped(rs, input.type)
-            blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
-            blur.setRadius(radius.toFloat())
-            blur.setInput(input)
-            blur.forEach(output)
-            output.copyTo(bitmap)
-        } catch (e: Exception) {
-            e.printStackTrace()
-            fastBlur(bitmap, radius, true)
-        } finally {
-            rs?.let {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                    RenderScript.releaseAllContexts()
-                } else {
-                    @Suppress("DEPRECATION")
-                    it.destroy()
-                }
-            }
-            input?.destroy()
-            output?.destroy()
-            blur?.destroy()
-        }
-
-        return bitmap
-    }
-
-    @JvmStatic
-    fun fastBlur(sentBitmap: Bitmap, radius: Int, canReuseInBitmap: Boolean): Bitmap {
-        val bitmap = if (canReuseInBitmap) {
-            sentBitmap
-        } else {
-            sentBitmap.copy(sentBitmap.config!!, true)
-        }
-
-        if (radius < 1) {
-            return sentBitmap
-        }
-
-        val w = bitmap.width
-        val h = bitmap.height
-        val pix = IntArray(w * h)
-        bitmap.getPixels(pix, 0, w, 0, 0, w, h)
-
-        val wm = w - 1
-        val hm = h - 1
-        val wh = w * h
-        val div = radius + radius + 1
-
-        val r = IntArray(wh)
-        val g = IntArray(wh)
-        val b = IntArray(wh)
-
-        val vmin = IntArray(max(w, h))
-
-        var divsum = (div + 1) shr 1
-        divsum *= divsum
-        val dv = IntArray(256 * divsum)
-        for (i in 0 until 256 * divsum) {
-            dv[i] = i / divsum
-        }
-
-        var yi = 0
-        var yw = yi
-
-        val stack = Array(div) { IntArray(3) }
-        val r1 = radius + 1
-        var routsum: Int
-        var goutsum: Int
-        var boutsum: Int
-        var rinsum: Int
-        var ginsum: Int
-        var binsum: Int
-
-        for (y in 0 until h) {
-            var rsum = 0
-            var gsum = 0
-            var bsum = 0
-            routsum = 0
-            goutsum = 0
-            boutsum = 0
-            rinsum = 0
-            ginsum = 0
-            binsum = 0
-
-            for (i in -radius..radius) {
-                val p = pix[yi + min(wm, max(i, 0))]
-                val sir = stack[i + radius]
-                sir[0] = (p and 0xff0000) shr 16
-                sir[1] = (p and 0x00ff00) shr 8
-                sir[2] = p and 0x0000ff
-                val rbs = r1 - abs(i)
-                rsum += sir[0] * rbs
-                gsum += sir[1] * rbs
-                bsum += sir[2] * rbs
-                if (i > 0) {
-                    rinsum += sir[0]
-                    ginsum += sir[1]
-                    binsum += sir[2]
-                } else {
-                    routsum += sir[0]
-                    goutsum += sir[1]
-                    boutsum += sir[2]
-                }
-            }
-
-            var stackpointer = radius
-            var x = 0
-            while (x < w) {
-                r[yi] = dv[rsum]
-                g[yi] = dv[gsum]
-                b[yi] = dv[bsum]
-
-                rsum -= routsum
-                gsum -= goutsum
-                bsum -= boutsum
-
-                val stackstart = stackpointer - radius + div
-                var sir = stack[stackstart % div]
-
-                routsum -= sir[0]
-                goutsum -= sir[1]
-                boutsum -= sir[2]
-
-                if (y == 0) {
-                    vmin[x] = min(x + radius + 1, wm)
-                }
-                val p = pix[yw + vmin[x]]
-
-                sir[0] = (p and 0xff0000) shr 16
-                sir[1] = (p and 0x00ff00) shr 8
-                sir[2] = p and 0x0000ff
-
-                rinsum += sir[0]
-                ginsum += sir[1]
-                binsum += sir[2]
-
-                rsum += rinsum
-                gsum += ginsum
-                bsum += binsum
-
-                stackpointer = (stackpointer + 1) % div
-                sir = stack[stackpointer % div]
-
-                routsum += sir[0]
-                goutsum += sir[1]
-                boutsum += sir[2]
-
-                rinsum -= sir[0]
-                ginsum -= sir[1]
-                binsum -= sir[2]
-
-                yi++
-                x++
-            }
-            yw += w
-        }
-
-        for (x in 0 until w) {
-            var rsum = 0
-            var gsum = 0
-            var bsum = 0
-            routsum = 0
-            goutsum = 0
-            boutsum = 0
-            rinsum = 0
-            ginsum = 0
-            binsum = 0
-            var yp = -radius * w
-
-            for (i in -radius..radius) {
-                yi = max(0, yp) + x
-                val sir = stack[i + radius]
-                sir[0] = r[yi]
-                sir[1] = g[yi]
-                sir[2] = b[yi]
-                val rbs = r1 - abs(i)
-                rsum += r[yi] * rbs
-                gsum += g[yi] * rbs
-                bsum += b[yi] * rbs
-                if (i > 0) {
-                    rinsum += sir[0]
-                    ginsum += sir[1]
-                    binsum += sir[2]
-                } else {
-                    routsum += sir[0]
-                    goutsum += sir[1]
-                    boutsum += sir[2]
-                }
-                if (i < hm) {
-                    yp += w
-                }
-            }
-
-            yi = x
-            var stackpointer = radius
-            for (y in 0 until h) {
-                pix[yi] = (pix[yi] and -0x1000000.toInt()) or
-                        (dv[rsum] shl 16) or
-                        (dv[gsum] shl 8) or
-                        dv[bsum]
-
-                rsum -= routsum
-                gsum -= goutsum
-                bsum -= boutsum
-
-                val stackstart = stackpointer - radius + div
-                var sir = stack[stackstart % div]
-
-                routsum -= sir[0]
-                goutsum -= sir[1]
-                boutsum -= sir[2]
-
-                if (x == 0) {
-                    vmin[y] = min(y + r1, hm) * w
-                }
-                val p = x + vmin[y]
-
-                sir[0] = r[p]
-                sir[1] = g[p]
-                sir[2] = b[p]
-
-                rinsum += sir[0]
-                ginsum += sir[1]
-                binsum += sir[2]
-
-                rsum += rinsum
-                gsum += ginsum
-                bsum += binsum
-
-                stackpointer = (stackpointer + 1) % div
-                sir = stack[stackpointer]
-
-                routsum += sir[0]
-                goutsum += sir[1]
-                boutsum += sir[2]
-
-                rinsum -= sir[0]
-                ginsum -= sir[1]
-                binsum -= sir[2]
-
-                yi += w
-            }
-        }
-
-        bitmap.setPixels(pix, 0, w, 0, 0, w, h)
-        return bitmap
-    }
-}

+ 0 - 22
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/DisplayUtil.kt

@@ -1,22 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.utils
-
-import android.content.Context
-import androidx.annotation.FloatRange
-import androidx.annotation.IntRange
-
-
-object DisplayUtil {
-
-    @JvmStatic
-    @IntRange(from = 0)
-    fun dp2px(context: Context, @FloatRange(from = 0.0) dpValue: Float): Int {
-        val scale = context.resources.displayMetrics.density
-        return (dpValue * scale + 0.5f).toInt()
-    }
-
-    @JvmStatic
-    fun px2dp(context: Context, pxValue: Float): Int {
-        val scale = context.resources.displayMetrics.density
-        return (pxValue / scale + 0.5f).toInt()
-    }
-}

+ 0 - 185
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/ImageLoader.kt

@@ -1,185 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.utils
-
-import android.app.Activity
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.RenderEffect
-import android.graphics.Shader
-import android.os.Build
-import android.util.TypedValue
-import android.widget.ImageView
-import androidx.annotation.DrawableRes
-import androidx.annotation.RawRes
-import com.bumptech.glide.Glide
-import com.bumptech.glide.RequestBuilder
-import com.bumptech.glide.load.MultiTransformation
-import com.bumptech.glide.load.Transformation
-import com.bumptech.glide.load.engine.DiskCacheStrategy
-import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
-import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
-import com.bumptech.glide.load.resource.bitmap.CenterCrop
-import com.bumptech.glide.load.resource.bitmap.RoundedCorners
-import com.bumptech.glide.request.RequestOptions
-import java.security.MessageDigest
-
-object ImageLoader {
-
-    @JvmStatic
-    fun load(context: Context, target: ImageView?, source: Any?, @DrawableRes placeImage: Int) {
-        val imageOptions = ImageOptions.Builder()
-            .setPlaceImage(placeImage)
-            .build()
-        load(context, target, source, imageOptions)
-    }
-
-    @JvmStatic
-    fun load(context: Context, target: ImageView?, source: Any?, config: ImageOptions) {
-        if (target == null) {
-            return
-        }
-
-        if (source == null) {
-            val image = if (config.placeImage != 0) config.placeImage else config.errorImage
-            target.setImageResource(image)
-            return
-        }
-
-        loadImageView(context, target, source, config)
-    }
-
-    @JvmStatic
-    fun loadGif(context: Context, target: ImageView?, @RawRes @DrawableRes resourceId: Int) {
-        if (target == null) {
-            return
-        }
-        Glide.with(context.applicationContext)
-            .asGif()
-            .load(resourceId)
-            .into(target)
-    }
-
-    @JvmStatic
-    fun transformBitmap(
-        context: Context,
-        source: Any?,
-        width: Int,
-        height: Int,
-        options: ImageOptions
-    ): Bitmap? {
-        if (source == null) {
-            return null
-        }
-
-        val builder = Glide.with(context.applicationContext)
-            .asBitmap()
-            .load(source)
-
-        setBuilderOptions(context.applicationContext, builder, options)
-
-        return try {
-            builder.submit(width, height).get()
-        } catch (e: Exception) {
-            e.printStackTrace()
-            null
-        }
-    }
-
-    @JvmStatic
-    fun clear(context: Context, target: ImageView?) {
-        if (target != null) {
-            Glide.with(context.applicationContext).clear(target)
-        }
-    }
-
-    private fun loadImageView(context: Context, target: ImageView, source: Any, config: ImageOptions) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            if (context is Activity) {
-                if (context.isFinishing || context.isDestroyed) {
-                    return
-                }
-            }
-        }
-
-        val builder = if (config.isGif) {
-            Glide.with(context.applicationContext).asGif().load(source)
-        } else {
-            Glide.with(context.applicationContext).load(source)
-        }
-
-        setBuilderOptions(context.applicationContext, builder, config)
-        builder.into(target)
-        setRenderEffect(target, config)
-    }
-
-    private fun setRenderEffect(target: ImageView, config: ImageOptions) {
-        val level = config.blurEffect
-        if (level > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            target.setRenderEffect(
-                RenderEffect.createBlurEffect(level, level, Shader.TileMode.MIRROR)
-            )
-        }
-    }
-
-    private fun setBuilderOptions(context: Context, builder: RequestBuilder<*>, config: ImageOptions) {
-        val options = RequestOptions()
-
-        val transformations = mutableListOf<Transformation<Bitmap>>()
-        transformations.add(CenterCrop())
-
-        if (config.roundRadius > 0) {
-            transformations.add(RoundedCorners(dpToPx(context, config.roundRadius)))
-        }
-
-        if (config.blurEffect > 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
-            transformations.add(BlurTransformation(context, config.blurEffect))
-        }
-
-        options.transform(MultiTransformation(transformations))
-
-        if (config.placeImage != 0) {
-            options.placeholder(config.placeImage)
-        }
-
-        if (config.errorImage != 0) {
-            options.error(config.errorImage)
-        }
-
-        options.diskCacheStrategy(
-            if (config.skipDiskCache) DiskCacheStrategy.NONE else DiskCacheStrategy.ALL
-        )
-        options.skipMemoryCache(config.skipMemoryCache)
-
-        builder.apply(options)
-    }
-
-    private fun dpToPx(context: Context, dp: Int): Int {
-        return TypedValue.applyDimension(
-            TypedValue.COMPLEX_UNIT_DIP,
-            dp.toFloat(),
-            context.resources.displayMetrics
-        ).toInt()
-    }
-
-    class BlurTransformation(
-        context: Context,
-        level: Float
-    ) : BitmapTransformation() {
-
-        private val radius: Float = level * 0.25f
-        private val contextRef = java.lang.ref.WeakReference(context)
-
-        override fun updateDiskCacheKey(messageDigest: MessageDigest) {
-            messageDigest.update("blur:$radius".toByteArray())
-        }
-
-        override fun transform(
-            pool: BitmapPool,
-            toTransform: Bitmap,
-            outWidth: Int,
-            outHeight: Int
-        ): Bitmap {
-            val context = contextRef.get() ?: return toTransform
-            return BlurUtils.blur(context, pool, toTransform, radius.toInt())
-        }
-    }
-}

+ 0 - 69
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/widget/utils/ImageOptions.kt

@@ -1,69 +0,0 @@
-package io.trtc.tuikit.atomicx.widget.utils
-
-import androidx.annotation.DrawableRes
-
-data class ImageOptions(
-    @DrawableRes val placeImage: Int = 0,
-    @DrawableRes val errorImage: Int = 0,
-    val roundRadius: Int = 0,
-    val isGif: Boolean = false,
-    val skipMemoryCache: Boolean = false,
-    val skipDiskCache: Boolean = false,
-    val blurEffect: Float = 0f
-) {
-    class Builder {
-        private var placeImage: Int = 0
-        private var errorImage: Int = 0
-        private var roundRadius: Int = 0
-        private var isGif: Boolean = false
-        private var skipMemoryCache: Boolean = false
-        private var skipDiskCache: Boolean = false
-        private var blurEffect: Float = 0f
-
-        fun setPlaceImage(@DrawableRes placeImage: Int) = apply {
-            this.placeImage = placeImage
-        }
-
-        fun setErrorImage(@DrawableRes errorImage: Int) = apply {
-            this.errorImage = errorImage
-        }
-
-        fun setRoundRadius(roundRadius: Int) = apply {
-            this.roundRadius = roundRadius
-        }
-
-        fun asGif(isGif: Boolean) = apply {
-            this.isGif = isGif
-        }
-
-        fun setSkipMemoryCache(skip: Boolean) = apply {
-            this.skipMemoryCache = skip
-        }
-
-        fun setSkipDiskCache(skip: Boolean) = apply {
-            this.skipDiskCache = skip
-        }
-
-        fun setBlurEffect(level: Float) = apply {
-            this.blurEffect = level
-        }
-
-        fun build() = ImageOptions(
-            placeImage = placeImage,
-            errorImage = errorImage,
-            roundRadius = roundRadius,
-            isGif = isGif,
-            skipMemoryCache = skipMemoryCache,
-            skipDiskCache = skipDiskCache,
-            blurEffect = blurEffect
-        )
-    }
-
-    companion object {
-        @JvmStatic
-        fun default() = ImageOptions()
-
-        @JvmStatic
-        fun withPlaceholder(@DrawableRes placeImage: Int) = ImageOptions(placeImage = placeImage)
-    }
-}

BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_check_box_group_selected.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_group_select_disable.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_hangup_loading.gif


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_add_user_black.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_audio_input.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_audio_route_picker.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_avatar.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_back.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_blur_background_accept.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_blur_disable.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_blur_enable.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_disable.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_enable.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_dialing.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_dialing_pressed.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_float.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_float_button.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_disable.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_enable.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_hangup.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_hangup_pressed.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_loading.gif


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_mute.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_unmute.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_network_bad.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_self_mute.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_switch_camera.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_switch_camera_group.png


BIN=BIN
frame/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_view_expand.png


+ 0 - 5
frame/atomic_x/src/main/res-callview/drawable/callview_bg_aisubtitle.xml

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

+ 0 - 5
frame/atomic_x/src/main/res-callview/drawable/callview_bg_audio_device.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/callview_ic_handsfree_enable" android:state_activated="true" android:state_enabled="true" />
-    <item android:drawable="@drawable/callview_ic_handsfree_disable" android:state_activated="false" android:state_enabled="true" />
-</selector>

+ 0 - 5
frame/atomic_x/src/main/res-callview/drawable/callview_bg_blur_background.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/callview_ic_blur_disable" android:state_activated="false" android:state_enabled="true" />
-    <item android:drawable="@drawable/callview_ic_blur_enable" android:state_activated="true" android:state_enabled="true" />
-</selector>

+ 0 - 5
frame/atomic_x/src/main/res-callview/drawable/callview_bg_camera.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/callview_ic_camera_disable" android:state_activated="false" android:state_enabled="true" />
-    <item android:drawable="@drawable/callview_ic_camera_enable" android:state_activated="true" android:state_enabled="true" />
-</selector>

+ 0 - 5
frame/atomic_x/src/main/res-callview/drawable/callview_bg_dialing.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/callview_ic_dialing_pressed" android:state_pressed="true" android:state_enabled="true" />
-    <item android:drawable="@drawable/callview_ic_dialing" android:state_pressed="false" android:state_enabled="true" />
-</selector>

+ 0 - 8
frame/atomic_x/src/main/res-callview/drawable/callview_bg_group_call_bottom.xml

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

+ 0 - 5
frame/atomic_x/src/main/res-callview/drawable/callview_bg_hangup.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/callview_ic_hangup_pressed" android:state_enabled="true" android:state_pressed="true" />
-    <item android:drawable="@drawable/callview_ic_hangup" android:state_enabled="true" android:state_pressed="false" />
-</selector>

+ 0 - 5
frame/atomic_x/src/main/res-callview/drawable/callview_bg_mute_mic.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/callview_ic_mic_unmute" android:state_activated="true" android:state_enabled="true" />
-    <item android:drawable="@drawable/callview_ic_mic_mute" android:state_activated="false" android:state_enabled="true" />
-</selector>

BIN=BIN
frame/atomic_x/src/main/res-callview/drawable/callview_check_box_group_unselected.png


+ 0 - 9
frame/atomic_x/src/main/res-callview/drawable/callview_group_checkbox_selector.xml

@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:drawable="@drawable/callview_check_box_group_selected" android:state_checked="true" android:state_enabled="true" />
-    <item android:drawable="@drawable/callview_group_select_disable" android:state_checked="true" android:state_enabled="false" />
-    <item android:drawable="@drawable/callview_check_box_group_unselected" android:state_checked="false" />
-    <item android:drawable="@drawable/callview_check_box_group_unselected" />
-
-</selector>

+ 0 - 11
frame/atomic_x/src/main/res-callview/drawable/callview_ic_bluetooth.xml

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="?android:attr/textColorPrimary">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71,-5.71l-4.3,-4.29l4.3,-4.29zM13,5.83l1.88,1.88L13,9.59V5.83zM13,14.41l1.88,1.88L13,18.17v-3.76z"/>
-</vector> 

+ 0 - 10
frame/atomic_x/src/main/res-callview/drawable/callview_ic_earpiece.xml

@@ -1,10 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="?attr/colorControlNormal">
-  <path
-      android:fillColor="@android:color/white"
-      android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.2c0.28,-0.28 0.36,-0.67 0.25,-1.02C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1c0,9.39 7.61,17 17,17c0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1z"/>
-</vector> 

+ 0 - 11
frame/atomic_x/src/main/res-callview/drawable/callview_ic_item_checked.xml

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="?android:attr/textColorPrimary">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19L21,7l-1.41,-1.41L9,16.17z"/>
-</vector> 

+ 0 - 10
frame/atomic_x/src/main/res-callview/drawable/callview_ic_speaker.xml

@@ -1,10 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="?attr/colorControlNormal">
-  <path
-      android:fillColor="@color/white"
-      android:pathData="M3,9v6h4l5,5V4L7,9H3zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77S18.01,4.14 14,3.23z"/>
-</vector> 

+ 0 - 9
frame/atomic_x/src/main/res-callview/drawable/callview_popup_background.xml

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

+ 0 - 44
frame/atomic_x/src/main/res-callview/layout/callview_function_view_audio.xml

@@ -1,44 +0,0 @@
-<?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="wrap_content"
-    android:layout_centerHorizontal="true"
-    android:layout_marginTop="20dp"
-    android:layout_marginBottom="74dp"
-    android:adjustViewBounds="true"
-    android:maxWidth="480dp">
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_mic"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_mute_mic"
-        app:cbText="@string/callview_toast_disable_mute"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/cb_hangup"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_hangup"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_hangup"
-        app:cbText="@string/callview_text_hangup"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/cb_audio_device"
-        app:layout_constraintStart_toEndOf="@+id/cb_mic"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_audio_device"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_audio_device"
-        app:cbText="@string/callview_text_use_earpiece"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/cb_hangup"
-        app:layout_constraintTop_toTopOf="parent" />
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 64
frame/atomic_x/src/main/res-callview/layout/callview_function_view_invited_waiting.xml

@@ -1,64 +0,0 @@
-<?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="wrap_content"
-    android:layout_centerHorizontal="true"
-    android:layout_marginTop="20dp"
-    android:layout_marginBottom="74dp"
-    android:adjustViewBounds="true"
-    android:maxWidth="480dp">
-
-    <LinearLayout
-        android:id="@+id/ll_reject"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center_horizontal"
-        android:orientation="vertical"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/ll_answer"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent">
-
-        <androidx.constraintlayout.utils.widget.ImageFilterView
-            android:id="@+id/img_reject"
-            android:layout_width="60dp"
-            android:layout_height="60dp"
-            android:src="@drawable/callview_bg_hangup" />
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="12dp"
-            android:gravity="center"
-            android:text="@string/callview_text_decline"
-            android:textColor="@color/callview_color_white"
-            android:textSize="@dimen/callview_text_size_hint" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/ll_answer"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center_horizontal"
-        android:orientation="vertical"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/ll_reject"
-        app:layout_constraintTop_toTopOf="parent">
-
-        <ImageView
-            android:layout_width="60dp"
-            android:layout_height="60dp"
-            android:src="@drawable/callview_bg_dialing" />
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="12dp"
-            android:gravity="center"
-            android:text="@string/callview_text_dialing"
-            android:textColor="@color/callview_color_white"
-            android:textSize="@dimen/callview_text_size_hint" />
-    </LinearLayout>
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 102
frame/atomic_x/src/main/res-callview/layout/callview_function_view_video.xml

@@ -1,102 +0,0 @@
-<?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:id="@+id/cl_view_video"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:adjustViewBounds="true"
-    app:layout_constraintBottom_toBottomOf="parent"
-    app:layout_constraintEnd_toEndOf="parent"
-    app:layout_constraintHorizontal_chainStyle="spread"
-    app:layout_constraintStart_toStartOf="parent"
-    app:layout_constraintTop_toTopOf="parent">
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/guideline"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_begin="20dp" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_microphone"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_mute_mic"
-        app:cbText="@string/callview_toast_disable_mute"
-        app:layout_constraintEnd_toStartOf="@+id/cb_speaker"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/guideline" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_speaker"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_audio_device"
-        app:cbText="@string/callview_text_speaker"
-        app:layout_constraintEnd_toStartOf="@+id/cb_open_camera"
-        app:layout_constraintStart_toEndOf="@+id/cb_microphone"
-        app:layout_constraintTop_toTopOf="@+id/guideline" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_open_camera"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_camera"
-        app:cbText="@string/callview_toast_enable_camera"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/cb_speaker"
-        app:layout_constraintTop_toTopOf="@+id/guideline" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/guideline2"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_percent="0.52" />
-
-    <androidx.constraintlayout.utils.widget.ImageFilterView
-        android:id="@+id/iv_hang_up"
-        android:layout_width="60dp"
-        android:layout_height="60dp"
-        android:layout_marginTop="22dp"
-        android:layout_marginBottom="22dp"
-        android:src="@drawable/callview_bg_hangup"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/guideline2" />
-
-    <ImageView
-        android:id="@+id/iv_function_switch_camera"
-        android:layout_width="28dp"
-        android:layout_height="28dp"
-        android:src="@drawable/callview_ic_switch_camera"
-        android:visibility="visible"
-        app:layout_constraintBottom_toBottomOf="@+id/iv_hang_up"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/iv_hang_up"
-        app:layout_constraintTop_toTopOf="@+id/iv_hang_up" />
-
-    <ImageView
-        android:id="@+id/img_blur_background"
-        android:layout_width="28dp"
-        android:layout_height="28dp"
-        android:src="@drawable/callview_ic_blur_background_accept"
-        android:visibility="visible"
-        app:layout_constraintBottom_toBottomOf="@+id/iv_hang_up"
-        app:layout_constraintEnd_toEndOf="@+id/cb_microphone"
-        app:layout_constraintStart_toStartOf="@+id/cb_microphone"
-        app:layout_constraintTop_toTopOf="@+id/iv_hang_up" />
-
-    <ImageView
-        android:id="@+id/iv_expanded"
-        android:layout_width="30dp"
-        android:layout_height="30dp"
-        android:layout_marginStart="15dp"
-        android:rotation="0"
-        android:src="@drawable/callview_ic_view_expand"
-        app:layout_constraintBottom_toBottomOf="@+id/iv_hang_up"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/iv_hang_up" />
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 64
frame/atomic_x/src/main/res-callview/layout/callview_function_view_video_inviting.xml

@@ -1,64 +0,0 @@
-<?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:id="@+id/constraint_layout"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_centerHorizontal="true"
-    android:layout_marginBottom="40dp"
-    android:adjustViewBounds="true"
-    android:maxWidth="480dp"
-    app:layout_constraintHorizontal_chainStyle="spread">
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/guideline"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_percent="0.5" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_cancel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="12dp"
-        app:cbIcon="@drawable/callview_bg_hangup"
-        app:cbText="@string/callview_btn_cancel"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/guideline" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_switch_camera"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_ic_switch_camera_group"
-        app:cbText="@string/callview_toast_switch_camera"
-        app:layout_constraintBottom_toTopOf="@+id/guideline"
-        app:layout_constraintEnd_toStartOf="@+id/cb_blur"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_blur"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_blur_background"
-        app:cbText="@string/callview_blur_background"
-        app:layout_constraintBottom_toTopOf="@+id/guideline"
-        app:layout_constraintEnd_toStartOf="@+id/cb_camera"
-        app:layout_constraintStart_toEndOf="@+id/cb_switch_camera"
-        app:layout_constraintTop_toTopOf="@+id/cb_switch_camera" />
-
-    <io.trtc.tuikit.atomicx.callview.core.common.widget.ControlButton
-        android:id="@+id/cb_camera"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cbIcon="@drawable/callview_bg_camera"
-        app:cbText="@string/callview_toast_enable_camera"
-        app:layout_constraintBottom_toTopOf="@+id/guideline"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/cb_blur"
-        app:layout_constraintTop_toTopOf="@+id/cb_switch_camera" />
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 29
frame/atomic_x/src/main/res-callview/layout/callview_function_view_video_item.xml

@@ -1,29 +0,0 @@
-<?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="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_horizontal"
-    android:gravity="center_horizontal">
-
-    <androidx.constraintlayout.utils.widget.ImageFilterView
-        android:id="@+id/iv_function"
-        android:layout_width="60dp"
-        android:layout_height="60dp"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <TextView
-        android:id="@+id/tv_function"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:layout_marginTop="12dp"
-        android:gravity="center"
-        android:textColor="@color/callview_color_white"
-        android:textSize="12sp"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/iv_function" />
-</androidx.constraintlayout.widget.ConstraintLayout>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio