diff --git a/application/pubspec.yaml b/application/pubspec.yaml index cc3463d5..dc3b3e4a 100644 --- a/application/pubspec.yaml +++ b/application/pubspec.yaml @@ -57,9 +57,9 @@ dependencies: # Temporary solution, to be removed after the release of chat/live dependency_overrides: - rtc_room_engine: 4.1.0 - live_uikit_barrage: 3.0.1 - atomic_x_core: ^5.0.0 + rtc_room_engine: ^4.2.0 + live_uikit_barrage: ^3.0.2 + atomic_x_core: ^5.0.2 tencent_cloud_chat_sdk: ^9.0.7652 dev_dependencies: @@ -130,4 +130,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/to/font-from-package flutter_intl: - enabled: true \ No newline at end of file + enabled: true diff --git a/atomic-x/.gitignore b/atomic-x/.gitignore new file mode 100644 index 00000000..ac5aa989 --- /dev/null +++ b/atomic-x/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/atomic-x/CHANGELOG.md b/atomic-x/CHANGELOG.md index e53cd688..e42242db 100644 --- a/atomic-x/CHANGELOG.md +++ b/atomic-x/CHANGELOG.md @@ -1,3 +1,9 @@ +## 5.1.0 + +- Add foreground service +- Add two new permission statuses: limited and notDetermined +- Fix other known issues + ## 5.0.0 ### Breaking changes diff --git a/atomic-x/android/src/main/AndroidManifest.xml b/atomic-x/android/src/main/AndroidManifest.xml index 580147b1..bf85a14e 100644 --- a/atomic-x/android/src/main/AndroidManifest.xml +++ b/atomic-x/android/src/main/AndroidManifest.xml @@ -15,6 +15,11 @@ + + + + + - + + + + + diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/albumpicker/AlbumPickerHandler.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/albumpicker/AlbumPickerHandler.kt index 9f061f8e..0f262bb6 100644 --- a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/albumpicker/AlbumPickerHandler.kt +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/albumpicker/AlbumPickerHandler.kt @@ -181,6 +181,7 @@ class AlbumPickerHandler( ) textMessage?.let { event["textMessage"] = it } capturedSessionId?.let { event["sessionId"] = it } + Log.d(TAG, "onPickConfirm dispatching event to Flutter (sessionId=$capturedSessionId)") mainHandler.post { eventSink(event) } } catch (e: Exception) { Log.e(TAG, "Error building onPickConfirm event", e) @@ -216,10 +217,12 @@ class AlbumPickerHandler( override fun onMediaProcessed() { Log.d(TAG, "onMediaProcessed") - val event = mutableMapOf("type" to "onMediaProcessed") - capturedSessionId?.let { event["sessionId"] = it } - mainHandler.post { eventSink(event) } - completeSession() + executor.execute { + val event = mutableMapOf("type" to "onMediaProcessed") + capturedSessionId?.let { event["sessionId"] = it } + mainHandler.post { eventSink(event) } + mainHandler.post { completeSession() } + } } override fun onCancel() { diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/AudioForegroundService.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/AudioForegroundService.kt new file mode 100644 index 00000000..01b3d9f3 --- /dev/null +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/AudioForegroundService.kt @@ -0,0 +1,31 @@ +package io.trtc.tuikit.atomicx.foregroundservice + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE +import androidx.core.content.ContextCompat +import io.trtc.tuikit.atomicx.foregroundservice.base.BaseForegroundService +import io.trtc.tuikit.atomicx.foregroundservice.base.ServiceLauncher + +class AudioForegroundService : BaseForegroundService(launcher) { + + override fun provideForegroundServiceType() = FOREGROUND_SERVICE_TYPE_MICROPHONE + + override fun provideChannelId() = "rtc_uikit_audio_foreground_service" + + companion object { + internal val launcher = + ServiceLauncher(AudioForegroundService::class.java, "AudioForegroundService") + + @JvmStatic + fun start(context: Context, title: String?, description: String?, icon: Int) { + launcher.start(context, title, description, icon) { + ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED + } + } + + @JvmStatic + fun stop(context: Context) = launcher.stop(context) + } +} \ No newline at end of file diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/MediaForegroundService.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/MediaForegroundService.kt new file mode 100644 index 00000000..4f3a7c10 --- /dev/null +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/MediaForegroundService.kt @@ -0,0 +1,26 @@ +package io.trtc.tuikit.atomicx.foregroundservice + +import android.content.Context +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK +import io.trtc.tuikit.atomicx.foregroundservice.base.BaseForegroundService +import io.trtc.tuikit.atomicx.foregroundservice.base.ServiceLauncher + +class MediaForegroundService : BaseForegroundService(launcher) { + + override fun provideForegroundServiceType() = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + + override fun provideChannelId() = "rtc_uikit_media_foreground_service" + + companion object { + internal val launcher = + ServiceLauncher(MediaForegroundService::class.java, "MediaForegroundService") + + @JvmStatic + fun start(context: Context, title: String?, description: String?, icon: Int) { + launcher.start(context, title, description, icon) { true } + } + + @JvmStatic + fun stop(context: Context) = launcher.stop(context) + } +} \ No newline at end of file diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/VideoForegroundService.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/VideoForegroundService.kt new file mode 100644 index 00000000..1ca05e77 --- /dev/null +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/VideoForegroundService.kt @@ -0,0 +1,47 @@ +package io.trtc.tuikit.atomicx.foregroundservice + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE +import android.os.Build +import androidx.core.content.ContextCompat +import io.trtc.tuikit.atomicx.foregroundservice.base.BaseForegroundService +import io.trtc.tuikit.atomicx.foregroundservice.base.ServiceLauncher + +class VideoForegroundService : BaseForegroundService(launcher) { + + override fun provideForegroundServiceType(): Int { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + FOREGROUND_SERVICE_TYPE_MICROPHONE or FOREGROUND_SERVICE_TYPE_CAMERA + } else 0 + } + + override fun provideChannelId() = "rtc_uikit_video_foreground_service" + + companion object { + internal val launcher = + ServiceLauncher(VideoForegroundService::class.java, "VideoForegroundService") + + @JvmStatic + fun start(context: Context, title: String?, description: String?, icon: Int) { + launcher.start(context, title, description, icon) { + val hasCameraPermission = + ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + val hasAudioPermission = + ContextCompat.checkSelfPermission( + context, + Manifest.permission.RECORD_AUDIO + ) == PackageManager.PERMISSION_GRANTED + hasCameraPermission && hasAudioPermission + } + } + + @JvmStatic + fun stop(context: Context) = launcher.stop(context) + } +} \ No newline at end of file diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/BaseForegroundService.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/BaseForegroundService.kt new file mode 100644 index 00000000..10c8a78d --- /dev/null +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/BaseForegroundService.kt @@ -0,0 +1,90 @@ +package io.trtc.tuikit.atomicx.foregroundservice.base + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.os.Build +import android.os.IBinder +import androidx.core.app.NotificationCompat +import io.trtc.tuikit.atomicx.basecomponent.utils.ContextProvider +import io.trtc.tuikit.atomicx.R +import io.trtc.tuikit.atomicx.util.TUIBuild + +abstract class BaseForegroundService( + private val launcher: ServiceLauncher<*> +) : Service() { + + companion object { + const val TITLE = "title" + const val ICON = "icon" + const val DESCRIPTION = "description" + const val NOTIFICATION_ID = 1001 + } + + abstract fun provideForegroundServiceType(): Int + + abstract fun provideChannelId(): String + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent == null) return START_NOT_STICKY + + val appContext = ContextProvider.appContext + val title = intent.getStringExtra(TITLE) + val description = intent.getStringExtra(DESCRIPTION) + val icon = intent.getIntExtra(ICON, appContext?.applicationInfo?.icon ?: 0) + + val notification = createNotification(title, description, icon) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + startForeground(NOTIFICATION_ID, notification, provideForegroundServiceType()) + } else { + startForeground(NOTIFICATION_ID, notification) + } + + if (launcher.state == ServiceState.STOPPING) { + stopSelf() + launcher.updateState(ServiceState.IDLE) + } else { + launcher.updateState(ServiceState.RUNNING) + } + return START_NOT_STICKY + } + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + stopSelf() + launcher.updateState(ServiceState.IDLE) + } + + override fun onDestroy() { + super.onDestroy() + clearNotification() + } + + private fun createNotification(title: String?, desc: String?, icon: Int): Notification { + val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + val channelId = provideChannelId() + if (TUIBuild.getVersionInt() >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + channelId, + applicationContext.getString(R.string.common_rtc_channel_name), + NotificationManager.IMPORTANCE_LOW + ) + manager.createNotificationChannel(channel) + } + return NotificationCompat.Builder(this, channelId) + .setSmallIcon(icon) + .setContentTitle(title) + .setContentText(desc) + .build() + } + + private fun clearNotification() { + val manager = getSystemService(NOTIFICATION_SERVICE) as? NotificationManager + manager?.cancel(NOTIFICATION_ID) + } +} \ No newline at end of file diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/ServiceLauncher.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/ServiceLauncher.kt new file mode 100644 index 00000000..2ac126cb --- /dev/null +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/ServiceLauncher.kt @@ -0,0 +1,83 @@ +package io.trtc.tuikit.atomicx.foregroundservice.base + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log + +/** + * + * api 状态说明: + * 1、start API说明:{@link AudioForegroundService#start(Context, String, String, int)} + * a、如果服务已经被调用start(STARTING) 或者 调用 start 之后服务已经被启动(RUNNING),此时代表重复 start 可以直接返回 + * b、如果服务状态为 STOPPING 状态,代表之前 start 之后,在服务没有完全启动时,调用了 stop API,此时由于 start 被后调用,所以变更状态为start + * 2、stop API说明:{@link AudioForegroundService#stop(Context)} + * a、如果服务为 IDLE 状态,调用 stop 为无效操作,可以忽略 + * b、如果服务为 STARTING 状态,代表服务还没有真正启动,所以需要标记 为 STOPPING。待真正启动之后,再结束自己避免 调用startForegroundService 之后,5秒没有调用 + * startForeground的异常。 + * c、如果服务为 RUNNING 状态,代表服务已经启动,直接正常停止即可。 + * d、如果服务为 STOPPING 状态,代表之前已经调用 stop 且没有完全停止,所以和之前还是一样状态,无需处理。 + */ +class ServiceLauncher( + private val serviceClass: Class, + private val tag: String +) { + @Volatile + var state = ServiceState.IDLE + private set + + fun start( + context: Context, + title: String?, + description: String?, + icon: Int, + permissionChecker: () -> Boolean = { true } + ) { + val appContext = context.applicationContext + + if (state == ServiceState.STARTING || state == ServiceState.RUNNING) { + Log.i(tag, "start foreground service, service is already active") + return + } + if (state == ServiceState.STOPPING) { + state = ServiceState.STARTING + Log.i(tag, "start foreground service, changing STOPPING to STARTING") + return + } + + state = ServiceState.STARTING + if (!permissionChecker()) { + state = ServiceState.IDLE + Log.e(tag, "start failed: permission denied") + return + } + + val intent = Intent(appContext, serviceClass).apply { + putExtra(BaseForegroundService.TITLE, title) + putExtra(BaseForegroundService.ICON, icon) + putExtra(BaseForegroundService.DESCRIPTION, description) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + appContext.startForegroundService(intent) + } else { + appContext.startService(intent) + } + } + + fun stop(context: Context) { + val appContext = context.applicationContext + when (state) { + ServiceState.RUNNING -> { + appContext.stopService(Intent(appContext, serviceClass)) + state = ServiceState.IDLE + } + ServiceState.STARTING -> state = ServiceState.STOPPING + else -> {} + } + } + + fun updateState(newState: ServiceState) { + state = newState + } +} \ No newline at end of file diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/ServiceState.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/ServiceState.kt new file mode 100644 index 00000000..823370b3 --- /dev/null +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/foregroundservice/base/ServiceState.kt @@ -0,0 +1,15 @@ +package io.trtc.tuikit.atomicx.foregroundservice.base + +/** + *后台服务状态说明: + * IDLE:空闲状态,默认初始状态 + * STARTING: 调用start之后一直到服务没有真正启动之前的状态 + * RUNNING:系统启动后台服务启动完成 + * STOPPING:调用stop之后到服务被系统真正停止之前的状态 + */ +enum class ServiceState { + IDLE, + STARTING, + RUNNING, + STOPPING +} diff --git a/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/utils/TUIBuild.kt b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/utils/TUIBuild.kt new file mode 100644 index 00000000..c61e7d40 --- /dev/null +++ b/atomic-x/android/src/main/kotlin/io/trtc/tuikit/atomicx/utils/TUIBuild.kt @@ -0,0 +1,284 @@ +package io.trtc.tuikit.atomicx.util + +import android.os.Build +import android.text.TextUtils +import android.util.Log + +object TUIBuild { + private const val TAG = "TUIBuild" + + @Volatile + private var MODEL = "" // Build.MODEL + + @Volatile + private var BRAND = "" // Build.BRAND + + @Volatile + private var DEVICE = "" // Build.DEVICE + + @Volatile + private var MANUFACTURER = "" // Build.MANUFACTURER + + @Volatile + private var HARDWARE = "" // Build.HARDWARE + + @Volatile + private var VERSION = "" // Build.VERSION.RELEASE + + @Volatile + private var BOARD = "" // Build.BOARD + + @Volatile + private var VERSION_INCREMENTAL = "" // Build.VERSION.INCREMENTAL + + @Volatile + private var VERSION_INT = 0 // Build.VERSION.SDK_INT + + @JvmStatic + fun setModel(model: String) { + synchronized(TUIBuild::class.java) { + MODEL = model + } + } + + @JvmStatic + fun getModel(): String { + if (MODEL.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (MODEL.isEmpty()) { + MODEL = Build.MODEL + Log.i(TAG, "get MODEL by Build.MODEL :$MODEL") + } + } + } + return MODEL + } + + @JvmStatic + fun setBrand(brand: String) { + synchronized(TUIBuild::class.java) { + BRAND = brand + } + } + + @JvmStatic + fun getBrand(): String { + if (BRAND.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (BRAND.isEmpty()) { + BRAND = Build.BRAND + Log.i(TAG, "get BRAND by Build.BRAND :$BRAND") + } + } + } + return BRAND + } + + @JvmStatic + fun setDevice(device: String) { + synchronized(TUIBuild::class.java) { + DEVICE = device + } + } + + @JvmStatic + fun getDevice(): String { + if (DEVICE.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (DEVICE.isEmpty()) { + DEVICE = Build.DEVICE + Log.i(TAG, "get DEVICE by Build.DEVICE :$DEVICE") + } + } + } + return DEVICE + } + + @JvmStatic + fun setManufacturer(manufacturer: String) { + synchronized(TUIBuild::class.java) { + MANUFACTURER = manufacturer + } + } + + @JvmStatic + fun getManufacturer(): String { + if (MANUFACTURER.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (MANUFACTURER.isEmpty()) { + MANUFACTURER = Build.MANUFACTURER + Log.i(TAG, "get MANUFACTURER by Build.MANUFACTURER :$MANUFACTURER") + } + } + } + return MANUFACTURER + } + + @JvmStatic + fun setHardware(hardware: String) { + synchronized(TUIBuild::class.java) { + HARDWARE = hardware + } + } + + @JvmStatic + fun getHardware(): String { + if (HARDWARE.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (HARDWARE.isEmpty()) { + HARDWARE = Build.HARDWARE + Log.i(TAG, "get HARDWARE by Build.HARDWARE :$HARDWARE") + } + } + } + return HARDWARE + } + + @JvmStatic + fun setVersion(version: String) { + synchronized(TUIBuild::class.java) { + VERSION = version + } + } + + @JvmStatic + fun getVersion(): String { + if (VERSION.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (VERSION.isEmpty()) { + VERSION = Build.VERSION.RELEASE + Log.i(TAG, "get VERSION by Build.VERSION.RELEASE :$VERSION") + } + } + } + return VERSION + } + + @JvmStatic + fun setVersionInt(versionInt: Int) { + synchronized(TUIBuild::class.java) { + VERSION_INT = versionInt + } + } + + @JvmStatic + fun getVersionInt(): Int { + if (VERSION_INT == 0) { + synchronized(TUIBuild::class.java) { + if (VERSION_INT == 0) { + VERSION_INT = Build.VERSION.SDK_INT + Log.i(TAG, "get VERSION_INT by Build.VERSION.SDK_INT :$VERSION_INT") + } + } + } + return VERSION_INT + } + + @JvmStatic + fun setVersionIncremental(versionIncremental: String) { + synchronized(TUIBuild::class.java) { + VERSION_INCREMENTAL = versionIncremental + } + } + + @JvmStatic + fun getVersionIncremental(): String { + if (VERSION_INCREMENTAL.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (VERSION_INCREMENTAL.isEmpty()) { + VERSION_INCREMENTAL = Build.VERSION.INCREMENTAL + Log.i(TAG, "get VERSION_INCREMENTAL by Build.VERSION.INCREMENTAL :$VERSION_INCREMENTAL") + } + } + } + return VERSION_INCREMENTAL + } + + @JvmStatic + fun setBoard(board: String) { + synchronized(TUIBuild::class.java) { + BOARD = board + } + } + + @JvmStatic + fun getBoard(): String { + if (BOARD.isEmpty()) { + synchronized(TUIBuild::class.java) { + if (BOARD.isEmpty()) { + BOARD = Build.BOARD + Log.i(TAG, "get BOARD by Build.BOARD :$BOARD") + } + } + } + return BOARD + } + + @JvmStatic + fun isBrandXiaoMi(): Boolean { + return "xiaomi".equals(getBrand(), ignoreCase = true) || + "xiaomi".equals(getManufacturer(), ignoreCase = true) + } + + @JvmStatic + fun isBrandHuawei(): Boolean { + return "huawei".equals(getBrand(), ignoreCase = true) || + "huawei".equals(getManufacturer(), ignoreCase = true) || + "honor".equals(getBrand(), ignoreCase = true) + } + + @JvmStatic + fun isBrandMeizu(): Boolean { + return "meizu".equals(getBrand(), ignoreCase = true) || + "meizu".equals(getManufacturer(), ignoreCase = true) || + "22c4185e".equals(getBrand(), ignoreCase = true) + } + + @JvmStatic + fun isBrandOppo(): Boolean { + return "oppo".equals(getBrand(), ignoreCase = true) || + "realme".equals(getBrand(), ignoreCase = true) || + "oneplus".equals(getBrand(), ignoreCase = true) || + "oppo".equals(getManufacturer(), ignoreCase = true) || + "realme".equals(getManufacturer(), ignoreCase = true) || + "oneplus".equals(getManufacturer(), ignoreCase = true) + } + + @JvmStatic + fun isBrandVivo(): Boolean { + return "vivo".equals(getBrand(), ignoreCase = true) || + "vivo".equals(getManufacturer(), ignoreCase = true) + } + + @JvmStatic + fun isBrandHonor(): Boolean { + return "honor".equals(getBrand(), ignoreCase = true) && + "honor".equals(getManufacturer(), ignoreCase = true) + } + + @JvmStatic + fun isHarmonyOS(): Boolean { + return try { + val clz = Class.forName("com.huawei.system.BuildEx") + val method = clz.getMethod("getOsBrand") + "harmony" == method.invoke(clz) + } catch (e: Exception) { + Log.e(TAG, "the phone not support the harmonyOS") + false + } + } + + @JvmStatic + fun isMiuiOptimization(): Boolean { + return try { + val systemProperties = Class.forName("android.os.systemProperties") + val get = systemProperties.getDeclaredMethod("get", String::class.java, String::class.java) + val miuiOptimization = get.invoke(systemProperties, "persist.sys.miuiOptimization", "") as String + // The user has not adjusted the MIUI-optimization switch (default) | user open MIUI-optimization + TextUtils.isEmpty(miuiOptimization) || "true" == miuiOptimization + } catch (e: Exception) { + Log.e(TAG, "the phone not support the miui optimization") + false + } + } +} diff --git a/atomic-x/android/src/main/res/values-zh/strings.xml b/atomic-x/android/src/main/res/values-zh/strings.xml new file mode 100644 index 00000000..5726d453 --- /dev/null +++ b/atomic-x/android/src/main/res/values-zh/strings.xml @@ -0,0 +1,3 @@ + + 音视频通信服务 + \ No newline at end of file diff --git a/atomic-x/android/src/main/res/values/strings.xml b/atomic-x/android/src/main/res/values/strings.xml new file mode 100644 index 00000000..2dc87e1f --- /dev/null +++ b/atomic-x/android/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Video Communications Foreground Service + \ No newline at end of file diff --git a/atomic-x/example/.gitignore b/atomic-x/example/.gitignore new file mode 100644 index 00000000..79c113f9 --- /dev/null +++ b/atomic-x/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/atomic-x/example/analysis_options.yaml b/atomic-x/example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/atomic-x/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/atomic-x/example/android/.gitignore b/atomic-x/example/android/.gitignore new file mode 100644 index 00000000..55afd919 --- /dev/null +++ b/atomic-x/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/atomic-x/example/android/app/build.gradle b/atomic-x/example/android/app/build.gradle new file mode 100644 index 00000000..97c9aa8f --- /dev/null +++ b/atomic-x/example/android/app/build.gradle @@ -0,0 +1,44 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 23 // Required by record_android and other plugins + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/atomic-x/example/android/app/src/debug/AndroidManifest.xml b/atomic-x/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/atomic-x/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/atomic-x/example/android/app/src/main/AndroidManifest.xml b/atomic-x/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..469d4fa0 --- /dev/null +++ b/atomic-x/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atomic-x/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/atomic-x/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 00000000..70f8f08f --- /dev/null +++ b/atomic-x/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/atomic-x/example/android/app/src/main/res/drawable-v21/launch_background.xml b/atomic-x/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/atomic-x/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/atomic-x/example/android/app/src/main/res/drawable/launch_background.xml b/atomic-x/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/atomic-x/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/atomic-x/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/atomic-x/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/atomic-x/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/atomic-x/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/atomic-x/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/atomic-x/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/atomic-x/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/atomic-x/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/atomic-x/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/atomic-x/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/atomic-x/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/atomic-x/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/atomic-x/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/atomic-x/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/atomic-x/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/atomic-x/example/android/app/src/main/res/values-night/styles.xml b/atomic-x/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/atomic-x/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/atomic-x/example/android/app/src/main/res/values/styles.xml b/atomic-x/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/atomic-x/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/atomic-x/example/android/app/src/profile/AndroidManifest.xml b/atomic-x/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/atomic-x/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/atomic-x/example/android/build.gradle b/atomic-x/example/android/build.gradle new file mode 100644 index 00000000..d2ffbffa --- /dev/null +++ b/atomic-x/example/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/atomic-x/example/android/gradle.properties b/atomic-x/example/android/gradle.properties new file mode 100644 index 00000000..84044a9f --- /dev/null +++ b/atomic-x/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=false diff --git a/atomic-x/example/android/gradle/wrapper/gradle-wrapper.properties b/atomic-x/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..7bb2df6b --- /dev/null +++ b/atomic-x/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/atomic-x/example/android/settings.gradle b/atomic-x/example/android/settings.gradle new file mode 100644 index 00000000..bedf5c23 --- /dev/null +++ b/atomic-x/example/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.2.1" apply false + id "org.jetbrains.kotlin.android" version "2.0.20" apply false +} + +include ":app" diff --git a/atomic-x/example/ios/.gitignore b/atomic-x/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/atomic-x/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/atomic-x/example/ios/Flutter/AppFrameworkInfo.plist b/atomic-x/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..1dc6cf76 --- /dev/null +++ b/atomic-x/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/atomic-x/example/ios/Flutter/Debug.xcconfig b/atomic-x/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/atomic-x/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/atomic-x/example/ios/Flutter/Release.xcconfig b/atomic-x/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/atomic-x/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/atomic-x/example/ios/Podfile b/atomic-x/example/ios/Podfile new file mode 100644 index 00000000..0b62d79e --- /dev/null +++ b/atomic-x/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '14.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/atomic-x/example/ios/Podfile.lock b/atomic-x/example/ios/Podfile.lock new file mode 100644 index 00000000..0b4d969c --- /dev/null +++ b/atomic-x/example/ios/Podfile.lock @@ -0,0 +1,214 @@ +PODS: + - atomic_x (0.0.1): + - Flutter + - atomic_x_core (0.0.1): + - Flutter + - audio_session (0.0.1): + - Flutter + - device_info_plus (0.0.1): + - Flutter + - DKImagePickerController/Core (4.3.9): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.9) + - DKImagePickerController/PhotoGallery (4.3.9): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.9) + - DKPhotoGallery (0.0.19): + - DKPhotoGallery/Core (= 0.0.19) + - DKPhotoGallery/Model (= 0.0.19) + - DKPhotoGallery/Preview (= 0.0.19) + - DKPhotoGallery/Resource (= 0.0.19) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.19): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.19): + - SDWebImage + - SwiftyGif + - fc_native_video_thumbnail (0.0.1): + - Flutter + - FlutterMacOS + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter + - Flutter (1.0.0) + - HydraAsync (2.0.6) + - image_picker_ios (0.0.1): + - Flutter + - just_audio (0.0.1): + - Flutter + - FlutterMacOS + - open_file_ios (0.0.1): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - permission_handler_apple (9.3.0): + - Flutter + - photo_manager (3.7.1): + - Flutter + - FlutterMacOS + - record_ios (1.1.0): + - Flutter + - rtc_room_engine_impl (3.4.0): + - Flutter + - RTCRoomEngine/Professional (= 3.4.0) + - RTCRoomEngine/Professional (3.4.0): + - TXIMSDK_Plus_iOS_XCFramework (>= 8.4.6676) + - TXLiteAVSDK_Professional (>= 12.2.16956) + - SDWebImage (5.21.3): + - SDWebImage/Core (= 5.21.3) + - SDWebImage/Core (5.21.3) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - SwiftyGif (5.4.5) + - tencent_cloud_chat_sdk (8.0.0): + - Flutter + - HydraAsync + - TXIMSDK_Plus_iOS_XCFramework (~> 8.7.7201) + - tencent_rtc_sdk (0.0.1): + - Flutter + - TXCustomBeautyProcesserPlugin (= 1.0.2) + - TXLiteAVSDK_Professional (~> 12.8.19666) + - TXCustomBeautyProcesserPlugin (1.0.2) + - TXIMSDK_Plus_iOS_XCFramework (8.7.7201) + - TXLiteAVSDK_Professional (12.8.19666): + - TXLiteAVSDK_Professional/Professional (= 12.8.19666) + - TXLiteAVSDK_Professional/Professional (12.8.19666) + - url_launcher_ios (0.0.1): + - Flutter + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - atomic_x (from `.symlinks/plugins/atomic_x/ios`) + - atomic_x_core (from `.symlinks/plugins/atomic_x_core/ios`) + - audio_session (from `.symlinks/plugins/audio_session/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - fc_native_video_thumbnail (from `.symlinks/plugins/fc_native_video_thumbnail/darwin`) + - file_picker (from `.symlinks/plugins/file_picker/ios`) + - Flutter (from `Flutter`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - just_audio (from `.symlinks/plugins/just_audio/darwin`) + - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - photo_manager (from `.symlinks/plugins/photo_manager/ios`) + - record_ios (from `.symlinks/plugins/record_ios/ios`) + - rtc_room_engine_impl (from `.symlinks/plugins/rtc_room_engine_impl/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - tencent_cloud_chat_sdk (from `.symlinks/plugins/tencent_cloud_chat_sdk/ios`) + - tencent_rtc_sdk (from `.symlinks/plugins/tencent_rtc_sdk/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) + +SPEC REPOS: + trunk: + - DKImagePickerController + - DKPhotoGallery + - HydraAsync + - RTCRoomEngine + - SDWebImage + - SwiftyGif + - TXCustomBeautyProcesserPlugin + - TXIMSDK_Plus_iOS_XCFramework + - TXLiteAVSDK_Professional + +EXTERNAL SOURCES: + atomic_x: + :path: ".symlinks/plugins/atomic_x/ios" + atomic_x_core: + :path: ".symlinks/plugins/atomic_x_core/ios" + audio_session: + :path: ".symlinks/plugins/audio_session/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" + fc_native_video_thumbnail: + :path: ".symlinks/plugins/fc_native_video_thumbnail/darwin" + file_picker: + :path: ".symlinks/plugins/file_picker/ios" + Flutter: + :path: Flutter + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + just_audio: + :path: ".symlinks/plugins/just_audio/darwin" + open_file_ios: + :path: ".symlinks/plugins/open_file_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + photo_manager: + :path: ".symlinks/plugins/photo_manager/ios" + record_ios: + :path: ".symlinks/plugins/record_ios/ios" + rtc_room_engine_impl: + :path: ".symlinks/plugins/rtc_room_engine_impl/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + tencent_cloud_chat_sdk: + :path: ".symlinks/plugins/tencent_cloud_chat_sdk/ios" + tencent_rtc_sdk: + :path: ".symlinks/plugins/tencent_rtc_sdk/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" + +SPEC CHECKSUMS: + atomic_x: 90aadf71691ecd8af8dfb56a6d185f599694b5c3 + atomic_x_core: 9ab26b4f42d08021f68ba622a35835e1f58cc463 + audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0 + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c + DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 + fc_native_video_thumbnail: ea75aa9d0f7e7b58215d2ad99ac9dddafc038bc9 + file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063 + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed + open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62 + record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374 + rtc_room_engine_impl: 6a6f38e439ff8229e0ceafceb992bc8a54cf17f8 + RTCRoomEngine: 1ef3ec25f18aaf21efae2ef144d89c632642de85 + SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 + tencent_cloud_chat_sdk: cfc39114e05d5d2218d97417369c5a798b6f9caf + tencent_rtc_sdk: 479367c8191b88fd7c3f86b66dedab0dcd12d530 + TXCustomBeautyProcesserPlugin: 099393b941cb40eda12b3a80bf6c0319957b1cfd + TXIMSDK_Plus_iOS_XCFramework: 3b435eae84c639f35ae8dc9c8b92c399a8b0a67f + TXLiteAVSDK_Professional: fab8ac9da64efa80bef81377b35530a791411a74 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + +PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 + +COCOAPODS: 1.16.2 diff --git a/atomic-x/example/ios/Runner.xcodeproj/project.pbxproj b/atomic-x/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..90e637eb --- /dev/null +++ b/atomic-x/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,601 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1E745066ECB416FF38295BF2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D5C09E6C2D7E0BD7A2B4312 /* Pods_Runner.framework */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 45C61003590B58B43CDDFB41 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 60E052E74F85A1E5242D32D0 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 67D77AE4633B8AD6011E752C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7B9638577322514E4FE28597 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 8D5C09E6C2D7E0BD7A2B4312 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C7FEAD595BD60E389FCEF8C0 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D7F4B942AA9DD3B74342296C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F71CBB7B226A9A8D58FE5534 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E745066ECB416FF38295BF2 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 91BADAD829814B57B4F7BC6A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8D5C09E6C2D7E0BD7A2B4312 /* Pods_Runner.framework */, + 67D77AE4633B8AD6011E752C /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + FF97B9100F9383D0FF4C09F9 /* Pods */, + 91BADAD829814B57B4F7BC6A /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + FF97B9100F9383D0FF4C09F9 /* Pods */ = { + isa = PBXGroup; + children = ( + 45C61003590B58B43CDDFB41 /* Pods-Runner.debug.xcconfig */, + 7B9638577322514E4FE28597 /* Pods-Runner.release.xcconfig */, + D7F4B942AA9DD3B74342296C /* Pods-Runner.profile.xcconfig */, + 60E052E74F85A1E5242D32D0 /* Pods-RunnerTests.debug.xcconfig */, + C7FEAD595BD60E389FCEF8C0 /* Pods-RunnerTests.release.xcconfig */, + F71CBB7B226A9A8D58FE5534 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9D09D35E55AEB75EEE8DACCC /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 3DC2EED54BE3DEF55A87C2B1 /* [CP] Embed Pods Frameworks */, + 68D34168EC6A467B01337268 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 3DC2EED54BE3DEF55A87C2B1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 68D34168EC6A467B01337268 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9D09D35E55AEB75EEE8DACCC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F8A3GH6Q4W; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F8A3GH6Q4W; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = F8A3GH6Q4W; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = abyWildcardDev; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/atomic-x/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/atomic-x/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/atomic-x/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..6ce5b0b2 --- /dev/null +++ b/atomic-x/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atomic-x/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/atomic-x/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/atomic-x/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/atomic-x/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/atomic-x/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/atomic-x/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/atomic-x/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/atomic-x/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/atomic-x/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/atomic-x/example/ios/Runner/AppDelegate.swift b/atomic-x/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/atomic-x/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/atomic-x/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/atomic-x/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/atomic-x/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/atomic-x/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atomic-x/example/ios/Runner/Base.lproj/Main.storyboard b/atomic-x/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/atomic-x/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atomic-x/example/ios/Runner/Info.plist b/atomic-x/example/ios/Runner/Info.plist new file mode 100644 index 00000000..ce6ec468 --- /dev/null +++ b/atomic-x/example/ios/Runner/Info.plist @@ -0,0 +1,65 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + + NSCameraUsageDescription + This app needs access to the camera to take photos and videos. + NSMicrophoneUsageDescription + This app needs access to the microphone to record audio. + NSPhotoLibraryUsageDescription + This app needs access to the photo library to select photos. + NSPhotoLibraryAddUsageDescription + This app needs access to save photos to your library. + NSLocationWhenInUseUsageDescription + This app needs access to your location when in use. + NSLocationAlwaysUsageDescription + This app needs access to your location always. + NSLocationAlwaysAndWhenInUseUsageDescription + This app needs access to your location. + + diff --git a/atomic-x/example/ios/Runner/Runner-Bridging-Header.h b/atomic-x/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/atomic-x/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/atomic-x/example/ios/RunnerTests/RunnerTests.swift b/atomic-x/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/atomic-x/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/atomic-x/example/lib/device_info/device_info_test_page.dart b/atomic-x/example/lib/device_info/device_info_test_page.dart new file mode 100644 index 00000000..f739b369 --- /dev/null +++ b/atomic-x/example/lib/device_info/device_info_test_page.dart @@ -0,0 +1,364 @@ +import 'package:flutter/material.dart'; +import 'package:tuikit_atomic_x/device_info/device.dart'; + +/// 优化的设备信息测试页面 +class DeviceInfoTestPage extends StatefulWidget { + const DeviceInfoTestPage({super.key}); + + @override + State createState() => _DeviceInfoTestPageState(); +} + +class _DeviceInfoTestPageState extends State { + String _testResult = ''; + bool _isTesting = false; + List _testResults = []; + + /// 执行单个测试 + Future _runTest(String testName, Future Function() testFunction) async { + setState(() { + _isTesting = true; + _testResult = '正在执行 $testName...\n'; + }); + + try { + await testFunction(); + _addTestResult(testName, true, '测试完成'); + } catch (e) { + _addTestResult(testName, false, '测试失败: $e'); + } + + setState(() { + _isTesting = false; + }); + } + + /// 添加测试结果 + void _addTestResult(String name, bool success, String message, {String? details}) { + _testResults.add(TestResult( + name: name, + success: success, + message: message, + details: details, + )); + + setState(() { + _testResult += '${success ? '✅' : '❌'} $name: $message\n'; + if (details != null) { + _testResult += ' $details\n'; + } + }); + } + + /// 测试getDeviceInfo功能 + Future _testGetDeviceInfo() async { + final platform = await Device.platform; + final model = await Device.model; + final manufacturer = await Device.manufacturer; + final version = await Device.version; + final sdkInt = await Device.sdkInt; + + _addTestResult( + 'getDeviceInfo', + true, + '设备信息获取成功', + details: ''' +平台: $platform +型号: $model +制造商: $manufacturer +版本: $version +SDK版本: ${sdkInt ?? 'N/A'}''', + ); + } + + /// 测试平台信息 + Future _testPlatform() async { + final platform = await Device.platform; + _addTestResult( + 'platform', + true, + '平台信息获取成功', + details: '平台类型: $platform', + ); + } + + /// 测试设备型号 + Future _testModel() async { + final model = await Device.model; + _addTestResult( + 'model', + true, + '设备型号获取成功', + details: '设备型号: $model', + ); + } + + /// 测试制造商信息 + Future _testManufacturer() async { + final manufacturer = await Device.manufacturer; + _addTestResult( + 'manufacturer', + true, + '制造商信息获取成功', + details: '制造商: $manufacturer', + ); + } + + /// 测试系统版本 + Future _testVersion() async { + final version = await Device.version; + _addTestResult( + 'version', + true, + '系统版本获取成功', + details: '系统版本: $version', + ); + } + + /// 测试SDK版本 + Future _testSdkInt() async { + final sdkInt = await Device.sdkInt; + _addTestResult( + 'sdkInt', + true, + 'SDK版本获取成功', + details: 'SDK版本: ${sdkInt ?? 'N/A'}', + ); + } + + /// 测试所有功能 + Future _testAll() async { + setState(() { + _testResults.clear(); + _testResult = '开始执行设备信息测试...\n\n'; + }); + + await _runTest('platform', _testPlatform); + await _runTest('model', _testModel); + await _runTest('manufacturer', _testManufacturer); + await _runTest('version', _testVersion); + await _runTest('sdkInt', _testSdkInt); + await _runTest('getDeviceInfo', _testGetDeviceInfo); + + _addTestResult('综合测试', true, '所有测试完成'); + } + + /// 清除测试结果 + void _clearResults() { + setState(() { + _testResults.clear(); + _testResult = ''; + }); + } + + /// 导出测试结果 + void _exportResults() { + final exportText = _testResults.map((result) { + return '${result.success ? '✅' : '❌'} ${result.name}: ${result.message}'; + }).join('\n'); + + // 这里可以添加分享或保存功能 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('测试结果已准备导出(${_testResults.length}项)'), + action: SnackBarAction( + label: '复制', + onPressed: () { + // 实际项目中可以添加复制到剪贴板功能 + }, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('设备信息测试'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + actions: [ + if (_testResults.isNotEmpty) + IconButton( + icon: const Icon(Icons.share), + onPressed: _exportResults, + tooltip: '导出结果', + ), + IconButton( + icon: const Icon(Icons.clear_all), + onPressed: _clearResults, + tooltip: '清除结果', + ), + ], + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 测试按钮区域 + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + ElevatedButton.icon( + onPressed: _isTesting ? null : _testAll, + icon: const Icon(Icons.play_arrow), + label: const Text('执行全部测试'), + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 48), + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _isTesting ? null : () => _runTest('platform', _testPlatform), + icon: const Icon(Icons.devices), + label: const Text('平台信息'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: _isTesting ? null : () => _runTest('model', _testModel), + icon: const Icon(Icons.phone_android), + label: const Text('设备型号'), + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _isTesting ? null : () => _runTest('manufacturer', _testManufacturer), + icon: const Icon(Icons.business), + label: const Text('制造商'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: _isTesting ? null : () => _runTest('version', _testVersion), + icon: const Icon(Icons.system_update), + label: const Text('系统版本'), + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _isTesting ? null : () => _runTest('sdkInt', _testSdkInt), + icon: const Icon(Icons.code), + label: const Text('SDK版本'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: _isTesting ? null : () => _runTest('getDeviceInfo', _testGetDeviceInfo), + icon: const Icon(Icons.device_hub), + label: const Text('完整信息'), + ), + ), + ], + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // 测试结果区域 + Expanded( + child: Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text( + '测试结果', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + if (_isTesting) + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ), + if (_testResults.isNotEmpty) + Chip( + label: Text('${_testResults.length}项'), + backgroundColor: Colors.green[50], + ), + ], + ), + const SizedBox(height: 8), + Expanded( + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300]!), + borderRadius: BorderRadius.circular(8), + color: Colors.grey[50], + ), + child: SingleChildScrollView( + child: Text( + _testResult.isEmpty + ? '点击上方按钮开始测试...' + : _testResult, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + height: 1.4, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + floatingActionButton: _isTesting + ? FloatingActionButton( + onPressed: null, + child: const CircularProgressIndicator(color: Colors.white), + ) + : null, + ); + } +} + +class TestResult { + final String name; + final bool success; + final String message; + final String? details; + + TestResult({ + required this.name, + required this.success, + required this.message, + this.details, + }); +} \ No newline at end of file diff --git a/atomic-x/example/lib/main.dart b/atomic-x/example/lib/main.dart new file mode 100644 index 00000000..08a49b98 --- /dev/null +++ b/atomic-x/example/lib/main.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'device_info/device_info_test_page.dart'; +import 'permission_example.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Atomic-X Example', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const AtomicXExampleHome(), + ); + } +} + +/// Atomic-X 示例主页 +class AtomicXExampleHome extends StatelessWidget { + const AtomicXExampleHome({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Atomic-X Example'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.developer_board, size: 64, color: Colors.deepPurple), + SizedBox(height: 16), + Text( + 'Atomic-X Examples', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + '选择要测试的功能模块', + style: TextStyle(fontSize: 16, color: Colors.grey), + textAlign: TextAlign.center, + ), + ], + ), + ), + floatingActionButton: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton.extended( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const PermissionExample(), + ), + ); + }, + icon: const Icon(Icons.security), + label: const Text('权限验证'), + heroTag: 'permission', + ), + const SizedBox(height: 16), + FloatingActionButton.extended( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DeviceInfoTestPage(), + ), + ); + }, + icon: const Icon(Icons.phone_android), + label: const Text('设备信息'), + heroTag: 'device_info', + ), + ], + ), + ); + } +} diff --git a/atomic-x/example/lib/permission_example.dart b/atomic-x/example/lib/permission_example.dart new file mode 100644 index 00000000..a8953399 --- /dev/null +++ b/atomic-x/example/lib/permission_example.dart @@ -0,0 +1,299 @@ +import 'package:flutter/material.dart'; +import 'package:tuikit_atomic_x/permission/permission.dart'; + +class PermissionExample extends StatefulWidget { + const PermissionExample({super.key}); + + @override + State createState() => _PermissionExampleState(); +} + +class _PermissionExampleState extends State { + String _statusMessage = 'Ready to test permissions'; + bool _isLoading = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Permission Module Example'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + if (_isLoading) + const CircularProgressIndicator() + else + const Icon(Icons.security, size: 48, color: Colors.blue), + const SizedBox(height: 16), + Text( + _statusMessage, + style: const TextStyle(fontSize: 16), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + const SizedBox(height: 24), + const Text( + 'Camera Permission', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _isLoading ? null : _checkCameraPermission, + icon: const Icon(Icons.check_circle_outline), + label: const Text('Check Camera Permission'), + ), + const SizedBox(height: 8), + ElevatedButton.icon( + onPressed: _isLoading ? null : _requestCameraPermission, + icon: const Icon(Icons.camera_alt), + label: const Text('Request Camera Permission'), + ), + const SizedBox(height: 24), + const Text( + 'Microphone Permission', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _isLoading ? null : _checkMicrophonePermission, + icon: const Icon(Icons.check_circle_outline), + label: const Text('Check Microphone Permission'), + ), + const SizedBox(height: 8), + ElevatedButton.icon( + onPressed: _isLoading ? null : _requestMicrophonePermission, + icon: const Icon(Icons.mic), + label: const Text('Request Microphone Permission'), + ), + const SizedBox(height: 24), + const Text( + 'Photos Permission', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _isLoading ? null : _checkPhotosPermission, + icon: const Icon(Icons.check_circle_outline), + label: const Text('Check Photos Permission'), + ), + const SizedBox(height: 8), + ElevatedButton.icon( + onPressed: _isLoading ? null : _requestPhotosPermission, + icon: const Icon(Icons.photo_library), + label: const Text('Request Photos Permission'), + ), + const SizedBox(height: 24), + const Text( + 'System Alert Window / Display Over Other Apps', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _isLoading ? null : _checkSystemAlertWindowPermission, + icon: const Icon(Icons.check_circle_outline), + label: const Text('Check System Alert Window'), + ), + const SizedBox(height: 8), + ElevatedButton.icon( + onPressed: _isLoading ? null : _requestSystemAlertWindowPermission, + icon: const Icon(Icons.picture_in_picture), + label: const Text('Request System Alert Window'), + ), + const SizedBox(height: 24), + const Text( + 'Multiple Permissions', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _isLoading ? null : _requestMultiplePermissions, + icon: const Icon(Icons.list), + label: const Text('Request Multiple Permissions'), + ), + const SizedBox(height: 24), + const Text( + 'Settings', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _isLoading ? null : _openSettings, + icon: const Icon(Icons.settings), + label: const Text('Open App Settings'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white, + ), + ), + ], + ), + ), + ); + } + + Future _checkCameraPermission() async { + setState(() => _isLoading = true); + try { + final status = await Permission.check(PermissionType.camera); + setState(() { + _statusMessage = '📷 Camera: ${_getStatusEmoji(status)} ${status.value}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _requestCameraPermission() async { + setState(() => _isLoading = true); + try { + final results = await Permission.request([PermissionType.camera]); + final status = results[PermissionType.camera] ?? PermissionStatus.denied; + setState(() { + _statusMessage = '📷 Camera Request: ${_getStatusEmoji(status)} ${status.value}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _checkMicrophonePermission() async { + setState(() => _isLoading = true); + try { + final status = await Permission.check(PermissionType.microphone); + setState(() { + _statusMessage = '🎤 Microphone: ${_getStatusEmoji(status)} ${status.value}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _requestMicrophonePermission() async { + setState(() => _isLoading = true); + try { + final results = await Permission.request([PermissionType.microphone]); + final status = results[PermissionType.microphone] ?? PermissionStatus.denied; + setState(() { + _statusMessage = '🎤 Microphone Request: ${_getStatusEmoji(status)} ${status.value}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _checkPhotosPermission() async { + setState(() => _isLoading = true); + try { + final status = await Permission.check(PermissionType.photos); + setState(() { + _statusMessage = '📸 Photos: ${_getStatusEmoji(status)} ${status.value}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _requestPhotosPermission() async { + setState(() => _isLoading = true); + try { + final results = await Permission.request([PermissionType.photos]); + final status = results[PermissionType.photos] ?? PermissionStatus.denied; + setState(() { + _statusMessage = '📸 Photos Request: ${_getStatusEmoji(status)} ${status.value}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _requestMultiplePermissions() async { + setState(() => _isLoading = true); + try { + final results = await Permission.request([ + PermissionType.camera, + PermissionType.microphone, + PermissionType.photos, + ]); + + final message = results.entries + .map((e) { + final name = e.key.name; + return '$name: ${_getStatusEmoji(e.value)} ${e.value.value}'; + }) + .join('\n'); + + setState(() { + _statusMessage = 'Multiple Permissions:\n$message'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + String _getStatusEmoji(PermissionStatus status) { + switch (status) { + case PermissionStatus.granted: + return '✅'; + case PermissionStatus.denied: + return '❌'; + case PermissionStatus.permanentlyDenied: + return '🚫'; + case PermissionStatus.limited: + return '⚡'; + case PermissionStatus.notDetermined: + return '❓'; + default: + return '❔'; + } + } + + Future _openSettings() async { + setState(() => _isLoading = true); + try { + final opened = await Permission.openAppSettings(); + setState(() { + _statusMessage = 'Open Settings: ${opened ? "✅ Success" : "❌ Failed"}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _checkSystemAlertWindowPermission() async { + setState(() => _isLoading = true); + try { + final status = await Permission.check(PermissionType.systemAlertWindow); + setState(() { + _statusMessage = '🪟 System Alert Window: ${_getStatusEmoji(status)} ${status.value}'; + }); + } finally { + setState(() => _isLoading = false); + } + } + + Future _requestSystemAlertWindowPermission() async { + setState(() => _isLoading = true); + try { + final results = await Permission.request([PermissionType.systemAlertWindow]); + final status = results[PermissionType.systemAlertWindow] ?? PermissionStatus.denied; + setState(() { + _statusMessage = '🪟 System Alert Window Request: ${_getStatusEmoji(status)} ${status.value}\n' + 'Note: On Android, this will open system settings.'; + }); + } finally { + setState(() => _isLoading = false); + } + } +} diff --git a/atomic-x/example/pubspec.yaml b/atomic-x/example/pubspec.yaml new file mode 100644 index 00000000..d6678048 --- /dev/null +++ b/atomic-x/example/pubspec.yaml @@ -0,0 +1,93 @@ +name: example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.6.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + + # Atomic-X plugin + tuikit_atomic_x: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/atomic-x/ios/Classes/AlbumPicker/AlbumPickerHandler.swift b/atomic-x/ios/Classes/AlbumPicker/AlbumPickerHandler.swift index 016bd20e..9395786b 100644 --- a/atomic-x/ios/Classes/AlbumPicker/AlbumPickerHandler.swift +++ b/atomic-x/ios/Classes/AlbumPicker/AlbumPickerHandler.swift @@ -384,16 +384,16 @@ class AlbumPickerHandler: NSObject { // MARK: - AlbumPickerDelegateProxy -/// Per-session delegate that captures sessionId and injects it into every event. -/// This is the iOS equivalent of Android's `val capturedSessionId = sessionId` -/// closure capture in `createAlbumPickerListener`. class AlbumPickerDelegateProxy: NSObject, AlbumPickerDelegate { let sessionId: String private weak var handler: AlbumPickerHandler? + private let serialQueue: DispatchQueue + init(sessionId: String, handler: AlbumPickerHandler) { self.sessionId = sessionId self.handler = handler + self.serialQueue = DispatchQueue(label: "com.albumpicker.event-queue.\(sessionId)", qos: .userInitiated) super.init() } @@ -404,33 +404,30 @@ class AlbumPickerDelegateProxy: NSObject, AlbumPickerDelegate { handler?.viewController?.dismiss(animated: true) } - DispatchQueue.global(qos: .userInitiated).async { [weak self] in + serialQueue.async { [weak self] in guard let self = self else { return } - let group = DispatchGroup() for media in pickedAlbumMedias { guard media.videoThumbnailPath == nil else { continue } - group.enter() - DispatchQueue.global(qos: .userInitiated).async { - if let path = self.handler?.generateThumbnailForMedia(media) { - media.videoThumbnailPath = path - } - group.leave() + if let path = self.handler?.generateThumbnailForMedia(media) { + media.videoThumbnailPath = path } } - group.notify(queue: .main) { - var event: [String: Any] = [ - "type": "onPickConfirm", - "sessionId": self.sessionId, - "pickedAlbumMedias": pickedAlbumMedias.map { self.handler?.buildMediaDataDict(albumMedia: $0) ?? [:] }, - ] - if let textMessage = textMessage { - event["textMessage"] = textMessage - } - self.handler?.sendEvent(event) + var event: [String: Any] = [ + "type": "onPickConfirm", + "sessionId": self.sessionId, + "pickedAlbumMedias": pickedAlbumMedias.map { self.handler?.buildMediaDataDict(albumMedia: $0) ?? [:] }, + ] + if let textMessage = textMessage { + event["textMessage"] = textMessage + } + print("[AlbumPickerHandler] onPickConfirm dispatching to Flutter, sessionId=\(self.sessionId)") + self.handler?.sendEvent(event) - if pickedAlbumMedias.isEmpty { + if pickedAlbumMedias.isEmpty { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } self.handler?.completeSession(sessionId: self.sessionId) } } @@ -444,24 +441,32 @@ class AlbumPickerDelegateProxy: NSObject, AlbumPickerDelegate { + " path=\(albumMedia.mediaPath ?? "nil")," + " sessionId=\(sessionId)") - handler?.sendEvent([ - "type": "onMediaProcessing", - "sessionId": sessionId, - "data": handler?.buildMediaDataDict(albumMedia: albumMedia) ?? [:], - "progress": Double(progress), - "error": error, - ]) + serialQueue.async { [weak self] in + guard let self = self else { return } + self.handler?.sendEvent([ + "type": "onMediaProcessing", + "sessionId": self.sessionId, + "data": self.handler?.buildMediaDataDict(albumMedia: albumMedia) ?? [:], + "progress": Double(progress), + "error": error, + ]) + } } func onMediaProcessed() { print("[AlbumPickerHandler] onMediaProcessed, sessionId=\(sessionId)") - handler?.sendEvent([ - "type": "onMediaProcessed", - "sessionId": sessionId, - ]) - - handler?.completeSession(sessionId: sessionId) + serialQueue.async { [weak self] in + guard let self = self else { return } + self.handler?.sendEvent([ + "type": "onMediaProcessed", + "sessionId": self.sessionId, + ]) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.handler?.completeSession(sessionId: self.sessionId) + } + } } func onCancel() { diff --git a/atomic-x/ios/Classes/Permission/PermissionHandler.swift b/atomic-x/ios/Classes/Permission/PermissionHandler.swift index 461deeef..18e895d5 100644 --- a/atomic-x/ios/Classes/Permission/PermissionHandler.swift +++ b/atomic-x/ios/Classes/Permission/PermissionHandler.swift @@ -84,7 +84,11 @@ class PermissionHandler { case .restricted: return "denied" case .notDetermined: - return "denied" + // The user has never been asked. Keep this distinct from `denied` + // so the Dart layer can tell "first-time access" apart from + // "previously refused" and avoid wrongly steering the user to the + // system Settings page. + return "notDetermined" @unknown default: return "denied" } @@ -131,7 +135,7 @@ class PermissionHandler { case .restricted: return "denied" case .notDetermined: - return "denied" + return "notDetermined" @unknown default: return "denied" } @@ -145,7 +149,7 @@ class PermissionHandler { case .restricted: return "denied" case .notDetermined: - return "denied" + return "notDetermined" @unknown default: return "denied" } @@ -212,7 +216,11 @@ class PermissionHandler { case .denied: return "permanentlyDenied" case .undetermined: - return "denied" + // The user has never been asked. Keep this distinct from `denied` + // so the Dart layer can tell "first-time access" apart from + // "previously refused" and avoid wrongly steering the user to the + // system Settings page. + return "notDetermined" @unknown default: return "denied" } @@ -244,7 +252,7 @@ class PermissionHandler { case .denied: status = "permanentlyDenied" case .notDetermined: - status = "denied" + status = "notDetermined" case .provisional, .ephemeral: status = "limited" @unknown default: diff --git a/atomic-x/lib/atomicx.dart b/atomic-x/lib/atomicx.dart index 1b3362b6..44d2b7ca 100644 --- a/atomic-x/lib/atomicx.dart +++ b/atomic-x/lib/atomicx.dart @@ -5,7 +5,6 @@ export 'package:atomic_x_core/atomicxcore.dart'; export 'ai/ai_transcriber.dart'; export 'album_picker/album_picker.dart'; export 'base_component/base_component.dart'; -export 'call/call_view.dart'; export 'image_uploader/image_uploader.dart'; export 'permission/permission.dart'; export 'pip/android_pip_feature.dart'; diff --git a/atomic-x/lib/base_component/base_component.dart b/atomic-x/lib/base_component/base_component.dart index 19e0cc12..1abba8b7 100644 --- a/atomic-x/lib/base_component/base_component.dart +++ b/atomic-x/lib/base_component/base_component.dart @@ -17,6 +17,5 @@ export 'theme/radius.dart'; export 'theme/spacing.dart'; export 'theme/theme_state.dart'; export 'utils/app_builder.dart'; -export 'utils/chat_utils.dart'; export 'utils/locale_provider.dart'; export 'utils/storage_util.dart'; diff --git a/atomic-x/lib/base_component/l10n/l10n_ar.arb b/atomic-x/lib/base_component/l10n/l10n_ar.arb index 71388220..0a8c9add 100644 --- a/atomic-x/lib/base_component/l10n/l10n_ar.arb +++ b/atomic-x/lib/base_component/l10n/l10n_ar.arb @@ -523,6 +523,7 @@ "callOtherPartyDeclinedCallRequest": "رفض الطرف الآخر المكالمة", "callOtherPartyBusy": "الطرف الآخر مشغول", "callOtherPartyNoResponse": "لم يستجب الطرف الآخر", + "callOtherPartyCanceled": "ألغى الطرف الآخر المكالمة", "callRequestDeclined": "رفض المكالمة", "callEndedTheCall": "أنهى المكالمة", "callGoToSettings": "الذهاب إلى الإعدادات", diff --git a/atomic-x/lib/base_component/l10n/l10n_en.arb b/atomic-x/lib/base_component/l10n/l10n_en.arb index 0e4956b1..fd00429a 100644 --- a/atomic-x/lib/base_component/l10n/l10n_en.arb +++ b/atomic-x/lib/base_component/l10n/l10n_en.arb @@ -523,6 +523,7 @@ "callOtherPartyDeclinedCallRequest": "Call rejected by other party", "callOtherPartyBusy": "Line Busy", "callOtherPartyNoResponse": "The other party did not respond", + "callOtherPartyCanceled": "Other party canceled the call", "callRequestDeclined": "rejected call", "callEndedTheCall": "end the call", "callGoToSettings": "Go to settings", diff --git a/atomic-x/lib/base_component/l10n/l10n_ja.arb b/atomic-x/lib/base_component/l10n/l10n_ja.arb index 0ee37e17..ec2621c6 100644 --- a/atomic-x/lib/base_component/l10n/l10n_ja.arb +++ b/atomic-x/lib/base_component/l10n/l10n_ja.arb @@ -523,6 +523,7 @@ "callOtherPartyDeclinedCallRequest": "相手による通話拒否", "callOtherPartyBusy": "相手が通話中です", "callOtherPartyNoResponse": "呼び出しタイムアウト", + "callOtherPartyCanceled": "相手が通話をキャンセルしました", "callRequestDeclined": "は通話を拒否しました", "callEndedTheCall": "通話終了", "callGoToSettings": "設定に移動", diff --git a/atomic-x/lib/base_component/l10n/l10n_ko.arb b/atomic-x/lib/base_component/l10n/l10n_ko.arb index 69f1027b..2bfb213a 100644 --- a/atomic-x/lib/base_component/l10n/l10n_ko.arb +++ b/atomic-x/lib/base_component/l10n/l10n_ko.arb @@ -524,6 +524,7 @@ "callOtherPartyDeclinedCallRequest": "상대방이 통화를 거절했습니다", "callOtherPartyBusy": "상대방이 통화 중입니다", "callOtherPartyNoResponse": "상대방이 응답하지 않았습니다", + "callOtherPartyCanceled": "상대방이 통화를 취소했습니다", "callRequestDeclined": "통화를 거절했습니다", "callEndedTheCall": "통화를 종료했습니다", "callGoToSettings": "설정으로 이동", diff --git a/atomic-x/lib/base_component/l10n/l10n_zh.arb b/atomic-x/lib/base_component/l10n/l10n_zh.arb index 4dcc0522..35611007 100644 --- a/atomic-x/lib/base_component/l10n/l10n_zh.arb +++ b/atomic-x/lib/base_component/l10n/l10n_zh.arb @@ -523,6 +523,7 @@ "callOtherPartyDeclinedCallRequest": "对方拒绝了通话请求", "callOtherPartyBusy": "对方忙", "callOtherPartyNoResponse": "对方未响应", + "callOtherPartyCanceled": "对方已取消通话", "callRequestDeclined": "拒绝了通话请求", "callEndedTheCall": "结束了通话", "callGoToSettings": "去设置", diff --git a/atomic-x/lib/base_component/l10n/l10n_zh_Hans.arb b/atomic-x/lib/base_component/l10n/l10n_zh_Hans.arb index 4dcc0522..35611007 100644 --- a/atomic-x/lib/base_component/l10n/l10n_zh_Hans.arb +++ b/atomic-x/lib/base_component/l10n/l10n_zh_Hans.arb @@ -523,6 +523,7 @@ "callOtherPartyDeclinedCallRequest": "对方拒绝了通话请求", "callOtherPartyBusy": "对方忙", "callOtherPartyNoResponse": "对方未响应", + "callOtherPartyCanceled": "对方已取消通话", "callRequestDeclined": "拒绝了通话请求", "callEndedTheCall": "结束了通话", "callGoToSettings": "去设置", diff --git a/atomic-x/lib/base_component/l10n/l10n_zh_Hant.arb b/atomic-x/lib/base_component/l10n/l10n_zh_Hant.arb index 88ebf620..43dcac4f 100644 --- a/atomic-x/lib/base_component/l10n/l10n_zh_Hant.arb +++ b/atomic-x/lib/base_component/l10n/l10n_zh_Hant.arb @@ -523,6 +523,7 @@ "callOtherPartyDeclinedCallRequest": "對方拒絕了通話請求", "callOtherPartyBusy": "對方忙", "callOtherPartyNoResponse": "對方未響應", + "callOtherPartyCanceled": "對方已取消通話", "callRequestDeclined": "拒絕了通話請求", "callEndedTheCall": "結束了通話", "callGoToSettings": "去設置", diff --git a/atomic-x/lib/base_component/localizations/atomic_localizations.dart b/atomic-x/lib/base_component/localizations/atomic_localizations.dart index 67dfac97..7e1779a6 100644 --- a/atomic-x/lib/base_component/localizations/atomic_localizations.dart +++ b/atomic-x/lib/base_component/localizations/atomic_localizations.dart @@ -2932,6 +2932,12 @@ abstract class AtomicLocalizations { /// **'The other party did not respond'** String get callOtherPartyNoResponse; + /// No description provided for @callOtherPartyCanceled. + /// + /// In en, this message translates to: + /// **'Other party canceled the call'** + String get callOtherPartyCanceled; + /// No description provided for @callRequestDeclined. /// /// In en, this message translates to: diff --git a/atomic-x/lib/base_component/localizations/atomic_localizations_ar.dart b/atomic-x/lib/base_component/localizations/atomic_localizations_ar.dart index a8d8fa6e..be46ef23 100644 --- a/atomic-x/lib/base_component/localizations/atomic_localizations_ar.dart +++ b/atomic-x/lib/base_component/localizations/atomic_localizations_ar.dart @@ -1489,6 +1489,9 @@ class AtomicLocalizationsAr extends AtomicLocalizations { @override String get callOtherPartyNoResponse => 'لم يستجب الطرف الآخر'; + @override + String get callOtherPartyCanceled => 'ألغى الطرف الآخر المكالمة'; + @override String get callRequestDeclined => 'رفض المكالمة'; diff --git a/atomic-x/lib/base_component/localizations/atomic_localizations_en.dart b/atomic-x/lib/base_component/localizations/atomic_localizations_en.dart index 1811b587..272a2681 100644 --- a/atomic-x/lib/base_component/localizations/atomic_localizations_en.dart +++ b/atomic-x/lib/base_component/localizations/atomic_localizations_en.dart @@ -1495,6 +1495,9 @@ class AtomicLocalizationsEn extends AtomicLocalizations { @override String get callOtherPartyNoResponse => 'The other party did not respond'; + @override + String get callOtherPartyCanceled => 'Other party canceled the call'; + @override String get callRequestDeclined => 'rejected call'; diff --git a/atomic-x/lib/base_component/localizations/atomic_localizations_ja.dart b/atomic-x/lib/base_component/localizations/atomic_localizations_ja.dart index d1a06c8c..a68f9637 100644 --- a/atomic-x/lib/base_component/localizations/atomic_localizations_ja.dart +++ b/atomic-x/lib/base_component/localizations/atomic_localizations_ja.dart @@ -1471,6 +1471,9 @@ class AtomicLocalizationsJa extends AtomicLocalizations { @override String get callOtherPartyNoResponse => '呼び出しタイムアウト'; + @override + String get callOtherPartyCanceled => '相手が通話をキャンセルしました'; + @override String get callRequestDeclined => 'は通話を拒否しました'; diff --git a/atomic-x/lib/base_component/localizations/atomic_localizations_ko.dart b/atomic-x/lib/base_component/localizations/atomic_localizations_ko.dart index b8cacac0..4334462c 100644 --- a/atomic-x/lib/base_component/localizations/atomic_localizations_ko.dart +++ b/atomic-x/lib/base_component/localizations/atomic_localizations_ko.dart @@ -1470,6 +1470,9 @@ class AtomicLocalizationsKo extends AtomicLocalizations { @override String get callOtherPartyNoResponse => '상대방이 응답하지 않았습니다'; + @override + String get callOtherPartyCanceled => '상대방이 통화를 취소했습니다'; + @override String get callRequestDeclined => '통화를 거절했습니다'; diff --git a/atomic-x/lib/base_component/localizations/atomic_localizations_zh.dart b/atomic-x/lib/base_component/localizations/atomic_localizations_zh.dart index 7ff1e9c3..859b1464 100644 --- a/atomic-x/lib/base_component/localizations/atomic_localizations_zh.dart +++ b/atomic-x/lib/base_component/localizations/atomic_localizations_zh.dart @@ -1468,6 +1468,8 @@ class AtomicLocalizationsZh extends AtomicLocalizations { @override String get callOtherPartyNoResponse => '对方未响应'; + @override + String get callOtherPartyCanceled => '对方已取消通话'; @override String get callRequestDeclined => '拒绝了通话请求'; @@ -3002,6 +3004,8 @@ class AtomicLocalizationsZhHans extends AtomicLocalizationsZh { @override String get callOtherPartyNoResponse => '对方未响应'; + @override + String get callOtherPartyCanceled => '对方已取消通话'; @override String get callRequestDeclined => '拒绝了通话请求'; @@ -4536,6 +4540,9 @@ class AtomicLocalizationsZhHant extends AtomicLocalizationsZh { @override String get callOtherPartyNoResponse => '對方未響應'; + @override + String get callOtherPartyCanceled => '對方已取消通話'; + @override String get callRequestDeclined => '拒絕了通話請求'; diff --git a/atomic-x/lib/base_component/utils/chat_utils.dart b/atomic-x/lib/base_component/utils/chat_utils.dart deleted file mode 100644 index 04edcbb9..00000000 --- a/atomic-x/lib/base_component/utils/chat_utils.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:atomic_x_core/atomicxcore.dart'; - -class ChatUtils { - static RegExp urlReg = RegExp( - r"([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/|[wW]{3}.|[wW][aA][pP].|[fF][tT][pP].|[fF][iI][lL][eE].)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"); - - /// Group member display name priority: nameCard > friendRemark > nickname > userID - static String memberDisplayName(GroupMember member) { - if (member.nameCard != null && member.nameCard!.isNotEmpty) return member.nameCard!; - if (member.friendRemark != null && member.friendRemark!.isNotEmpty) return member.friendRemark!; - if (member.nickname != null && member.nickname!.isNotEmpty) return member.nickname!; - return member.userID; - } -} \ No newline at end of file diff --git a/atomic-x/lib/call/call_view.dart b/atomic-x/lib/call/call_view.dart deleted file mode 100644 index bbb838fd..00000000 --- a/atomic-x/lib/call/call_view.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:atomic_x_core/atomicxcore.dart'; -import 'package:flutter/material.dart'; -import 'package:tuikit_atomic_x/call/component/widgets/float/call_float_widget.dart'; -import 'package:tuikit_atomic_x/call/component/widgets/grid/call_grid_widget.dart'; -import 'package:tuikit_atomic_x/call/component/widgets/pip/call_pip_widget.dart'; - -class CallView extends StatefulWidget { - final bool isPipMode; - final bool enableAITranscriber; - - const CallView({ - super.key, - this.enableAITranscriber = false, - this.isPipMode = false, - }); - - @override - State createState() => _CallViewState(); -} - -class _CallViewState extends State { - late final CallCoreController controller; - - @override - void initState() { - controller = CallCoreController.create(); - super.initState(); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Material( - child: SizedBox( - width: double.infinity, - height: double.infinity, - child: ValueListenableBuilder( - valueListenable: CallStore.shared.state.activeCall, - builder: (context, activeCall, child) { - if (widget.isPipMode) { - controller.setLayoutTemplate(CallLayoutTemplate.pip); - return CallPipWidget( - controller: controller, - ); - } - - if (activeCall.chatGroupId.isNotEmpty || activeCall.inviteeIds.length > 1) { - controller.setLayoutTemplate(CallLayoutTemplate.grid); - return CallGridWidget( - controller: controller, - enableAITranscriber: widget.enableAITranscriber, - ); - } - - controller.setLayoutTemplate(CallLayoutTemplate.float); - return CallFloatWidget( - controller: controller, - enableAITranscriber: widget.enableAITranscriber, - ); - } - ), - ), - ); - } -} \ No newline at end of file diff --git a/atomic-x/lib/call/component/hint/hint_widget.dart b/atomic-x/lib/call/component/hint/hint_widget.dart deleted file mode 100644 index 63b9cad0..00000000 --- a/atomic-x/lib/call/component/hint/hint_widget.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'dart:async'; - -import 'package:tuikit_atomic_x/atomicx.dart'; -import 'package:flutter/material.dart'; - -import '../../common/call_colors.dart'; - -class _HintDisplayTracker { - static String? _currentCallId; - static bool _hadShowAcceptText = false; - - static bool shouldShowAcceptText(String callId) { - if (_currentCallId != callId) { - _currentCallId = callId; - _hadShowAcceptText = false; - } - return !_hadShowAcceptText; - } - - static void markAcceptTextShown(String callId) { - if (_currentCallId == callId) { - _hadShowAcceptText = true; - } - } -} - -class HintWidget extends StatefulWidget { - const HintWidget({super.key}); - - @override - State createState() => _HintWidgetState(); -} - -class _HintWidgetState extends State { - final _acceptTextDisplayDuration = const Duration(seconds: 1); - Timer? _acceptTextTimer; - - @override - void dispose() { - _acceptTextTimer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final l10n = AtomicLocalizations.of(context); - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, selfInfo, child) { - return _buildConnectionHint(selfInfo, l10n) ?? - _buildStatusHint(selfInfo, l10n) ?? - _buildNetworkQualityHint(selfInfo, l10n) ?? - const SizedBox.shrink(); - }, - ); - } - - Widget? _buildConnectionHint(CallParticipantInfo selfInfo, AtomicLocalizations l10n) { - final activeCall = CallStore.shared.state.activeCall.value; - final callId = activeCall.callId; - - if (selfInfo.status != CallParticipantStatus.accept || - !_HintDisplayTracker.shouldShowAcceptText(callId)) { - return null; - } - - _acceptTextTimer?.cancel(); - _acceptTextTimer = Timer(_acceptTextDisplayDuration, () { - if (mounted) { - _HintDisplayTracker.markAcceptTextShown(callId); - setState(() {}); - } - }); - - return Text( - l10n.callConnected, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: CallColors.colorG7, - ), - ); - } - - Widget? _buildStatusHint(CallParticipantInfo selfInfo, AtomicLocalizations l10n) { - if (selfInfo.status != CallParticipantStatus.waiting) { - return null; - } - - final activeCall = CallStore.shared.state.activeCall.value; - - if (selfInfo.id == activeCall.inviterId) { - return Text( - l10n.callWaitingForInvitationAcceptance, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _getHintTextColor(), - ), - ); - } else { - final hintText = activeCall.mediaType == CallMediaType.audio - ? l10n.callInvitedToAudioCall - : l10n.callInvitedToVideoCall; - - return Text( - hintText, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _getHintTextColor(), - ), - ); - } - } - - Widget? _buildNetworkQualityHint(CallParticipantInfo selfInfo, AtomicLocalizations l10n) { - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.networkQualities, - builder: (context, networkQualities, child) { - final hintText = _getNetworkQualityHintText(selfInfo, networkQualities, l10n); - return hintText.isNotEmpty - ? Text( - hintText, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _getHintTextColor(), - ), - ) - : const SizedBox(); - }, - ); - } - - String _getNetworkQualityHintText( - CallParticipantInfo selfInfo, - Map networkQualities, - AtomicLocalizations l10n, - ) { - final selfNetwork = networkQualities[selfInfo.id]; - if (selfNetwork != null && _isBadNetwork(selfNetwork)) { - return l10n.callSelfNetworkLowQuality; - } - - for (var entry in networkQualities.entries) { - if (entry.key != selfInfo.id && _isBadNetwork(entry.value)) { - return l10n.callOtherPartyNetworkLowQuality; - } - } - - return ''; - } - - bool _isBadNetwork(NetworkQuality? network) { - return network == NetworkQuality.bad || - network == NetworkQuality.veryBad || - network == NetworkQuality.down; - } - - Color _getHintTextColor() { - if (CallStore.shared.state.activeCall.value.mediaType == CallMediaType.video) { - return CallColors.colorWhite; - } - return CallColors.colorG7; - } -} \ No newline at end of file diff --git a/atomic-x/lib/call/component/hint/timer_widget.dart b/atomic-x/lib/call/component/hint/timer_widget.dart deleted file mode 100644 index d2314e03..00000000 --- a/atomic-x/lib/call/component/hint/timer_widget.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:tuikit_atomic_x/atomicx.dart'; -import 'package:flutter/material.dart'; - -import '../../common/call_colors.dart'; - -class TimerWidget extends StatelessWidget { - final double? fontSize; - final FontWeight? fontWeight; - - const TimerWidget({ - super.key, - this.fontSize, - this.fontWeight, - }); - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, info, child) { - if (info.status == CallParticipantStatus.accept) { - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.activeCall, - builder: (context, activeCall, child) { - return Text( - formatDuration(activeCall.duration.toInt()), - style: TextStyle( - fontSize: fontSize, - fontWeight: fontWeight, - color: CallStore.shared.state.activeCall.value.mediaType == CallMediaType.audio - ? CallColors.colorG7 - : CallColors.colorWhite, - ), - ); - }, - ); - } else { - return Container(); - } - } - ); - } - - String formatDuration(int timeCount) { - int hour = timeCount ~/ 3600; - int minute = (timeCount % 3600) ~/ 60; - String minuteShow = minute <= 9 ? "0$minute" : "$minute"; - int second = timeCount % 60; - String secondShow = second <= 9 ? "0$second" : "$second"; - - if (hour > 0) { - String hourShow = hour <= 9 ? "0$hour" : "$hour"; - return '$hourShow:$minuteShow:$secondShow'; - } else { - return '$minuteShow:$secondShow'; - } - } - -} \ No newline at end of file diff --git a/atomic-x/lib/call/component/widgets/float/call_float_widget.dart b/atomic-x/lib/call/component/widgets/float/call_float_widget.dart deleted file mode 100644 index 64374a4d..00000000 --- a/atomic-x/lib/call/component/widgets/float/call_float_widget.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:atomic_x_core/atomicxcore.dart'; -import 'package:flutter/material.dart'; - -import '../../../../ai/ai_transcriber.dart'; -import '../../../common/call_colors.dart'; -import '../../../common/constants.dart'; -import '../../aisubtitle/ai_subtitle.dart'; -import '../../../common/utils/utils.dart'; -import '../../controls/single_call_controls_widget.dart'; -import '../../hint/hint_widget.dart'; -import '../../hint/timer_widget.dart'; - -class CallFloatWidget extends StatefulWidget { - final CallCoreController controller; - final bool enableAITranscriber; - - const CallFloatWidget({ - super.key, - required this.controller, - this.enableAITranscriber = false, - }); - - @override - State createState() => _CallFloatWidgetState(); -} - -class _CallFloatWidgetState extends State { - final GlobalKey _controlsKey = GlobalKey(); - double _controlsHeight = 120; - - void _measureControlsHeight() { - WidgetsBinding.instance.addPostFrameCallback((_) { - final renderBox = _controlsKey.currentContext?.findRenderObject() as RenderBox?; - if (renderBox != null && mounted) { - final height = renderBox.size.height; - if (height != _controlsHeight) { - setState(() { - _controlsHeight = height; - }); - } - } - }); - } - - @override - Widget build(BuildContext context) { - _measureControlsHeight(); - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, self, child) { - return Stack( - children: [ - CallCoreView( - controller: widget.controller, - defaultAvatar: Constants.defaultAvatarImage, - ), - _buildUserInfoWidget(context), - Positioned( - top: MediaQuery.of(context).size.height * 2 / 3, - width: MediaQuery.of(context).size.width, - child: const Center(child: HintWidget()), - ), - Positioned( - left: 0, - right: 0, - bottom: 40 + _controlsHeight + 8 + MediaQuery.of(context).padding.bottom, - child: Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * 0.9, - maxHeight: MediaQuery.of(context).size.height * 0.3, - ), - child: AISubtitle(userId: CallStore.shared.state.selfInfo.value.id), - ), - ), - - if (widget.enableAITranscriber && self.status == CallParticipantStatus.accept) - AITranscriberPanel( - bottomOffset: _controlsHeight + 48 + MediaQuery.of(context).padding.bottom, - animationDuration: Duration.zero, - ), - - Positioned( - right: 0, - left: 0, - bottom: 40 + MediaQuery.of(context).padding.bottom, - child: SingleCallControlsWidget(key: _controlsKey), - ), - _getTimerWidget(), - _buildAITranscriberBtnWidget(), - ], - ); - }, - ); - } - - Widget _buildUserInfoWidget(BuildContext context) { - if (CallStore.shared.state.activeCall.value.mediaType == CallMediaType.video - && CallStore.shared.state.selfInfo.value.status == CallParticipantStatus.accept) { - return Container(); - } - return Positioned( - top: MediaQuery.of(context).size.height / 4, - width: MediaQuery.of(context).size.width, - child: ValueListenableBuilder( - valueListenable: CallStore.shared.state.allParticipants, - builder: (context, allParticipants, child) { - CallParticipantInfo? remoteParticipant; - for (var participant in allParticipants) { - if (participant.id != CallStore.shared.state.selfInfo.value.id) { - remoteParticipant = participant; - } - } - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: 100, - width: 100, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - child: Image( - image: NetworkImage( - StringStream.makeNull(remoteParticipant?.avatarURL, Constants.defaultAvatar), - ), - fit: BoxFit.cover, - errorBuilder: (ctx, err, stackTrace) => Image.asset( - 'call_assets/user_icon.png', - package: 'tuikit_atomic_x', - ), - ), - ), - const SizedBox(height: 10), - Text( - remoteParticipant != null - ? _getUserDisplayName(remoteParticipant) - : "", - textScaleFactor: 1.0, - style: TextStyle( - fontSize: 18, - color: _getUserNameColor(), - fontWeight: FontWeight.bold, - ), - ), - ], - ); - }, - ), - ); - } - - Widget _getTimerWidget() { - return Positioned( - top: 20, - width: MediaQuery.of(context).size.width, - height: 100, - child: const Center( - child: TimerWidget(), - ), - ); - } - - String _getUserDisplayName(CallParticipantInfo info) { - if (info.remark.isNotEmpty) { - return info.remark; - } else if (info.name.isNotEmpty) { - return info.name; - } else { - return info.id; - } - } - - _getUserNameColor() { - return CallStore.shared.state.activeCall.value.mediaType == CallMediaType.audio - ? CallColors.colorG7 - : CallColors.colorWhite; - } - - _buildAITranscriberBtnWidget() { - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, selfInfo, child) { - if (selfInfo.status != CallParticipantStatus.accept || !widget.enableAITranscriber) { - return const SizedBox(); - } - return const Positioned( - left: 52, - top: 52, - width: 40, - height: 40, - child: AITranscriberButton(), - ); - }, - ); - } -} \ No newline at end of file diff --git a/atomic-x/lib/call/component/widgets/grid/call_grid_widget.dart b/atomic-x/lib/call/component/widgets/grid/call_grid_widget.dart deleted file mode 100644 index 9e75f880..00000000 --- a/atomic-x/lib/call/component/widgets/grid/call_grid_widget.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:atomic_x_core/atomicxcore.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:tuikit_atomic_x/call/component/widgets/grid/call_grid_waiting_widget.dart'; - -import '../../../../ai/ai_transcriber.dart'; -import '../../../common/constants.dart'; -import '../../../common/utils/utils.dart'; -import '../../aisubtitle/ai_subtitle.dart'; -import '../../controls/multi_call_controls_widget.dart'; -import '../../hint/timer_widget.dart'; - -class CallGridWidget extends StatefulWidget { - final CallCoreController controller; - final bool enableAITranscriber; - - const CallGridWidget({ - super.key, - required this.controller, - this.enableAITranscriber = false, - }); - - @override - State createState() => _CallGridWidgetState(); -} - -class _CallGridWidgetState extends State { - double _controlsHeight = 115; - static const int _animationDuration = 300; - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, selfInfo, child) { - return Stack( - children: [ - Positioned.fill( - child: Image( - image: NetworkImage( - StringStream.makeNull(selfInfo.avatarURL, Constants.defaultAvatar), - ), - fit: BoxFit.cover, - errorBuilder: (ctx, err, stackTrace) => Image.asset( - 'call_assets/user_icon.png', - package: 'tuikit_atomic_x', - ), - ), - ), - Opacity( - opacity: 1, - child: Container( - color: const Color.fromRGBO(45, 45, 45, 0.9), - ), - ), - selfInfo.id != CallStore.shared.state.activeCall.value.inviterId - && selfInfo.status == CallParticipantStatus.waiting - ? _buildReceivedGroupCallWaiting(context) - : _buildCallGridView(), - Positioned( - top: 20, - width: MediaQuery.of(context).size.width, - height: 100, - child: const Center( - child: TimerWidget( - fontSize: 18, - fontWeight: FontWeight.w500, - ), - ), - ), - Positioned( - left: 0, - right: 0, - bottom: _controlsHeight + 8 + MediaQuery.of(context).padding.bottom, - child: Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * 0.9, - maxHeight: MediaQuery.of(context).size.height * 0.3, - ), - child: AISubtitle(userId: CallStore.shared.state.selfInfo.value.id), - ), - ), - - if (widget.enableAITranscriber && selfInfo.status == CallParticipantStatus.accept) - AITranscriberPanel( - bottomOffset: _controlsHeight + 8 + MediaQuery.of(context).padding.bottom, - animationDuration: const Duration(milliseconds: _animationDuration), - ), - - Positioned( - right: 0, - left: 0, - bottom: MediaQuery.of(context).padding.bottom, - child: MultiCallControlsWidget( - onHeightChanged: (height) { - setState(() => _controlsHeight = height); - }, - ), - ), - _buildAITranscriberBtnWidget(), - ], - ); - }, - ); - } - - Widget _buildCallGridView() { - return Container( - margin: const EdgeInsets.only(top: 90), - child: CallCoreView( - controller: widget.controller, - defaultAvatar: Constants.defaultAvatarImage, - loadingAnimation: Constants.loading, - volumeIcons: Constants.volumeIcons, - networkQualityIcons: Constants.networkQualityIcons, - ), - ); - } - - Widget _buildReceivedGroupCallWaiting(BuildContext context) { - return Positioned( - top: 0, - left: 0, - width: MediaQuery.of(context).size.width, - child: const CallGridWaitingWidget(), - ); - } - - _buildAITranscriberBtnWidget() { - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, selfInfo, child) { - if (selfInfo.status != CallParticipantStatus.accept || !widget.enableAITranscriber) { - return const SizedBox(); - } - return const Positioned( - left: 52, - top: 52, - width: 40, - height: 40, - child: AITranscriberButton(), - ); - }, - ); - } -} \ No newline at end of file diff --git a/atomic-x/lib/permission/permission.dart b/atomic-x/lib/permission/permission.dart index 421dcb82..4480fbfe 100644 --- a/atomic-x/lib/permission/permission.dart +++ b/atomic-x/lib/permission/permission.dart @@ -55,19 +55,37 @@ enum PermissionType { /// Permission status returned by the platform. enum PermissionStatus { - /// 完全授权:功能可用 + /// Fully granted — the feature is available. granted('granted'), - /// 拒绝或未授权:功能不可用,包含可重试(Deny)和系统受限(Restricted/Unknown)状态 + /// Denied or unauthorized — the feature is unavailable. Covers both + /// retryable denials and system-restricted states (Restricted / Unknown). denied('denied'), - /// 永久拒绝:功能不可用,需引导用户至系统设置 + /// Permanently denied — the feature is unavailable and the user must be + /// guided to the system Settings to re-enable it. permanentlyDenied('permanentlyDenied'), - /// 部分授权:功能受限可用 - /// - iOS: 相册(Photos)的部分访问权限(iOS 14+),通知的临时授权 - /// - Android: 相册(Photos)的部分访问权限(Android 14+,用户选择"仅允许访问选定的照片和视频") - limited('limited'); + /// Partially granted — the feature works with reduced scope. + /// - iOS: Photos limited access (iOS 14+); provisional / ephemeral + /// notifications. + /// - Android: Photos partial access (Android 14+, "Allow access to + /// selected photos and videos only"). + limited('limited'), + + /// Not yet determined — the user has never been asked for this permission. + /// - iOS: Maps to the native `notDetermined` / `undetermined` state. + /// The OS will surface its native prompt the next time the permission + /// is requested (or, for some APIs, when the underlying resource is + /// first accessed by the SDK). + /// - Android: Currently unused (Android distinguishes the first-ask state + /// via `shouldShowRationale` plus a persisted flag and does not expose + /// this value). + /// + /// Callers deciding whether to send the user to the system Settings should + /// EXCLUDE this state — `notDetermined` is not a refusal, just an absence + /// of prior interaction. + notDetermined('notDetermined'); const PermissionStatus(this.value); @@ -84,6 +102,10 @@ enum PermissionStatus { return PermissionStatus.permanentlyDenied; case 'limited': return PermissionStatus.limited; + case 'notdetermined': + case 'not_determined': + case 'undetermined': + return PermissionStatus.notDetermined; case 'denied': case 'restricted': case 'unknown': diff --git a/atomic-x/pubspec.yaml b/atomic-x/pubspec.yaml index eeb22810..8a10ea6b 100644 --- a/atomic-x/pubspec.yaml +++ b/atomic-x/pubspec.yaml @@ -1,6 +1,6 @@ name: tuikit_atomic_x description: "tuikit_atomic_x is a public UI library for Tencent components." -version: 5.0.0 +version: 5.1.0 homepage: https://trtc.io environment: @@ -25,7 +25,7 @@ dependencies: tencent_cloud_chat_sdk: any tencent_rtc_sdk: any - atomic_x_core: ^5.0.0 + atomic_x_core: ^5.0.2 dev_dependencies: flutter_test: diff --git a/call/.gitignore b/call/.gitignore new file mode 100644 index 00000000..ac5aa989 --- /dev/null +++ b/call/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/call/CHANGELOG.md b/call/CHANGELOG.md index ecd74806..b1ea9f2f 100644 --- a/call/CHANGELOG.md +++ b/call/CHANGELOG.md @@ -1,3 +1,14 @@ +## Version 4.1.1 +### Dependency Description +- Upgrade tuikit_atomic_x to ^4.1.1. + +## Version 4.1.0 +### Dependency Description +- Upgrade tuikit_atomic_x to 4.1.0. +### Bugfix +- Optimized the routing scheme for the call page to support displaying other overlays. +- Optimize the Text Internationalization Strategy. + ## Version 4.0.8 ### Dependency Description - Upgrade tuikit_atomic_x to 4.0.0. diff --git a/call/lib/src/bridge/voip/fcm_data_sync_handler.dart b/call/lib/src/bridge/voip/fcm_data_sync_handler.dart index 34e551af..46e31420 100644 --- a/call/lib/src/bridge/voip/fcm_data_sync_handler.dart +++ b/call/lib/src/bridge/voip/fcm_data_sync_handler.dart @@ -1,6 +1,7 @@ import 'package:tencent_calls_uikit/src/common/platform/call_kit_platform_interface.dart'; import 'package:tuikit_atomic_x/atomicx.dart'; import 'package:tencent_calls_uikit/src/common/utils/app_lifecycle.dart'; +import 'package:tencent_calls_uikit/src/manager/call_manager.dart'; class FcmDataSyncHandler { FcmDataSyncHandler() { @@ -24,10 +25,10 @@ class FcmDataSyncHandler { } void handleFcmReject() { - CallStore.shared.reject(); + CallManager.instance.reject(); } void handleFcmAccept() { - CallStore.shared.accept(); + CallManager.instance.accept(); } } \ No newline at end of file diff --git a/call/lib/src/bridge/voip/voip_data_sync_handler.dart b/call/lib/src/bridge/voip/voip_data_sync_handler.dart index fd057d48..58280d2c 100644 --- a/call/lib/src/bridge/voip/voip_data_sync_handler.dart +++ b/call/lib/src/bridge/voip/voip_data_sync_handler.dart @@ -1,25 +1,26 @@ import 'package:tuikit_atomic_x/atomicx.dart'; import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:atomic_x_core/atomicxcore.dart'; +import 'package:tencent_calls_uikit/src/manager/call_manager.dart'; class VoIPDataSyncHandler { void handleVoipChangeMute(bool mute) { if (mute) { - DeviceStore.shared.closeLocalMicrophone(); + CallManager.instance.closeLocalMicrophone(); } else { - DeviceStore.shared.openLocalMicrophone(); + CallManager.instance.openLocalMicrophone(); } } void handleVoipChangeAudioPlaybackDevice(AudioRoute audioDevice) { - DeviceStore.shared.setAudioRoute(audioDevice); + CallManager.instance.setAudioRoute(audioDevice); } void handleVoipHangup() { - CallStore.shared.hangup(); + CallManager.instance.hangup(); } void handleVoipAccept() { - CallStore.shared.accept(); + CallManager.instance.accept(); } } \ No newline at end of file diff --git a/call/lib/src/common/utils/app_lifecycle.dart b/call/lib/src/common/utils/app_lifecycle.dart index 180e2983..21f96cce 100644 --- a/call/lib/src/common/utils/app_lifecycle.dart +++ b/call/lib/src/common/utils/app_lifecycle.dart @@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../tui_call_kit_impl.dart'; -import '../../view/call_page_manager.dart'; +import '../../manager/call_page_router.dart'; class AppLifecycle with WidgetsBindingObserver { static final AppLifecycle _instance = AppLifecycle._internal(); @@ -32,7 +32,7 @@ class AppLifecycle with WidgetsBindingObserver { @override Future didPopRoute() async { - CallPageType pageType = TUICallKitImpl.instance.pageManager.getCurrentPageRoute(); + CallPageType pageType = TUICallKitImpl.instance.pageRouter.getCurrentPageRoute(); if (pageType == CallPageType.calling || pageType == CallPageType.invite) { return true; } diff --git a/call/lib/src/feature/calling_bell_feature.dart b/call/lib/src/feature/calling_bell_feature.dart index 12e56137..9696354e 100644 --- a/call/lib/src/feature/calling_bell_feature.dart +++ b/call/lib/src/feature/calling_bell_feature.dart @@ -4,7 +4,6 @@ import 'package:file/local.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:tencent_calls_uikit/src/common/utils/logger.dart'; -import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:tencent_calls_uikit/src/state/global_state.dart'; import 'package:path_provider/path_provider.dart'; import 'package:tencent_calls_uikit/src/common/utils/preference.dart'; @@ -29,16 +28,55 @@ class CallingBellFeature { bool isPlaying = false; bool isVibrating = false; + VoidCallback? _selfInfoListener; + bool _initialized = false; + CallingBellFeature._(); - Future startRing() async { + void init() { + if (_initialized) { + return; + } + _initialized = true; + _selfInfoListener = _onSelfInfoChanged; + CallStore.shared.state.selfInfo.addListener(_selfInfoListener!); + } + + void dispose() { + if (!_initialized) { + return; + } + if (_selfInfoListener != null) { + CallStore.shared.state.selfInfo.removeListener(_selfInfoListener!); + _selfInfoListener = null; + } + _initialized = false; + _stopRing(); + } + + void _onSelfInfoChanged() { + final activeCall = CallStore.shared.state.activeCall.value; + if (activeCall.mediaType == null) { + _stopRing(); + return; + } + + final status = CallStore.shared.state.selfInfo.value.status; + if (status == CallParticipantStatus.waiting) { + _startRing(); + } else { + _stopRing(); + } + } + + Future _startRing() async { if (isPlaying) { return; } - + Logger.info('CallingBellFeature startRing'); isPlaying = true; - + final filePath = await _getRingFilePath(); if (filePath.isEmpty) { isPlaying = false; @@ -53,11 +91,11 @@ class CallingBellFeature { } } - Future stopRing() async { + Future _stopRing() async { if (!isPlaying) { return; } - + Logger.info('CallingBellFeature stopRing'); isPlaying = false; @@ -65,6 +103,7 @@ class CallingBellFeature { if (isVibrating) { TUICallKitPlatform.instance.stopVibration(); + isVibrating = false; } } @@ -73,17 +112,18 @@ class CallingBellFeature { if (GlobalState.instance.enableMuteMode) { return ""; } - + final customAssetName = GlobalState.instance.callingBellAssetName; if (customAssetName != null && customAssetName.isNotEmpty) { return await getAssetsFilePath(customAssetName); } - - final customFilePath = await PreferenceUtils.getInstance().getString(_keyRingPath); + + final customFilePath = + await PreferenceUtils.getInstance().getString(_keyRingPath); if (customFilePath.isNotEmpty) { return customFilePath; } - + return await _getAssetsFilePath(_calledRingName); } else { return await _getAssetsFilePath(_callerRingName); @@ -96,23 +136,21 @@ class CallingBellFeature { return userId != inviterId; } - - Future getAssetsFilePath(String assetName) async { if (assetName.isEmpty) { return ""; } - + final tempDirectory = await getTempDirectory(); final filePath = "$tempDirectory/$assetName"; final file = fileSystem.file(filePath); - + if (!await file.exists()) { final byteData = await loadAsset(assetName); await file.create(recursive: true); await file.writeAsBytes(byteData.buffer.asUint8List()); } - + return file.path; } @@ -125,7 +163,8 @@ class CallingBellFeature { final trtcCloud = await TRTCCloud.sharedInstance(); final audioEffectManager = trtcCloud.getAudioEffectManager(); - final param = AudioMusicParam(id: _musicId, path: filePath, loopCount: _loopCount); + final param = + AudioMusicParam(id: _musicId, path: filePath, loopCount: _loopCount); param.publish = false; audioEffectManager.startPlayMusic(param); audioEffectManager.setMusicPlayoutVolume(_musicId, _volume); @@ -141,5 +180,6 @@ class CallingBellFeature { Future loadAsset(String path) => rootBundle.load(path); @visibleForTesting - Future getTempDirectory() async => (await getTemporaryDirectory()).path; -} \ No newline at end of file + Future getTempDirectory() async => + (await getTemporaryDirectory()).path; +} diff --git a/call/lib/src/manager/call_ended_hint_resolver.dart b/call/lib/src/manager/call_ended_hint_resolver.dart new file mode 100644 index 00000000..7e7f8cdb --- /dev/null +++ b/call/lib/src/manager/call_ended_hint_resolver.dart @@ -0,0 +1,21 @@ +import 'package:tuikit_atomic_x/atomicx.dart'; + +class CallEndedHintResolver { + static String? resolveText( + CallEndReason reason, + AtomicLocalizations l10n, + ) { + switch (reason) { + case CallEndReason.hangup: + return l10n.callOtherPartyHungUp; + case CallEndReason.reject: + return l10n.callOtherPartyDeclinedCallRequest; + case CallEndReason.noResponse: + return l10n.callOtherPartyNoResponse; + case CallEndReason.canceled: + return l10n.callOtherPartyCanceled; + default: + return null; + } + } +} diff --git a/call/lib/src/manager/call_manager.dart b/call/lib/src/manager/call_manager.dart new file mode 100644 index 00000000..cb307a0d --- /dev/null +++ b/call/lib/src/manager/call_manager.dart @@ -0,0 +1,190 @@ +import 'dart:async'; + +import 'package:tuikit_atomic_x/atomicx.dart'; +import 'package:tencent_calls_uikit/src/manager/call_page_router.dart'; + +class CallManager { + CallManager._(); + + static final CallManager _instance = CallManager._(); + static CallManager get instance => _instance; + + CallPageRouter? _pageManager; + + void bindPageManager(CallPageRouter pageManager) { + _pageManager = pageManager; + } + + Future setSelfInfo(UserProfile userInfo) { + return LoginStore.shared.setSelfInfo(userInfo: userInfo); + } + + Future calls( + List userIdList, + CallMediaType callMediaType, [ + CallParams? params, + ]) async { + final hasPermission = await _ensureAudioVideoPermission(callMediaType); + if (!hasPermission) { + _pageManager?.handleNoPermissionAndEndCall(); + final handler = CompletionHandler(); + handler.errorCode = -1101; + handler.errorMessage = 'Failed to obtain audio and video permissions'; + return handler; + } + + return CallStore.shared.calls(userIdList, callMediaType, params); + } + + Future join(String callId) { + return CallStore.shared.join(callId); + } + + Future accept() async { + final activeCall = CallStore.shared.state.activeCall.value; + final mediaType = activeCall.mediaType; + if (mediaType == null) { + return; + } + + final canProceed = await _runPermissionGate( + callId: activeCall.callId, + mediaType: mediaType, + ); + if (!canProceed) { + return; + } + unawaited(_openDevices(mediaType)); + await CallStore.shared.accept(); + } + + Future reject() { + return CallStore.shared.reject(); + } + + Future hangup() { + return CallStore.shared.hangup(); + } + + Future openLocalMicrophone() async { + await DeviceStore.shared.openLocalMicrophone(); + } + + Future closeLocalMicrophone() async { + DeviceStore.shared.closeLocalMicrophone(); + } + + Future openLocalCamera(bool isFront) async { + final hasPermission = await _ensurePermission(PermissionType.camera); + if (!hasPermission) { + _pageManager?.handleNoPermissionAndEndCall(); + return; + } + await DeviceStore.shared.openLocalCamera(isFront); + } + + Future closeLocalCamera() async { + DeviceStore.shared.closeLocalCamera(); + } + + Future switchCamera(bool isFront) async { + DeviceStore.shared.switchCamera(isFront); + } + + Future setAudioRoute(AudioRoute route) async { + DeviceStore.shared.setAudioRoute(route); + } + + Future prepareCallDevices({ + required String callId, + required CallMediaType mediaType, + required bool isCalled, + }) async { + final activeCall = _activeCallMatching(callId); + if (activeCall == null) { + return; + } + + final canProceed = await _runPermissionGate( + callId: callId, + mediaType: mediaType, + ); + if (!canProceed) { + return; + } + unawaited(_openDevices(mediaType)); + } + + Future openLocalCameraIfPermitted() async { + final cameraStatus = await Permission.check(PermissionType.camera); + if (cameraStatus != PermissionStatus.granted) { + return; + } + await DeviceStore.shared.openLocalCamera(true); + } + + CallInfo? _activeCallMatching(String expectedCallId) { + final activeCall = CallStore.shared.state.activeCall.value; + if (activeCall.callId != expectedCallId) { + return null; + } + return activeCall; + } + + Future _runPermissionGate({ + required String callId, + required CallMediaType mediaType, + }) async { + final hasPermission = await _ensureAudioVideoPermission(mediaType); + if (_activeCallMatching(callId) == null) { + return false; + } + if (!hasPermission) { + _pageManager?.handleNoPermissionAndEndCall(); + return false; + } + return true; + } + + Future _openDevices(CallMediaType mediaType) async { + await openLocalMicrophone(); + await setAudioRoute( + mediaType == CallMediaType.audio + ? AudioRoute.earpiece + : AudioRoute.speakerphone, + ); + if (mediaType == CallMediaType.video) { + await DeviceStore.shared.openLocalCamera(true); + } + } + + Future _ensureAudioVideoPermission(CallMediaType mediaType) async { + final micOk = await _ensurePermission(PermissionType.microphone); + if (!micOk) { + return false; + } + if (mediaType == CallMediaType.video) { + final cameraOk = await _ensurePermission(PermissionType.camera); + if (!cameraOk) { + return false; + } + } + return true; + } + + Future _ensurePermission(PermissionType type) async { + final status = await Permission.check(type); + if (_isUsable(status)) { + return true; + } + if (status == PermissionStatus.permanentlyDenied) { + return false; + } + final result = await Permission.request([type]); + return _isUsable(result[type] ?? PermissionStatus.denied); + } + + bool _isUsable(PermissionStatus status) => + status == PermissionStatus.granted || + status == PermissionStatus.limited; +} diff --git a/call/lib/src/view/call_page_manager.dart b/call/lib/src/manager/call_page_router.dart similarity index 70% rename from call/lib/src/view/call_page_manager.dart rename to call/lib/src/manager/call_page_router.dart index 9ed3de6f..be06af1d 100644 --- a/call/lib/src/view/call_page_manager.dart +++ b/call/lib/src/manager/call_page_router.dart @@ -8,10 +8,13 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:tuikit_atomic_x/atomicx.dart'; import 'package:tencent_calls_uikit/src/common/utils/app_lifecycle.dart'; +import 'package:tencent_calls_uikit/src/common/widget/global.dart'; import '../common/metrics/key_metrics.dart'; -import 'call_main_widget.dart'; -import 'component/incoming_banner/incoming_banner_widget.dart'; -import 'component/inviter/invite_user_widget.dart'; +import '../view/call_main_widget.dart'; +import '../view/callview/core/common/constants.dart'; +import '../view/component/incoming_banner/incoming_banner_widget.dart'; +import '../view/component/inviter/invite_user_widget.dart'; +import 'call_ended_hint_resolver.dart'; enum CallPageType { none, @@ -50,7 +53,17 @@ class InviteUserCallbacks { }); } -class CallPageManager { +class EndedHintState { + final CallEndReason reason; + final String text; + + const EndedHintState({ + required this.reason, + required this.text, + }); +} + +class CallPageRouter { final GlobalKey _callNavigatorKey = GlobalKey(); final NavigatorState? Function() _navigatorGetter; final AndroidPipFeature pipController = AndroidPipFeature(); @@ -63,11 +76,17 @@ class CallPageManager { OverlayEntry? _callOverlayEntry; bool _hasManuallyShownCalling = false; bool _shouldAnimateRect = false; + bool _disposed = false; + + Completer? _overlayDisposeCompleter; + + final ValueNotifier endedHintState = ValueNotifier(null); + Timer? _endedHintTimer; + bool get isEndedHintActive => endedHintState.value != null; Size _originScreenSize = Size.zero; Size get originScreenSize => _originScreenSize; void setOriginScreenSize(Size size) { - // Only grow, never shrink — prevents small-window constraints from polluting the value. _originScreenSize = Size( max(size.width, _originScreenSize.width), max(size.height, _originScreenSize.height), @@ -83,7 +102,7 @@ class CallPageManager { NavigatorState? get callNavigator => _callNavigatorKey.currentState; - CallPageManager({ + CallPageRouter({ required NavigatorState? Function() navigatorGetter, }) : _navigatorGetter = navigatorGetter; @@ -120,6 +139,7 @@ class CallPageManager { void closeIncomingBanner() => _hidePage(CallPageType.banner); void closeAllPage() { + _currentPageType = CallPageType.none; pipController.onEnterPip = null; pipController.onLeavePip = null; @@ -128,16 +148,65 @@ class CallPageManager { } pipController.disable(); + _clearEndedHint(); _removeCallOverlay(); _hasManuallyShownCalling = false; _originScreenSize = Size.zero; } void dispose() { + _disposed = true; + _clearEndedHint(); + if (_overlayDisposeCompleter != null && !_overlayDisposeCompleter!.isCompleted) { + _overlayDisposeCompleter!.complete(); + } + _overlayDisposeCompleter = null; _removeCallOverlay(); _hasManuallyShownCalling = false; } + bool enterEndedHint(CallEndReason reason, String operatorUserId) { + final activeCall = CallStore.shared.state.activeCall.value; + final selfId = CallStore.shared.state.selfInfo.value.id; + + final isGroupOrMulti = activeCall.inviteeIds.length > 1 || + activeCall.chatGroupId.isNotEmpty; + final isSelfInitiated = operatorUserId == selfId && + (reason == CallEndReason.hangup || + reason == CallEndReason.reject || + reason == CallEndReason.canceled); + if (isGroupOrMulti || isSelfInitiated) { + return false; + } + + if (_currentPageType != CallPageType.calling && + _currentPageType != CallPageType.banner) { + return false; + } + + final appContext = Global.appContext(); + final localizations = AtomicLocalizations.of(appContext); + final hintText = CallEndedHintResolver.resolveText(reason, localizations); + if (hintText == null || hintText.isEmpty) { + return false; + } + + _endedHintTimer?.cancel(); + endedHintState.value = EndedHintState(reason: reason, text: hintText); + _endedHintTimer = Timer(Constants.callEndedHintDuration, () { + closeAllPage(); + }); + return true; + } + + void _clearEndedHint() { + _endedHintTimer?.cancel(); + _endedHintTimer = null; + if (endedHintState.value != null) { + endedHintState.value = null; + } + } + void _showPage(CallPageType pageType, {bool isManualSwitch = false}) { KeyMetrics.instance.countUV(EventId.wakeup); @@ -161,6 +230,17 @@ class CallPageManager { SystemChannels.textInput.invokeMethod('TextInput.hide'); if (_callOverlayEntry == null) { + if (_overlayDisposeCompleter != null && !_overlayDisposeCompleter!.isCompleted) { + final waiting = _overlayDisposeCompleter!; + Future.any([waiting.future, Future.delayed(const Duration(milliseconds: 300))]).then((_) { + if (_disposed || waiting != _overlayDisposeCompleter) return; + _overlayDisposeCompleter = null; + _showPage(pageType, isManualSwitch: isManualSwitch); + }); + return; + } + _overlayDisposeCompleter = null; + _currentPageType = pageType; _callOverlayEntry = OverlayEntry( builder: (context) => _buildCallNavigator(), @@ -174,23 +254,16 @@ class CallPageManager { final isFromSmallToFull = _isSmallWindow(previousPageType) && !_isSmallWindow(targetPageType); if (isFromSmallToFull) { - // Small window → Fullscreen: - // Immediately update page type and rebuild overlay, then navigate route. _shouldAnimateRect = true; _currentPageType = targetPageType; _callOverlayEntry?.markNeedsBuild(); WidgetsBinding.instance.addPostFrameCallback((_) { _navigateToPage(targetPageType); }); - // Reset after AnimatedPositioned finishes (250ms), so subsequent - // rebuilds (e.g. drag updates) use Duration.zero instead of animating. Future.delayed(const Duration(milliseconds: 250), () { _shouldAnimateRect = false; }); } else { - // Fullscreen → Small window, or same-size switch: - // Animate when at least one side is not a small window; - // skip animation for small↔small transitions (e.g. floating → banner). _shouldAnimateRect = !_isSmallWindow(previousPageType) || !_isSmallWindow(targetPageType); _currentPageType = targetPageType; _callOverlayEntry?.markNeedsBuild(); @@ -223,11 +296,20 @@ class CallPageManager { } void _removeCallOverlay() { - _callOverlayEntry?.remove(); - _callOverlayEntry = null; + if (_callOverlayEntry != null) { + _overlayDisposeCompleter = Completer(); + _callOverlayEntry!.remove(); + _callOverlayEntry = null; + } _currentPageType = CallPageType.none; } + void _onOverlayDisposed() { + if (_overlayDisposeCompleter != null && !_overlayDisposeCompleter!.isCompleted) { + _overlayDisposeCompleter!.complete(); + } + } + void _navigateToPage(CallPageType pageType) { final navigator = _callNavigatorKey.currentState; if (navigator == null) return; @@ -268,6 +350,7 @@ class CallPageManager { rectGetter: _getPageRect, borderRadiusGetter: _getPageBorderRadius, isAnimatingGetter: () => _shouldAnimateRect, + onDisposed: _onOverlayDisposed, child: Navigator( key: _callNavigatorKey, initialRoute: _getRouteName(_currentPageType), @@ -434,102 +517,115 @@ class CallPageManager { ); } - void handleNoPermissionAndEndCall(bool isCalled) async { + void handleNoPermissionAndEndCall() async { final overlay = _navigatorGetter()?.overlay; - if (overlay == null) { - if (isCalled) { - CallStore.shared.hangup(); - } - return; + bool? goSettings; + if (overlay != null) { + goSettings = await _showPermissionSettingsOverlay(overlay); + } + + final selfStatus = CallStore.shared.state.selfInfo.value.status; + if (selfStatus == CallParticipantStatus.waiting) { + CallStore.shared.reject(); + } + + if (goSettings == true) { + await Permission.openAppSettings(); } + } - final completer = Completer(); + Future _showPermissionSettingsOverlay(OverlayState overlay) { + final completer = Completer(); OverlayEntry? overlayEntry; overlayEntry = OverlayEntry( builder: (context) { final l10n = AtomicLocalizations.of(context); return Material( - color: Colors.black54, - child: Center( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 40), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - l10n.callNeedToAccessMicrophoneAndCameraPermissions, - style: const TextStyle(fontSize: 16), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - overlayEntry?.remove(); - completer.complete(false); - }, - child: Text(l10n.cancel), - ), - const SizedBox(width: 10), - TextButton( - onPressed: () async { - overlayEntry?.remove(); - completer.complete(true); - }, - child: Text(l10n.callGoToSettings), - ), - ], - ), - ], + color: Colors.black54, + child: Center( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 40), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.callNeedToAccessMicrophoneAndCameraPermissions, + style: const TextStyle(fontSize: 16), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + overlayEntry?.remove(); + if (!completer.isCompleted) completer.complete(false); + }, + child: Text(l10n.cancel), + ), + const SizedBox(width: 10), + TextButton( + onPressed: () { + overlayEntry?.remove(); + if (!completer.isCompleted) completer.complete(true); + }, + child: Text(l10n.callGoToSettings), + ), + ], + ), + ], + ), ), ), - ), - ); + ); }, ); overlay.insert(overlayEntry); - - final goSettings = await completer.future; - - if (goSettings == true) { - await Permission.openAppSettings(); - } - - if (isCalled) { - CallStore.shared.reject(); - } + return completer.future; } } -class _CallOverlayLayout extends StatelessWidget { +class _CallOverlayLayout extends StatefulWidget { final Rect Function(Size screenSize, EdgeInsets viewPadding) rectGetter; final BorderRadius Function() borderRadiusGetter; final bool Function() isAnimatingGetter; + final VoidCallback? onDisposed; final Widget child; const _CallOverlayLayout({ required this.rectGetter, required this.borderRadiusGetter, required this.isAnimatingGetter, + this.onDisposed, required this.child, }); + @override + State<_CallOverlayLayout> createState() => _CallOverlayLayoutState(); +} + +class _CallOverlayLayoutState extends State<_CallOverlayLayout> { + @override + void dispose() { + widget.onDisposed?.call(); + super.dispose(); + } + @override Widget build(BuildContext context) { final mq = MediaQuery.of(context); final screenSize = mq.size; - final rect = rectGetter(screenSize, mq.viewPadding); - final borderRadius = borderRadiusGetter(); - final shouldAnimate = isAnimatingGetter(); + final rect = widget.rectGetter(screenSize, mq.viewPadding); + final borderRadius = widget.borderRadiusGetter(); + final shouldAnimate = widget.isAnimatingGetter(); final duration = shouldAnimate ? const Duration(milliseconds: 250) : Duration.zero; @@ -545,7 +641,7 @@ class _CallOverlayLayout extends StatelessWidget { height: rect.height, child: ClipRRect( borderRadius: borderRadius, - child: child, + child: widget.child, ), ), ], diff --git a/call/lib/src/tui_call_kit_impl.dart b/call/lib/src/tui_call_kit_impl.dart index e1772079..5fbfad8f 100644 --- a/call/lib/src/tui_call_kit_impl.dart +++ b/call/lib/src/tui_call_kit_impl.dart @@ -2,16 +2,16 @@ import 'dart:async'; import 'dart:io'; import 'package:tencent_calls_uikit/src/common/metrics/key_metrics.dart'; -import 'package:tuikit_atomic_x/permission/permission.dart'; import 'package:tuikit_atomic_x/atomicx.dart'; import 'package:tencent_calls_uikit/src/common/widget/global.dart'; import 'package:tencent_calls_uikit/src/common/utils/app_lifecycle.dart'; import 'package:tencent_calls_uikit/src/common/utils/foreground_service.dart'; import 'package:tencent_calls_uikit/src/feature/calling_bell_feature.dart'; +import 'package:tencent_calls_uikit/src/manager/call_manager.dart'; import 'package:tencent_calls_uikit/src/state/global_state.dart'; import 'package:tencent_calls_uikit/src/tui_call_kit.dart'; import 'package:tencent_cloud_uikit_core/tencent_cloud_uikit_core.dart'; -import 'package:tencent_calls_uikit/src/view/call_page_manager.dart'; +import 'package:tencent_calls_uikit/src/manager/call_page_router.dart'; import 'bridge/bootloader/bootloader.dart'; import 'package:rtc_room_engine/rtc_room_engine.dart' hide CallEndReason; import 'bridge/voip/fcm_data_sync_handler.dart'; @@ -22,7 +22,7 @@ import 'feature/ios_pip_feature.dart'; class TUICallKitImpl implements TUICallKit { static final TUICallKitImpl _instance = TUICallKitImpl(); static TUICallKitImpl get instance => _instance; - late final CallPageManager pageManager; + late final CallPageRouter pageRouter; IosPipFeature? pictureInPictureFeature; late final voIPDataSyncHandler; late final fcmDataSyncHandler; @@ -30,53 +30,35 @@ class TUICallKitImpl implements TUICallKit { bool isNotificationPreparing = false; late CallEventListener callEventListener = CallEventListener( onCallStarted: (callId, mediaType) async { - await _prepareCallDevices(callId, mediaType, isCalled: false); + await CallManager.instance.prepareCallDevices( + callId: callId, + mediaType: mediaType, + isCalled: false, + ); if (GlobalState.instance.enableAITranscriber) { aiTranscriberConfigManager.start(); } }, onCallReceived: (String callId, CallMediaType mediaType, String userData) async { KeyMetrics.instance.countUV(EventId.received); - await _prepareCallDevices(callId, mediaType, isCalled: true); + if (mediaType == CallMediaType.video) { + await CallManager.instance.openLocalCameraIfPermitted(); + } }, onCallEnded: (callId, mediaType, reason, userId) { - DeviceStore.shared.closeLocalMicrophone(); + CallManager.instance.closeLocalMicrophone(); aiTranscriberConfigManager.reset(preserveSettings: false); - _closePage(); - _stopRing(); ForegroundService.stop(); - if (CallStore.shared.state.activeCall.value.inviteeIds.length > 1 - || CallStore.shared.state.activeCall.value.chatGroupId.isNotEmpty - || CallStore.shared.state.selfInfo.value.id == userId) { + if (reason == CallEndReason.lineBusy) { + final l10n = AtomicLocalizations.of(Global.appContext()); + TUIToast.show(content: l10n.callOtherPartyBusy); + _closePage(); return; } - final l10n = AtomicLocalizations.of(Global.appContext()); - switch (reason) { - case CallEndReason.hangup: - TUIToast.show(content: l10n.callOtherPartyHungUp); - break; - case CallEndReason.unknown: - break; - case CallEndReason.reject: - TUIToast.show(content: l10n.callOtherPartyDeclinedCallRequest); - break; - case CallEndReason.noResponse: - TUIToast.show(content: l10n.callOtherPartyNoResponse); - break; - case CallEndReason.offline: - break; - case CallEndReason.lineBusy: - TUIToast.show(content: l10n.callOtherPartyBusy); - break; - case CallEndReason.canceled: - break; - case CallEndReason.otherDeviceAccepted: - break; - case CallEndReason.otherDeviceReject: - break; - case CallEndReason.endByServer: - break; - } + final entered = pageRouter.enterEndedHint(reason, userId); + if (!entered) { + _closePage(); + } } ); @@ -84,7 +66,8 @@ class TUICallKitImpl implements TUICallKit { CallStore.shared; voIPDataSyncHandler = VoIPDataSyncHandler(); fcmDataSyncHandler = FcmDataSyncHandler(); - pageManager = CallPageManager(navigatorGetter: () => Bootloader.instance.navigator); + pageRouter = CallPageRouter(navigatorGetter: () => Bootloader.instance.navigator); + CallManager.instance.bindPageManager(pageRouter); _subscribeState(); } @@ -154,33 +137,19 @@ class TUICallKitImpl implements TUICallKit { .loginState .loginUserInfo! .customInfo); - CompletionHandler handler = await LoginStore.shared.setSelfInfo(userInfo: userInfo); - return handler; + return CallManager.instance.setSelfInfo(userInfo); } @override Future calls(List userIdList, callMediaType, [CallParams? params]) async { - bool isGroupCall = (userIdList.length > 1) || (params?.chatGroupId.isNotEmpty ?? false); - final hasPermission = await _getAndroidAudioAndVideoPermission(callMediaType, isGroupCall); - if (!hasPermission) { - pageManager.handleNoPermissionAndEndCall(false); - handleErrorCode(-1101); - CompletionHandler handler = CompletionHandler(); - handler.errorCode = -1101; - handler.errorMessage = "Failed to obtain audio and video permissions"; - return handler; - } - CompletionHandler handler = await CallStore.shared.calls( - userIdList, callMediaType, params); - + final handler = await CallManager.instance.calls(userIdList, callMediaType, params); handleErrorCode(handler.errorCode); - return handler; } @override Future join(String callId) async { - CompletionHandler handler = await CallStore.shared.join(callId); + final handler = await CallManager.instance.join(callId); handleErrorCode(handler.errorCode); } @@ -225,6 +194,7 @@ class TUICallKitImpl implements TUICallKit { LoginStore.shared.login( sdkAppID: sdkAppID, userID: userId, userSig: userSig); CallStore.shared.addListener(callEventListener); + CallingBellFeature.instance.init(); if (Platform.isIOS) { pictureInPictureFeature = IosPipFeature(); } @@ -233,6 +203,7 @@ class TUICallKitImpl implements TUICallKit { void handleLogoutSuccess() async { TUICallEngine.instance.unInit(); CallStore.shared.removeListener(callEventListener); + CallingBellFeature.instance.dispose(); LoginStore.shared.logout(); if (Platform.isIOS && pictureInPictureFeature != null) { pictureInPictureFeature!.dispose(); @@ -240,34 +211,6 @@ class TUICallKitImpl implements TUICallKit { } } - Future _prepareCallDevices( - String callId, - CallMediaType mediaType, { - required bool isCalled, - }) async { - final activeCall = CallStore.shared.state.activeCall.value; - final isGroupCall = - activeCall.inviteeIds.length > 1 || activeCall.chatGroupId.isNotEmpty; - final hasPermission = - await _getAndroidAudioAndVideoPermission(mediaType, isGroupCall); - if (CallStore.shared.state.activeCall.value.callId != callId) { - return; - } - if (!hasPermission) { - pageManager.handleNoPermissionAndEndCall(isCalled); - return; - } - DeviceStore.shared.openLocalMicrophone(); - DeviceStore.shared.setAudioRoute( - mediaType == CallMediaType.audio - ? AudioRoute.earpiece - : AudioRoute.speakerphone, - ); - if (mediaType == CallMediaType.video) { - DeviceStore.shared.openLocalCamera(true); - } - } - void _subscribeState() { CallStore.shared.state.selfInfo.addListener(() async { final activeCall = CallStore.shared.state.activeCall.value; @@ -277,56 +220,23 @@ class TUICallKitImpl implements TUICallKit { final callStatus = CallStore.shared.state.selfInfo.value.status; - if (callStatus == CallParticipantStatus.waiting) { - _showPage(); - _startRing(); - } else if (callStatus == CallParticipantStatus.accept) { + if (callStatus == CallParticipantStatus.waiting || + callStatus == CallParticipantStatus.accept) { + if (pageRouter.isEndedHintActive) { + pageRouter.closeAllPage(); + } _showPage(); - _stopRing(); } else if (callStatus == CallParticipantStatus.none) { + if (pageRouter.isEndedHintActive) { + return; + } _closePage(); - _stopRing(); } }); } - Future _getAndroidAudioAndVideoPermission(CallMediaType mediaType, bool isGroupCall) async { - if (mediaType == CallMediaType.video || isGroupCall) { - final cameraStatus = await Permission.check(PermissionType.camera); - final microphoneStatus = await Permission.check(PermissionType.microphone); - - if (cameraStatus == PermissionStatus.granted && microphoneStatus == PermissionStatus.granted) { - return true; - } - - final status = await Permission.request([PermissionType.camera, PermissionType.microphone]); - if (status.containsValue(PermissionStatus.denied) || - status.containsValue(PermissionStatus.permanentlyDenied)) { - return false; - } - return true; - } - - if (mediaType == CallMediaType.audio) { - final microphoneStatus = await Permission.check(PermissionType.microphone); - - if (microphoneStatus == PermissionStatus.granted) { - return true; - } - - final status = await Permission.request([PermissionType.microphone]); - final micStatus = status[PermissionType.microphone]; - if (micStatus == PermissionStatus.denied || - micStatus == PermissionStatus.permanentlyDenied) { - return false; - } - return true; - } - return true; - } - void _showPage() async { - if (pageManager.getCurrentPageRoute() != CallPageType.none) return; + if (pageRouter.getCurrentPageRoute() != CallPageType.none) return; final activeCall = CallStore.shared.state.activeCall.value; if (AppLifecycle.instance.isBackground && activeCall.inviterId.isNotEmpty) { @@ -349,26 +259,18 @@ class TUICallKitImpl implements TUICallKit { CallStore.shared.state.selfInfo.value.id != CallStore.shared.state.activeCall.value.inviterId && CallStore.shared.state.selfInfo.value.status == CallParticipantStatus.waiting) { - pageManager.showIncomingBanner(); + pageRouter.showIncomingBanner(); } else { - pageManager.showCallingPage(); + pageRouter.showCallingPage(); } } void _closePage() async { - if (pageManager.getCurrentPageRoute() == CallPageType.none) return; + if (pageRouter.getCurrentPageRoute() == CallPageType.none) return; isNotificationPreparing = false; fcmDataSyncHandler.closeNotificationView(); - pageManager.closeAllPage(); - } - - void _startRing() async { - await CallingBellFeature.instance.startRing(); - } - - void _stopRing() async { - await CallingBellFeature.instance.stopRing(); + pageRouter.closeAllPage(); } void handleErrorCode(int errorCode) { @@ -377,4 +279,4 @@ class TUICallKitImpl implements TUICallKit { TUIToast.show(content: errorMessage); } } -} \ No newline at end of file +} diff --git a/call/lib/src/view/call_main_widget.dart b/call/lib/src/view/call_main_widget.dart index 6c928e63..24d7386c 100644 --- a/call/lib/src/view/call_main_widget.dart +++ b/call/lib/src/view/call_main_widget.dart @@ -1,13 +1,13 @@ import 'dart:math'; -import 'package:tuikit_atomic_x/atomicx.dart'; import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:tencent_calls_uikit/src/bridge/voip/fcm_data_sync_handler.dart'; import 'package:tencent_calls_uikit/src/common/utils/foreground_service.dart'; import 'package:tencent_calls_uikit/src/state/global_state.dart'; -import 'package:tencent_calls_uikit/src/view/call_page_manager.dart'; +import 'package:tencent_calls_uikit/src/manager/call_page_router.dart'; +import 'package:tencent_calls_uikit/src/view/callview/call_view.dart'; class CallMainWidget extends StatefulWidget { diff --git a/call/lib/src/view/callview/call_view.dart b/call/lib/src/view/callview/call_view.dart new file mode 100644 index 00000000..9de5e4df --- /dev/null +++ b/call/lib/src/view/callview/call_view.dart @@ -0,0 +1,78 @@ +import 'package:atomic_x_core/atomicxcore.dart'; +import 'package:flutter/material.dart'; +import 'package:tencent_calls_uikit/src/view/callview/public/float/call_float_widget.dart'; +import 'package:tencent_calls_uikit/src/view/callview/public/multi/call_grid_widget.dart'; +import 'package:tencent_calls_uikit/src/view/callview/public/pip/call_pip_widget.dart'; + +class CallView extends StatefulWidget { + final bool isPipMode; + final bool enableAITranscriber; + + const CallView({ + super.key, + this.enableAITranscriber = false, + this.isPipMode = false, + }); + + @override + State createState() => _CallViewState(); +} + +class _CallViewState extends State { + late final CallCoreController controller; + CallInfo _activeCall = CallStore.shared.state.activeCall.value; + + @override + void initState() { + controller = CallCoreController.create(); + super.initState(); + CallStore.shared.state.activeCall.addListener(_onActiveCallChanged); + } + + @override + void dispose() { + CallStore.shared.state.activeCall.removeListener(_onActiveCallChanged); + controller.dispose(); + super.dispose(); + } + + void _onActiveCallChanged() { + final newValue = CallStore.shared.state.activeCall.value; + if (newValue.callId.isEmpty) return; + setState(() => _activeCall = newValue); + } + + @override + Widget build(BuildContext context) { + return Material( + child: SizedBox( + width: double.infinity, + height: double.infinity, + child: Builder( + builder: (context) { + if (widget.isPipMode) { + controller.setLayoutTemplate(CallLayoutTemplate.pip); + return CallPipWidget( + controller: controller, + ); + } + + if (_activeCall.chatGroupId.isNotEmpty || _activeCall.inviteeIds.length > 1) { + controller.setLayoutTemplate(CallLayoutTemplate.grid); + return CallGridWidget( + controller: controller, + enableAITranscriber: widget.enableAITranscriber, + ); + } + + controller.setLayoutTemplate(CallLayoutTemplate.float); + return CallFloatWidget( + controller: controller, + enableAITranscriber: widget.enableAITranscriber, + ); + }, + ), + ), + ); + } +} \ No newline at end of file diff --git a/atomic-x/lib/call/common/call_colors.dart b/call/lib/src/view/callview/core/common/call_colors.dart similarity index 100% rename from atomic-x/lib/call/common/call_colors.dart rename to call/lib/src/view/callview/core/common/call_colors.dart diff --git a/atomic-x/lib/call/common/constants.dart b/call/lib/src/view/callview/core/common/constants.dart similarity index 95% rename from atomic-x/lib/call/common/constants.dart rename to call/lib/src/view/callview/core/common/constants.dart index 7099ccfe..36367308 100644 --- a/atomic-x/lib/call/common/constants.dart +++ b/call/lib/src/view/callview/core/common/constants.dart @@ -11,6 +11,8 @@ class Constants { static const int blurLevelHigh = 3; static const int blurLevelClose = 0; + static const Duration callEndedHintDuration = Duration(seconds: 1); + static final Image defaultAvatarImage = Image.network(defaultAvatar, fit: BoxFit.cover,); static final Image loading = Image.asset('call_assets/loading.gif', package: 'tuikit_atomic_x'); static final Map volumeIcons = { diff --git a/atomic-x/lib/call/common/utils/logger.dart b/call/lib/src/view/callview/core/common/utils/logger.dart similarity index 100% rename from atomic-x/lib/call/common/utils/logger.dart rename to call/lib/src/view/callview/core/common/utils/logger.dart diff --git a/atomic-x/lib/call/common/utils/utils.dart b/call/lib/src/view/callview/core/common/utils/utils.dart similarity index 100% rename from atomic-x/lib/call/common/utils/utils.dart rename to call/lib/src/view/callview/core/common/utils/utils.dart diff --git a/atomic-x/lib/call/common/widget/controls_button.dart b/call/lib/src/view/callview/core/common/widget/controls_button.dart similarity index 95% rename from atomic-x/lib/call/common/widget/controls_button.dart rename to call/lib/src/view/callview/core/common/widget/controls_button.dart index d5014498..25716eb2 100644 --- a/atomic-x/lib/call/common/widget/controls_button.dart +++ b/call/lib/src/view/callview/core/common/widget/controls_button.dart @@ -57,9 +57,7 @@ class ControlsButton extends StatelessWidget { } return GestureDetector( behavior: HitTestBehavior.deferToChild, - onTap: () { - onTap?.call(); - }, + onTap: onTap, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -74,7 +72,7 @@ class ControlsButton extends StatelessWidget { alignment: Alignment.center, child: Text( tips, - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, style: TextStyle(fontSize: 12, color: textColor), ), ), diff --git a/call/lib/src/view/callview/core/common/widget/slide_text_switcher.dart b/call/lib/src/view/callview/core/common/widget/slide_text_switcher.dart new file mode 100644 index 00000000..f3d936e3 --- /dev/null +++ b/call/lib/src/view/callview/core/common/widget/slide_text_switcher.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +class SlideTextSwitcher extends StatelessWidget { + final String text; + + final TextStyle? style; + + final Duration duration; + + final Curve curve; + + final TextAlign? textAlign; + + final int? maxLines; + + final TextOverflow? overflow; + + const SlideTextSwitcher({ + super.key, + required this.text, + this.style, + this.duration = const Duration(milliseconds: 300), + this.curve = Curves.easeInOut, + this.textAlign, + this.maxLines, + this.overflow, + }); + + @override + Widget build(BuildContext context) { + return AnimatedSwitcher( + duration: duration, + transitionBuilder: (child, animation) { + final isEntering = child.key == ValueKey(text); + final tween = isEntering + ? Tween(begin: const Offset(0, 1.0), end: Offset.zero) + : Tween(begin: const Offset(0, -1.0), end: Offset.zero); + return ClipRect( + child: SlideTransition( + position: tween.animate( + CurvedAnimation(parent: animation, curve: curve), + ), + child: child, + ), + ); + }, + layoutBuilder: (currentChild, previousChildren) { + return Stack( + alignment: Alignment.center, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, + child: Text( + text, + key: ValueKey(text), + style: style, + textAlign: textAlign, + maxLines: maxLines, + overflow: overflow, + ), + ); + } +} diff --git a/atomic-x/lib/call/component/controls/multi_call_controls_widget.dart b/call/lib/src/view/callview/public/controls/multi_call_controls_widget.dart similarity index 87% rename from atomic-x/lib/call/component/controls/multi_call_controls_widget.dart rename to call/lib/src/view/callview/public/controls/multi_call_controls_widget.dart index 8a2d18b2..432434b5 100644 --- a/atomic-x/lib/call/component/controls/multi_call_controls_widget.dart +++ b/call/lib/src/view/callview/public/controls/multi_call_controls_widget.dart @@ -1,9 +1,10 @@ import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/material.dart'; import 'package:tuikit_atomic_x/base_component/localizations/atomic_localizations.dart'; -import 'package:tuikit_atomic_x/call/common/widget/controls_button.dart'; +import 'package:tencent_calls_uikit/src/manager/call_manager.dart'; +import 'package:tencent_calls_uikit/src/view/callview/core/common/widget/controls_button.dart'; -import '../../common/call_colors.dart'; +import '../../core/common/call_colors.dart'; class MultiCallControlsWidget extends StatefulWidget { final ValueChanged? onHeightChanged; @@ -29,24 +30,35 @@ class _MultiCallControlsWidgetState extends State { bool isFunctionExpand = false; late AtomicLocalizations _l10n; + CallParticipantInfo _selfInfo = CallStore.shared.state.selfInfo.value; + @override void initState() { super.initState(); + CallStore.shared.state.selfInfo.addListener(_onSelfInfoChanged); + } + + @override + void dispose() { + CallStore.shared.state.selfInfo.removeListener(_onSelfInfoChanged); + super.dispose(); + } + + void _onSelfInfoChanged() { + final newValue = CallStore.shared.state.selfInfo.value; + if (newValue.status == CallParticipantStatus.none) return; + setState(() => _selfInfo = newValue); } @override Widget build(BuildContext context) { _l10n = AtomicLocalizations.of(context); - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, selfInfo, child) { - if (selfInfo.status == CallParticipantStatus.waiting && - selfInfo.id != CallStore.shared.state.activeCall.value.inviterId) { - return _buildWaitingFunctionView(); - } else { - return _buildAcceptedFunctionView(context); - } - }); + if (_selfInfo.status == CallParticipantStatus.waiting && + _selfInfo.id != CallStore.shared.state.activeCall.value.inviterId) { + return _buildWaitingFunctionView(); + } else { + return _buildAcceptedFunctionView(context); + } } _buildWaitingFunctionView() { @@ -174,7 +186,7 @@ class _MultiCallControlsWidgetState extends State { imgHeight: 28, imgOffsetX: -16, onTap: () { - DeviceStore.shared.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); + CallManager.instance.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); }, ); }); @@ -198,7 +210,7 @@ class _MultiCallControlsWidgetState extends State { textColor: CallColors.colorG7, imgHeight: 60, onTap: () { - CallStore.shared.reject(); + CallManager.instance.reject(); }, ); } @@ -210,7 +222,7 @@ class _MultiCallControlsWidgetState extends State { textColor: CallColors.colorG7, imgHeight: 60, onTap: () { - CallStore.shared.accept(); + CallManager.instance.accept(); }, ); } @@ -228,9 +240,9 @@ class _MultiCallControlsWidgetState extends State { imgHeight: isFunctionExpand ? bigBtnHeight : smallBtnHeight, onTap: () { if (value == DeviceStatus.on) { - DeviceStore.shared.closeLocalMicrophone(); + CallManager.instance.closeLocalMicrophone(); } else { - DeviceStore.shared.openLocalMicrophone(); + CallManager.instance.openLocalMicrophone(); } }, useAnimation: true, @@ -252,9 +264,9 @@ class _MultiCallControlsWidgetState extends State { imgHeight: isFunctionExpand ? bigBtnHeight : smallBtnHeight, onTap: () { if (value == AudioRoute.speakerphone) { - DeviceStore.shared.setAudioRoute(AudioRoute.earpiece); + CallManager.instance.setAudioRoute(AudioRoute.earpiece); } else { - DeviceStore.shared.setAudioRoute(AudioRoute.speakerphone); + CallManager.instance.setAudioRoute(AudioRoute.speakerphone); } }, useAnimation: true, @@ -275,9 +287,9 @@ class _MultiCallControlsWidgetState extends State { imgHeight: isFunctionExpand ? bigBtnHeight : smallBtnHeight, onTap: () { if (value == DeviceStatus.on) { - DeviceStore.shared.closeLocalCamera(); + CallManager.instance.closeLocalCamera(); } else { - DeviceStore.shared.openLocalCamera(DeviceStore.shared.state.isFrontCamera.value); + CallManager.instance.openLocalCamera(DeviceStore.shared.state.isFrontCamera.value); } }, useAnimation: true, @@ -292,7 +304,7 @@ class _MultiCallControlsWidgetState extends State { textColor: CallColors.colorG7, imgHeight: isFunctionExpand ? bigBtnHeight : smallBtnHeight, onTap: () { - CallStore.shared.hangup(); + CallManager.instance.hangup(); }, useAnimation: true, duration: Duration(milliseconds: duration), diff --git a/atomic-x/lib/call/component/controls/single_call_controls_widget.dart b/call/lib/src/view/callview/public/controls/single_call_controls_widget.dart similarity index 75% rename from atomic-x/lib/call/component/controls/single_call_controls_widget.dart rename to call/lib/src/view/callview/public/controls/single_call_controls_widget.dart index d8583c3f..fa4011a4 100644 --- a/atomic-x/lib/call/component/controls/single_call_controls_widget.dart +++ b/call/lib/src/view/callview/public/controls/single_call_controls_widget.dart @@ -1,43 +1,63 @@ -import 'package:tuikit_atomic_x/call/common/widget/controls_button.dart'; +import 'package:tencent_calls_uikit/src/view/callview/core/common/widget/controls_button.dart'; import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/material.dart'; +import 'package:tencent_calls_uikit/src/manager/call_manager.dart'; import 'package:tuikit_atomic_x/base_component/localizations/atomic_localizations.dart'; -import '../../common/call_colors.dart'; +import '../../core/common/call_colors.dart'; typedef _ViewBuilder = Widget Function(); -// ignore: must_be_immutable -class SingleCallControlsWidget extends StatelessWidget { +class SingleCallControlsWidget extends StatefulWidget { + const SingleCallControlsWidget({super.key}); + + @override + State createState() => _SingleCallControlsWidgetState(); +} + +class _SingleCallControlsWidgetState extends State { late final Map _viewStrategies; late AtomicLocalizations _l10n; - SingleCallControlsWidget({ - super.key, - }) { + CallInfo _activeCall = CallStore.shared.state.activeCall.value; + CallParticipantInfo _selfInfo = CallStore.shared.state.selfInfo.value; + + @override + void initState() { + super.initState(); _viewStrategies = _getViewStrategies(); + CallStore.shared.state.activeCall.addListener(_onActiveCallChanged); + CallStore.shared.state.selfInfo.addListener(_onSelfInfoChanged); + } + + @override + void dispose() { + CallStore.shared.state.activeCall.removeListener(_onActiveCallChanged); + CallStore.shared.state.selfInfo.removeListener(_onSelfInfoChanged); + super.dispose(); + } + + void _onActiveCallChanged() { + final newValue = CallStore.shared.state.activeCall.value; + if (newValue.mediaType == null) return; + setState(() => _activeCall = newValue); + } + + void _onSelfInfoChanged() { + final newValue = CallStore.shared.state.selfInfo.value; + if (newValue.status == CallParticipantStatus.none) return; + setState(() => _selfInfo = newValue); } @override Widget build(BuildContext context) { _l10n = AtomicLocalizations.of(context); - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.activeCall, - builder: (context, activeCall, child) { - if (activeCall.mediaType == null) { - return Container(); - } - final type = activeCall.mediaType!; - return ValueListenableBuilder( - valueListenable: CallStore.shared.state.selfInfo, - builder: (context, selfInfo, child) { - if (selfInfo.id == activeCall.inviterId) { - return _selectViewStrategy(type, selfInfo.status, "caller"); - } - return _selectViewStrategy(type, selfInfo.status, "called"); - }); - }, - ); + if (_activeCall.mediaType == null) { + return Container(); + } + final type = _activeCall.mediaType!; + final role = _selfInfo.id == _activeCall.inviterId ? "caller" : "called"; + return _selectViewStrategy(type, _selfInfo.status, role); } Map _getViewStrategies() { @@ -171,7 +191,7 @@ class SingleCallControlsWidget extends StatelessWidget { textColor: _getTextColor(), imgHeight: 60, onTap: () { - DeviceStore.shared.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); + CallManager.instance.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); }, ); } @@ -183,7 +203,7 @@ class SingleCallControlsWidget extends StatelessWidget { textColor: CallColors.colorG7, imgHeight: 60, onTap: () { - CallStore.shared.accept(); + CallManager.instance.accept(); }, ); } @@ -195,7 +215,7 @@ class SingleCallControlsWidget extends StatelessWidget { textColor: CallColors.colorG7, imgHeight: 60, onTap: () { - CallStore.shared.hangup(); + CallManager.instance.hangup(); }, ); } @@ -207,7 +227,7 @@ class SingleCallControlsWidget extends StatelessWidget { textColor: CallColors.colorG7, imgHeight: 60, onTap: () { - CallStore.shared.reject(); + CallManager.instance.reject(); }, ); } @@ -223,9 +243,9 @@ class SingleCallControlsWidget extends StatelessWidget { imgHeight: 60, onTap: () { if (value == DeviceStatus.on) { - DeviceStore.shared.closeLocalMicrophone(); + CallManager.instance.closeLocalMicrophone(); } else { - DeviceStore.shared.openLocalMicrophone(); + CallManager.instance.openLocalMicrophone(); } }, ); @@ -243,9 +263,9 @@ class SingleCallControlsWidget extends StatelessWidget { imgHeight: 60, onTap: () { if (value == AudioRoute.speakerphone) { - DeviceStore.shared.setAudioRoute(AudioRoute.earpiece); + CallManager.instance.setAudioRoute(AudioRoute.earpiece); } else { - DeviceStore.shared.setAudioRoute(AudioRoute.speakerphone); + CallManager.instance.setAudioRoute(AudioRoute.speakerphone); } }, ); @@ -263,9 +283,9 @@ class SingleCallControlsWidget extends StatelessWidget { imgHeight: 60, onTap: () { if (value == DeviceStatus.on) { - DeviceStore.shared.closeLocalCamera(); + CallManager.instance.closeLocalCamera(); } else { - DeviceStore.shared.openLocalCamera(DeviceStore.shared.state.isFrontCamera.value); + CallManager.instance.openLocalCamera(DeviceStore.shared.state.isFrontCamera.value); } }, ); @@ -283,7 +303,7 @@ class SingleCallControlsWidget extends StatelessWidget { imgHeight: 28, imgOffsetX: -16, onTap: () { - DeviceStore.shared.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); + CallManager.instance.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); }, ); }); diff --git a/call/lib/src/view/callview/public/float/call_float_widget.dart b/call/lib/src/view/callview/public/float/call_float_widget.dart new file mode 100644 index 00000000..73794b81 --- /dev/null +++ b/call/lib/src/view/callview/public/float/call_float_widget.dart @@ -0,0 +1,260 @@ +import 'package:atomic_x_core/atomicxcore.dart'; +import 'package:flutter/material.dart'; + +import 'package:tuikit_atomic_x/ai/ai_transcriber.dart'; +import 'package:tencent_calls_uikit/src/manager/call_page_router.dart'; +import 'package:tencent_calls_uikit/src/tui_call_kit_impl.dart'; +import '../../core/common/call_colors.dart'; +import '../../core/common/constants.dart'; +import '../transcriber/ai_subtitle.dart'; +import '../controls/single_call_controls_widget.dart'; +import '../hint/hint_widget.dart'; +import '../hint/timer_widget.dart'; + +class CallFloatWidget extends StatefulWidget { + final CallCoreController controller; + final bool enableAITranscriber; + + const CallFloatWidget({ + super.key, + required this.controller, + this.enableAITranscriber = false, + }); + + @override + State createState() => _CallFloatWidgetState(); +} + +class _CallFloatWidgetState extends State { + final GlobalKey _controlsKey = GlobalKey(); + double _controlsHeight = 120; + + CallParticipantInfo _selfInfo = CallStore.shared.state.selfInfo.value; + CallInfo _activeCall = CallStore.shared.state.activeCall.value; + + String _remoteAvatarURL = ''; + String _remoteDisplayName = ''; + + @override + void initState() { + super.initState(); + CallStore.shared.state.selfInfo.addListener(_onSelfInfoChanged); + CallStore.shared.state.activeCall.addListener(_onActiveCallChanged); + CallStore.shared.state.allParticipants.addListener(_onAllParticipantsChanged); + _updateRemoteUserInfo(CallStore.shared.state.allParticipants.value); + } + + @override + void dispose() { + CallStore.shared.state.selfInfo.removeListener(_onSelfInfoChanged); + CallStore.shared.state.activeCall.removeListener(_onActiveCallChanged); + CallStore.shared.state.allParticipants.removeListener(_onAllParticipantsChanged); + super.dispose(); + } + + void _onSelfInfoChanged() { + final newValue = CallStore.shared.state.selfInfo.value; + if (newValue.status == CallParticipantStatus.none) return; + setState(() => _selfInfo = newValue); + } + + void _onActiveCallChanged() { + final newValue = CallStore.shared.state.activeCall.value; + if (newValue.callId.isEmpty) return; + setState(() => _activeCall = newValue); + } + + void _onAllParticipantsChanged() { + final participants = CallStore.shared.state.allParticipants.value; + _updateRemoteUserInfo(participants); + } + + void _updateRemoteUserInfo(List participants) { + for (var participant in participants) { + if (participant.id != _selfInfo.id) { + final avatarURL = participant.avatarURL; + final displayName = _getUserDisplayName(participant); + bool changed = false; + if (avatarURL.isNotEmpty && avatarURL != _remoteAvatarURL) { + _remoteAvatarURL = avatarURL; + changed = true; + } + if (displayName.isNotEmpty && displayName != _remoteDisplayName) { + _remoteDisplayName = displayName; + changed = true; + } + if (changed) setState(() {}); + break; + } + } + } + + void _measureControlsHeight() { + WidgetsBinding.instance.addPostFrameCallback((_) { + final renderBox = _controlsKey.currentContext?.findRenderObject() as RenderBox?; + if (renderBox != null && mounted) { + final height = renderBox.size.height; + if (height != _controlsHeight) { + setState(() { + _controlsHeight = height; + }); + } + } + }); + } + + @override + Widget build(BuildContext context) { + _measureControlsHeight(); + return Stack( + children: [ + CallCoreView( + controller: widget.controller, + defaultAvatar: Constants.defaultAvatarImage, + ), + _buildUserInfoWidget(context, _selfInfo, _activeCall), + Positioned( + top: MediaQuery.of(context).size.height * 0.55, + left: 0, + right: 0, + child: const Center(child: HintWidget()), + ), + _buildAISubtitle(context, _selfInfo), + _buildAITranscriberPanel(context, _selfInfo), + Positioned( + right: 0, + left: 0, + bottom: 40 + MediaQuery.of(context).padding.bottom, + child: SingleCallControlsWidget(key: _controlsKey), + ), + _getTimerWidget(), + _buildAITranscriberBtnWidget(_selfInfo), + ], + ); + } + + Widget _buildAISubtitle(BuildContext context, CallParticipantInfo self) { + return ValueListenableBuilder( + valueListenable: TUICallKitImpl.instance.pageRouter.endedHintState, + builder: (context, ended, _) { + if (ended != null) return const SizedBox.shrink(); + return Positioned( + left: 0, + right: 0, + bottom: 40 + _controlsHeight + 8 + MediaQuery.of(context).padding.bottom, + child: Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.9, + maxHeight: MediaQuery.of(context).size.height * 0.3, + ), + child: AISubtitle(userId: self.id), + ), + ); + }, + ); + } + + Widget _buildAITranscriberPanel(BuildContext context, CallParticipantInfo self) { + if (!widget.enableAITranscriber || self.status != CallParticipantStatus.accept) { + return const SizedBox.shrink(); + } + return ValueListenableBuilder( + valueListenable: TUICallKitImpl.instance.pageRouter.endedHintState, + builder: (context, ended, _) { + if (ended != null) return const SizedBox.shrink(); + return AITranscriberPanel( + bottomOffset: _controlsHeight + 48 + MediaQuery.of(context).padding.bottom, + animationDuration: Duration.zero, + ); + }, + ); + } + + Widget _buildUserInfoWidget( + BuildContext context, + CallParticipantInfo self, + CallInfo activeCall, + ) { + if (activeCall.mediaType != CallMediaType.video + || self.status != CallParticipantStatus.waiting) { + return Container(); + } + return Positioned( + top: MediaQuery.of(context).size.height / 4, + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 100, + width: 100, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: Image( + image: NetworkImage( + _remoteAvatarURL.isNotEmpty ? _remoteAvatarURL : Constants.defaultAvatar, + ), + fit: BoxFit.cover, + errorBuilder: (ctx, err, stackTrace) => Image.asset( + 'call_assets/user_icon.png', + package: 'tuikit_atomic_x', + ), + ), + ), + const SizedBox(height: 10), + Text( + _remoteDisplayName, + textScaler: TextScaler.noScaling, + style: TextStyle( + fontSize: 18, + color: _getUserNameColor(activeCall), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } + + Widget _getTimerWidget() { + return Positioned( + top: 20, + width: MediaQuery.of(context).size.width, + height: 100, + child: const Center( + child: TimerWidget(), + ), + ); + } + + String _getUserDisplayName(CallParticipantInfo info) { + if (info.remark.isNotEmpty) { + return info.remark; + } else if (info.name.isNotEmpty) { + return info.name; + } else { + return info.id; + } + } + + Color _getUserNameColor(CallInfo activeCall) { + return activeCall.mediaType == CallMediaType.audio + ? CallColors.colorG7 + : CallColors.colorWhite; + } + + Widget _buildAITranscriberBtnWidget(CallParticipantInfo self) { + if (self.status != CallParticipantStatus.accept || !widget.enableAITranscriber) { + return const SizedBox(); + } + return const Positioned( + left: 52, + top: 52, + width: 40, + height: 40, + child: AITranscriberButton(), + ); + } +} \ No newline at end of file diff --git a/call/lib/src/view/callview/public/hint/hint_widget.dart b/call/lib/src/view/callview/public/hint/hint_widget.dart new file mode 100644 index 00000000..0b71dba0 --- /dev/null +++ b/call/lib/src/view/callview/public/hint/hint_widget.dart @@ -0,0 +1,201 @@ +import 'dart:async'; + +import 'package:tuikit_atomic_x/atomicx.dart'; +import 'package:flutter/material.dart'; +import 'package:tencent_calls_uikit/src/tui_call_kit_impl.dart'; + +import '../../core/common/call_colors.dart'; +import '../../core/common/widget/slide_text_switcher.dart'; + +class _HintDisplayTracker { + static String? _currentCallId; + static bool _hadShowAcceptText = false; + + static bool shouldShowAcceptText(String callId) { + if (_currentCallId != callId) { + _currentCallId = callId; + _hadShowAcceptText = false; + } + return !_hadShowAcceptText; + } + + static void markAcceptTextShown(String callId) { + if (_currentCallId == callId) { + _hadShowAcceptText = true; + } + } +} + +class HintWidget extends StatefulWidget { + const HintWidget({super.key}); + + @override + State createState() => _HintWidgetState(); +} + +class _HintWidgetState extends State { + final _acceptTextDisplayDuration = const Duration(seconds: 1); + Timer? _acceptTextTimer; + + String _displayedText = ''; + Color _displayedColor = CallColors.colorG7; + + @override + void initState() { + super.initState(); + CallStore.shared.state.selfInfo.addListener(_onStateChanged); + CallStore.shared.state.networkQualities.addListener(_onStateChanged); + TUICallKitImpl.instance.pageRouter.endedHintState.addListener(_onStateChanged); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _resolveAndUpdate(); + } + + @override + void dispose() { + CallStore.shared.state.selfInfo.removeListener(_onStateChanged); + CallStore.shared.state.networkQualities.removeListener(_onStateChanged); + TUICallKitImpl.instance.pageRouter.endedHintState.removeListener(_onStateChanged); + _acceptTextTimer?.cancel(); + super.dispose(); + } + + void _onStateChanged() { + _resolveAndUpdate(); + } + + void _resolveAndUpdate() { + if (!mounted) return; + final l10n = AtomicLocalizations.of(context); + final ended = TUICallKitImpl.instance.pageRouter.endedHintState.value; + final selfInfo = CallStore.shared.state.selfInfo.value; + final networkQualities = CallStore.shared.state.networkQualities.value; + + final hintData = ended != null + ? _HintData(ended.text, _getHintTextColor()) + : _resolveHintData(l10n, selfInfo, networkQualities); + + if (hintData.text != _displayedText || hintData.color != _displayedColor) { + setState(() { + _displayedText = hintData.text; + _displayedColor = hintData.color; + }); + } + } + + _HintData _resolveHintData(AtomicLocalizations l10n, + CallParticipantInfo selfInfo, Map networkQualities) { + final connectionText = _getConnectionHintText(selfInfo, l10n); + if (connectionText != null) { + return _HintData(connectionText, CallColors.colorG7); + } + + final statusText = _getStatusHintText(selfInfo, l10n); + if (statusText != null) { + return _HintData(statusText, _getHintTextColor()); + } + + final networkText = _getNetworkQualityHintText(selfInfo, networkQualities, l10n); + if (networkText.isNotEmpty) { + return _HintData(networkText, _getHintTextColor()); + } + + return _HintData('', _getHintTextColor()); + } + + @override + Widget build(BuildContext context) { + if (_displayedText.isEmpty) { + return const SizedBox.shrink(); + } + + return SlideTextSwitcher( + text: _displayedText, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.normal, + color: _displayedColor, + ), + ); + } + + String? _getConnectionHintText(CallParticipantInfo selfInfo, AtomicLocalizations l10n) { + final activeCall = CallStore.shared.state.activeCall.value; + final callId = activeCall.callId; + + if (selfInfo.status != CallParticipantStatus.accept || + !_HintDisplayTracker.shouldShowAcceptText(callId)) { + return null; + } + + if (_acceptTextTimer?.isActive == true) { + return l10n.callConnected; + } + + _acceptTextTimer = Timer(_acceptTextDisplayDuration, () { + if (mounted) { + _HintDisplayTracker.markAcceptTextShown(callId); + _resolveAndUpdate(); + } + }); + + return l10n.callConnected; + } + + String? _getStatusHintText(CallParticipantInfo selfInfo, AtomicLocalizations l10n) { + if (selfInfo.status != CallParticipantStatus.waiting) { + return null; + } + + final activeCall = CallStore.shared.state.activeCall.value; + + if (selfInfo.id == activeCall.inviterId) { + return l10n.callWaitingForInvitationAcceptance; + } else { + return activeCall.mediaType == CallMediaType.audio + ? l10n.callInvitedToAudioCall + : l10n.callInvitedToVideoCall; + } + } + + String _getNetworkQualityHintText( + CallParticipantInfo selfInfo, + Map networkQualities, + AtomicLocalizations l10n, + ) { + final selfNetwork = networkQualities[selfInfo.id]; + if (selfNetwork != null && _isBadNetwork(selfNetwork)) { + return l10n.callSelfNetworkLowQuality; + } + + for (var entry in networkQualities.entries) { + if (entry.key != selfInfo.id && _isBadNetwork(entry.value)) { + return l10n.callOtherPartyNetworkLowQuality; + } + } + + return ''; + } + + bool _isBadNetwork(NetworkQuality? network) { + return network == NetworkQuality.bad || + network == NetworkQuality.veryBad || + network == NetworkQuality.down; + } + + Color _getHintTextColor() { + if (CallStore.shared.state.activeCall.value.mediaType == CallMediaType.video) { + return CallColors.colorWhite; + } + return CallColors.colorG7; + } +} + +class _HintData { + final String text; + final Color color; + const _HintData(this.text, this.color); +} diff --git a/call/lib/src/view/callview/public/hint/timer_widget.dart b/call/lib/src/view/callview/public/hint/timer_widget.dart new file mode 100644 index 00000000..aa3caf1c --- /dev/null +++ b/call/lib/src/view/callview/public/hint/timer_widget.dart @@ -0,0 +1,79 @@ +import 'package:tuikit_atomic_x/atomicx.dart'; +import 'package:flutter/material.dart'; + +import '../../core/common/call_colors.dart'; + +class TimerWidget extends StatefulWidget { + final double? fontSize; + final FontWeight? fontWeight; + + const TimerWidget({ + super.key, + this.fontSize, + this.fontWeight, + }); + + @override + State createState() => _TimerWidgetState(); +} + +class _TimerWidgetState extends State { + CallParticipantInfo _selfInfo = CallStore.shared.state.selfInfo.value; + + @override + void initState() { + super.initState(); + CallStore.shared.state.selfInfo.addListener(_onSelfInfoChanged); + } + + @override + void dispose() { + CallStore.shared.state.selfInfo.removeListener(_onSelfInfoChanged); + super.dispose(); + } + + void _onSelfInfoChanged() { + final newValue = CallStore.shared.state.selfInfo.value; + if (newValue.status == CallParticipantStatus.none) return; + setState(() => _selfInfo = newValue); + } + + @override + Widget build(BuildContext context) { + if (_selfInfo.status == CallParticipantStatus.accept) { + return ValueListenableBuilder( + valueListenable: CallStore.shared.state.activeCall, + builder: (context, activeCall, child) { + return Text( + formatDuration(activeCall.duration.toInt()), + style: TextStyle( + fontSize: widget.fontSize, + fontWeight: widget.fontWeight, + color: CallStore.shared.state.activeCall.value.mediaType == CallMediaType.audio + ? CallColors.colorG7 + : CallColors.colorWhite, + ), + ); + }, + ); + } else { + return Container(); + } + } + + String formatDuration(int timeCount) { + int hour = timeCount ~/ 3600; + int minute = (timeCount % 3600) ~/ 60; + String minuteShow = minute <= 9 ? "0$minute" : "$minute"; + int second = timeCount % 60; + String secondShow = second <= 9 ? "0$second" : "$second"; + + if (hour > 0) { + String hourShow = hour <= 9 ? "0$hour" : "$hour"; + return '$hourShow:$minuteShow:$secondShow'; + } else { + return '$minuteShow:$secondShow'; + } + } + +} diff --git a/atomic-x/lib/call/component/widgets/grid/call_grid_waiting_widget.dart b/call/lib/src/view/callview/public/multi/call_grid_waiting_widget.dart similarity index 94% rename from atomic-x/lib/call/component/widgets/grid/call_grid_waiting_widget.dart rename to call/lib/src/view/callview/public/multi/call_grid_waiting_widget.dart index 84873d21..da3c94b4 100644 --- a/atomic-x/lib/call/component/widgets/grid/call_grid_waiting_widget.dart +++ b/call/lib/src/view/callview/public/multi/call_grid_waiting_widget.dart @@ -1,12 +1,10 @@ import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/cupertino.dart'; -import '../../../common/call_colors.dart'; -import '../../../common/constants.dart'; +import '../../core/common/call_colors.dart'; +import '../../core/common/constants.dart'; import 'package:tuikit_atomic_x/base_component/localizations/atomic_localizations.dart'; -import '../../../common/utils/utils.dart'; - -// ignore_for_file: unused_import +import '../../core/common/utils/utils.dart'; class CallGridWaitingWidget extends StatelessWidget { const CallGridWaitingWidget({super.key}); @@ -21,7 +19,7 @@ class CallGridWaitingWidget extends StatelessWidget { _getCallerInfoDisplay(), Text( l10n.callInvitedToGroupCall, - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, style: const TextStyle(fontSize: 16, color: CallColors.colorG5), ), const SizedBox( @@ -59,7 +57,7 @@ class CallGridWaitingWidget extends StatelessWidget { children: [ Text( AtomicLocalizations.of(context).callTheyAreAlsoThere, - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, style: const TextStyle(fontSize: 15, color: CallColors.colorG5), ), Container( @@ -162,7 +160,7 @@ class _CallerInfoWidgetState extends State<_CallerInfoWidget> { padding: const EdgeInsets.symmetric(vertical: 10), child: Text( displayName, - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, style: const TextStyle(fontSize: 24, color: CallColors.colorG7), ), ), diff --git a/call/lib/src/view/callview/public/multi/call_grid_widget.dart b/call/lib/src/view/callview/public/multi/call_grid_widget.dart new file mode 100644 index 00000000..468fb192 --- /dev/null +++ b/call/lib/src/view/callview/public/multi/call_grid_widget.dart @@ -0,0 +1,156 @@ +import 'package:atomic_x_core/atomicxcore.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:tencent_calls_uikit/src/view/callview/public/multi/call_grid_waiting_widget.dart'; + +import 'package:tuikit_atomic_x/ai/ai_transcriber.dart'; +import '../../core/common/constants.dart'; +import '../../core/common/utils/utils.dart'; +import '../transcriber/ai_subtitle.dart'; +import '../controls/multi_call_controls_widget.dart'; +import '../hint/timer_widget.dart'; + +class CallGridWidget extends StatefulWidget { + final CallCoreController controller; + final bool enableAITranscriber; + + const CallGridWidget({ + super.key, + required this.controller, + this.enableAITranscriber = false, + }); + + @override + State createState() => _CallGridWidgetState(); +} + +class _CallGridWidgetState extends State { + double _controlsHeight = 115; + static const int _animationDuration = 300; + + CallParticipantInfo _selfInfo = CallStore.shared.state.selfInfo.value; + + @override + void initState() { + super.initState(); + CallStore.shared.state.selfInfo.addListener(_onSelfInfoChanged); + } + + @override + void dispose() { + CallStore.shared.state.selfInfo.removeListener(_onSelfInfoChanged); + super.dispose(); + } + + void _onSelfInfoChanged() { + final newValue = CallStore.shared.state.selfInfo.value; + if (newValue.status == CallParticipantStatus.none) return; + setState(() => _selfInfo = newValue); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned.fill( + child: Image( + image: NetworkImage( + StringStream.makeNull(_selfInfo.avatarURL, Constants.defaultAvatar), + ), + fit: BoxFit.cover, + errorBuilder: (ctx, err, stackTrace) => Image.asset( + 'call_assets/user_icon.png', + package: 'tuikit_atomic_x', + ), + ), + ), + Opacity( + opacity: 1, + child: Container( + color: const Color.fromRGBO(45, 45, 45, 0.9), + ), + ), + _selfInfo.id != CallStore.shared.state.activeCall.value.inviterId + && _selfInfo.status == CallParticipantStatus.waiting + ? _buildReceivedGroupCallWaiting(context) + : _buildCallGridView(), + Positioned( + top: 20, + width: MediaQuery.of(context).size.width, + height: 100, + child: const Center( + child: TimerWidget( + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ), + Positioned( + left: 0, + right: 0, + bottom: _controlsHeight + 8 + MediaQuery.of(context).padding.bottom, + child: Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.9, + maxHeight: MediaQuery.of(context).size.height * 0.3, + ), + child: AISubtitle(userId: CallStore.shared.state.selfInfo.value.id), + ), + ), + + if (widget.enableAITranscriber && _selfInfo.status == CallParticipantStatus.accept) + AITranscriberPanel( + bottomOffset: _controlsHeight + 8 + MediaQuery.of(context).padding.bottom, + animationDuration: const Duration(milliseconds: _animationDuration), + ), + + Positioned( + right: 0, + left: 0, + bottom: MediaQuery.of(context).padding.bottom, + child: MultiCallControlsWidget( + onHeightChanged: (height) { + setState(() => _controlsHeight = height); + }, + ), + ), + _buildAITranscriberBtnWidget(), + ], + ); + } + + Widget _buildCallGridView() { + return Container( + margin: const EdgeInsets.only(top: 90), + child: CallCoreView( + controller: widget.controller, + defaultAvatar: Constants.defaultAvatarImage, + loadingAnimation: Constants.loading, + volumeIcons: Constants.volumeIcons, + networkQualityIcons: Constants.networkQualityIcons, + ), + ); + } + + Widget _buildReceivedGroupCallWaiting(BuildContext context) { + return Positioned( + top: 0, + left: 0, + width: MediaQuery.of(context).size.width, + child: const CallGridWaitingWidget(), + ); + } + + _buildAITranscriberBtnWidget() { + if (_selfInfo.status != CallParticipantStatus.accept || !widget.enableAITranscriber) { + return const SizedBox(); + } + return const Positioned( + left: 52, + top: 52, + width: 40, + height: 40, + child: AITranscriberButton(), + ); + } +} \ No newline at end of file diff --git a/atomic-x/lib/call/component/widgets/pip/call_pip_widget.dart b/call/lib/src/view/callview/public/pip/call_pip_widget.dart similarity index 91% rename from atomic-x/lib/call/component/widgets/pip/call_pip_widget.dart rename to call/lib/src/view/callview/public/pip/call_pip_widget.dart index 84968e53..29e26c85 100644 --- a/atomic-x/lib/call/component/widgets/pip/call_pip_widget.dart +++ b/call/lib/src/view/callview/public/pip/call_pip_widget.dart @@ -1,8 +1,8 @@ import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/cupertino.dart'; -import '../../../common/constants.dart'; -import '../../hint/timer_widget.dart'; +import '../../core/common/constants.dart'; +import '../hint/timer_widget.dart'; class CallPipWidget extends StatefulWidget { final CallCoreController controller; diff --git a/atomic-x/lib/call/component/aisubtitle/ai_subtitle.dart b/call/lib/src/view/callview/public/transcriber/ai_subtitle.dart similarity index 100% rename from atomic-x/lib/call/component/aisubtitle/ai_subtitle.dart rename to call/lib/src/view/callview/public/transcriber/ai_subtitle.dart diff --git a/call/lib/src/view/component/incoming_banner/incoming_banner_widget.dart b/call/lib/src/view/component/incoming_banner/incoming_banner_widget.dart index 7ad6a00f..acee5458 100644 --- a/call/lib/src/view/component/incoming_banner/incoming_banner_widget.dart +++ b/call/lib/src/view/component/incoming_banner/incoming_banner_widget.dart @@ -1,9 +1,8 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:atomic_x_core/atomicxcore.dart'; -import 'package:rtc_room_engine/rtc_room_engine.dart'; -import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; -import 'package:tencent_calls_uikit/src/common/constants.dart'; +import 'package:tencent_calls_uikit/src/manager/call_manager.dart'; +import 'package:tencent_calls_uikit/src/manager/call_page_router.dart'; +import 'package:tencent_calls_uikit/src/tui_call_kit_impl.dart'; import 'package:tuikit_atomic_x/base_component/localizations/atomic_localizations.dart'; class IncomingBannerWidget extends StatefulWidget { @@ -17,24 +16,68 @@ class IncomingBannerWidget extends StatefulWidget { } class _IncomingBannerWidgetState extends State { + CallInfo _activeCall = CallStore.shared.state.activeCall.value; + + String _inviterAvatarURL = ''; + String _inviterName = ''; @override void initState() { super.initState(); + CallStore.shared.state.activeCall.addListener(_onActiveCallChanged); + CallStore.shared.state.allParticipants.addListener(_onAllParticipantsChanged); + _updateInviterInfo(CallStore.shared.state.allParticipants.value); } @override void dispose() { + CallStore.shared.state.activeCall.removeListener(_onActiveCallChanged); + CallStore.shared.state.allParticipants.removeListener(_onAllParticipantsChanged); super.dispose(); } + void _onActiveCallChanged() { + final newValue = CallStore.shared.state.activeCall.value; + if (newValue.callId.isEmpty) return; + setState(() => _activeCall = newValue); + } + + void _onAllParticipantsChanged() { + _updateInviterInfo(CallStore.shared.state.allParticipants.value); + } + + void _updateInviterInfo(List participants) { + final inviterId = CallStore.shared.state.activeCall.value.inviterId; + for (var participant in participants) { + if (participant.id == inviterId) { + final name = participant.remark.isNotEmpty + ? participant.remark + : participant.name.isNotEmpty + ? participant.name + : participant.id; + final avatar = participant.avatarURL; + bool changed = false; + if (name.isNotEmpty && name != _inviterName) { + _inviterName = name; + changed = true; + } + if (avatar.isNotEmpty && avatar != _inviterAvatarURL) { + _inviterAvatarURL = avatar; + changed = true; + } + if (changed) setState(() {}); + break; + } + } + } + Future _onAccept() async { - await CallStore.shared.accept(); + await CallManager.instance.accept(); widget.onShowCalling?.call(); } Future _onReject() async { - await CallStore.shared.reject(); + await CallManager.instance.reject(); widget.onCloseAll?.call(); } @@ -44,15 +87,22 @@ class _IncomingBannerWidgetState extends State { @override Widget build(BuildContext context) { - final activeCall = CallStore.shared.state.activeCall.value; - if (activeCall.callId.isEmpty) { - return const SizedBox.shrink(); - } + return ValueListenableBuilder( + valueListenable: TUICallKitImpl.instance.pageRouter.endedHintState, + builder: (context, ended, _) { + if (ended == null && _activeCall.callId.isEmpty) { + return const SizedBox.shrink(); + } + return _buildBannerContent(context, ended); + }, + ); + } + Widget _buildBannerContent(BuildContext context, EndedHintState? ended) { return Material( color: Colors.transparent, child: GestureDetector( - onTap: _onTapBanner, + onTap: ended == null ? _onTapBanner : null, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 15), decoration: BoxDecoration( @@ -71,9 +121,9 @@ class _IncomingBannerWidgetState extends State { children: [ _getInviterAvatarWidget(), const SizedBox(width: 12), - _getInviterInfoWidget(), + _getInviterInfoWidget(ended), const SizedBox(width: 12), - _getActionButtonWidget() + if (ended == null) _getActionButtonWidget(), ], ), ), @@ -81,92 +131,77 @@ class _IncomingBannerWidgetState extends State { ); } - _getInviterAvatarWidget() { - return ValueListenableBuilder(valueListenable: CallStore.shared.state.allParticipants, - builder: (context, allParticipants, child) { - final inviterId = CallStore.shared.state.activeCall.value.inviterId; - final inviter = allParticipants.firstWhere( - (participant) => participant.id == inviterId, - orElse: () => CallStore.shared.state.selfInfo.value, - ); - - return Container( - width: 50, - height: 50, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: const Color(0xFFEFEFEF), - ), - child: Image( - image: NetworkImage(inviter.avatarURL), + Widget _getInviterAvatarWidget() { + return Container( + width: 50, + height: 50, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: const Color(0xFFEFEFEF), + ), + child: _inviterAvatarURL.isNotEmpty + ? Image( + image: NetworkImage(_inviterAvatarURL), fit: BoxFit.cover, errorBuilder: (ctx, err, stack) => Image.asset( 'assets/images/user_icon.png', package: 'tencent_calls_uikit', ), + ) + : Image.asset( + 'assets/images/user_icon.png', + package: 'tencent_calls_uikit', ), - ); - }); + ); } - _getInviterInfoWidget() { - return ValueListenableBuilder(valueListenable: CallStore.shared.state.allParticipants, - builder: (context, allParticipants, child) { - final inviterId = CallStore.shared.state.activeCall.value.inviterId; - final inviter = allParticipants.firstWhere( - (participant) => participant.id == inviterId, - orElse: () => CallStore.shared.state.selfInfo.value, - ); - - var inviterName = inviter.remark.isNotEmpty ? inviter.remark : inviter.name; - if (inviterName.isEmpty) { - inviterName = inviter.id; - } - - var invitationInfo = ''; - final l10n = AtomicLocalizations.of(context); - if (CallStore.shared.state.activeCall.value.inviteeIds.length >= 2) { - invitationInfo = l10n.callInvitedToGroupCall; - } else if (CallStore.shared.state.activeCall.value.mediaType == CallMediaType.audio) { - invitationInfo = l10n.callInvitedToAudioCall; - } else if (CallStore.shared.state.activeCall.value.mediaType == CallMediaType.video) { - invitationInfo = l10n.callInvitedToVideoCall; - } - - return Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - inviterName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - const SizedBox(height: 2), - Text( - invitationInfo, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 12, - color: Colors.white, - ), - ), - ], - ), - ); - }); + Widget _getInviterInfoWidget(EndedHintState? ended) { + var invitationInfo = ''; + if (ended != null) { + invitationInfo = ended.text; + } else { + final l10n = AtomicLocalizations.of(context); + if (_activeCall.inviteeIds.length >= 2) { + invitationInfo = l10n.callInvitedToGroupCall; + } else if (_activeCall.mediaType == CallMediaType.audio) { + invitationInfo = l10n.callInvitedToAudioCall; + } else if (_activeCall.mediaType == CallMediaType.video) { + invitationInfo = l10n.callInvitedToVideoCall; + } + } + return Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _inviterName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + const SizedBox(height: 2), + Text( + invitationInfo, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 12, + color: Colors.white, + ), + ), + ], + ), + ); } - _getActionButtonWidget() { + Widget _getActionButtonWidget() { return Row( children: [ GestureDetector( @@ -208,5 +243,3 @@ class _IncomingBannerWidgetState extends State { ); } } - - diff --git a/call/lib/src/view/component/inviter/invite_user_widget.dart b/call/lib/src/view/component/inviter/invite_user_widget.dart index 4f5f6720..ed32f647 100644 --- a/call/lib/src/view/component/inviter/invite_user_widget.dart +++ b/call/lib/src/view/component/inviter/invite_user_widget.dart @@ -8,7 +8,7 @@ import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:tencent_calls_uikit/src/common/constants.dart'; import 'package:tuikit_atomic_x/base_component/localizations/atomic_localizations.dart'; import 'package:tencent_calls_uikit/src/common/utils/string_stream.dart'; -import 'package:tencent_calls_uikit/src/view/call_page_manager.dart'; +import 'package:tencent_calls_uikit/src/manager/call_page_router.dart'; class InviteUserWidget extends StatefulWidget { final InviteUserCallbacks? callbacks; @@ -37,7 +37,7 @@ class _InviteUserWidgetState extends State { title: Center( child: Text( l10n.callInviteMembers, - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, ), ), leading: IconButton( @@ -95,7 +95,7 @@ class _InviteUserWidgetState extends State { const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), Text( _getMemberDisPlayName(_groupMemberList[index]), - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, style: TextStyle( color: isDefaultSelected ? Colors.grey : Colors.black, fontSize: 18 diff --git a/call/lib/src/view/component/join/join_in_group_widget.dart b/call/lib/src/view/component/join/join_in_group_widget.dart index 2c279fcf..0cbe678e 100644 --- a/call/lib/src/view/component/join/join_in_group_widget.dart +++ b/call/lib/src/view/component/join/join_in_group_widget.dart @@ -1,6 +1,7 @@ import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:tencent_calls_uikit/src/manager/call_manager.dart'; import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:tuikit_atomic_x/base_component/localizations/atomic_localizations.dart'; @@ -78,7 +79,7 @@ class _JoinInGroupWidgetState extends State { const Padding(padding: EdgeInsets.only(left: 15)), Text( '${widget.userIDs.length} ${AtomicLocalizations.of(context).callPersonIsOnTheCall}', - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, ), const Spacer(), Image.asset( @@ -153,7 +154,7 @@ class _JoinInGroupWidgetState extends State { alignment: Alignment.center, child: Text( AtomicLocalizations.of(context).callJoinIn, - textScaleFactor: 1.0, + textScaler: TextScaler.noScaling, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500), ), @@ -184,7 +185,7 @@ class _JoinInGroupWidgetState extends State { } _joinInGroupCallAction() { - CallStore.shared.join(widget.callId!); + CallManager.instance.join(widget.callId!); } _updateUserAvatars() async { diff --git a/call/pubspec.yaml b/call/pubspec.yaml index a16ddceb..f1a83c4d 100644 --- a/call/pubspec.yaml +++ b/call/pubspec.yaml @@ -1,6 +1,6 @@ name: tencent_calls_uikit description: "TUICallKit is a UIKit about audio&video calls launched by Tencent Cloud." -version: 4.0.8 +version: 4.1.1 homepage: environment: @@ -13,7 +13,7 @@ dependencies: tuikit_atomic_x: path: ../atomic-x - tencent_cloud_uikit_core: ^1.7.6 + tencent_cloud_uikit_core: ^2.0.0 plugin_platform_interface: ^2.1.8 shared_preferences: ^2.5.3 path_provider: ^2.1.5 diff --git a/live/live_uikit_barrage/CHANGELOG.md b/live/live_uikit_barrage/CHANGELOG.md index 46d54d46..37c32f96 100644 --- a/live/live_uikit_barrage/CHANGELOG.md +++ b/live/live_uikit_barrage/CHANGELOG.md @@ -1,5 +1,9 @@ # LiveUIKitBarrage +## 3.0.2 + +- Removed unused dependencies. + ## 3.0.1 - Default internationalization language set to English. diff --git a/live/live_uikit_barrage/analysis_options.yaml b/live/live_uikit_barrage/analysis_options.yaml index a5744c1c..797839d8 100644 --- a/live/live_uikit_barrage/analysis_options.yaml +++ b/live/live_uikit_barrage/analysis_options.yaml @@ -1,4 +1,6 @@ include: package:flutter_lints/flutter.yaml +formatter: + page_width: 120 # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/live/live_uikit_barrage/example/lib/main.dart b/live/live_uikit_barrage/example/lib/main.dart index 8e76449d..e0194681 100644 --- a/live/live_uikit_barrage/example/lib/main.dart +++ b/live/live_uikit_barrage/example/lib/main.dart @@ -109,7 +109,7 @@ class _MyAppState extends State { debugPrint("LoginStore login code:${result.errorCode}, message:${result.errorMessage}"); await Future.delayed(const Duration(seconds: 3)); final liveInfo = LiveInfo(liveID: roomId); - final startLiveResult = await _liveListStore.createLive(liveInfo); + final startLiveResult = await _liveListStore.startLive(liveInfo); debugPrint("startLive result:${startLiveResult.errorCode}-${startLiveResult.errorMessage}"); if (startLiveResult.isSuccess) { diff --git a/live/live_uikit_barrage/lib/manager/barrage_manager.dart b/live/live_uikit_barrage/lib/manager/barrage_manager.dart index 7cc83a51..09c49847 100644 --- a/live/live_uikit_barrage/lib/manager/barrage_manager.dart +++ b/live/live_uikit_barrage/lib/manager/barrage_manager.dart @@ -1,11 +1,9 @@ import 'package:atomic_x_core/api/barrage/barrage_store.dart'; -import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart'; import 'package:atomic_x_core/atomicxcore.dart'; import '../state/store.dart'; class BarrageManager { - late V2TimAdvancedMsgListener listener; bool isInit = false; late BarrageStore barrageStore; diff --git a/live/live_uikit_barrage/pubspec.yaml b/live/live_uikit_barrage/pubspec.yaml index 53729fc4..42af3bf3 100644 --- a/live/live_uikit_barrage/pubspec.yaml +++ b/live/live_uikit_barrage/pubspec.yaml @@ -1,6 +1,6 @@ name: live_uikit_barrage description: "Barrage is an interactive bullet screen component in the interactive live broadcast scene." -version: 3.0.1 +version: 3.0.2 repository: https://github.com/Tencent-RTC/TUILiveKit homepage: https://trtc.io/ @@ -14,11 +14,10 @@ dependencies: flutter_localizations: sdk: flutter plugin_platform_interface: ^2.0.2 - tencent_cloud_chat_sdk: ^8.0.5901 extended_text_field: '>= 11.0.0' extended_text: '>= 10.0.0' intl: '>= 0.19.0' - atomic_x_core: ^4.0.1 + atomic_x_core: any dev_dependencies: flutter_test: diff --git a/live/live_uikit_gift/CHANGELOG.md b/live/live_uikit_gift/CHANGELOG.md index 4fa7e2e9..4e7e5ba5 100644 --- a/live/live_uikit_gift/CHANGELOG.md +++ b/live/live_uikit_gift/CHANGELOG.md @@ -1,5 +1,9 @@ # LiveUIKitGift +## 4.1.1 + +- Removed unused dependencies. + ## 4.1.0 - Upgraded `flutter_effect_player` to 3.4.1, fixed the xmagic compilation conflict issue. diff --git a/live/live_uikit_gift/analysis_options.yaml b/live/live_uikit_gift/analysis_options.yaml index a5744c1c..797839d8 100644 --- a/live/live_uikit_gift/analysis_options.yaml +++ b/live/live_uikit_gift/analysis_options.yaml @@ -1,4 +1,6 @@ include: package:flutter_lints/flutter.yaml +formatter: + page_width: 120 # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/live/live_uikit_gift/pubspec.yaml b/live/live_uikit_gift/pubspec.yaml index a89171f6..908b99c1 100644 --- a/live/live_uikit_gift/pubspec.yaml +++ b/live/live_uikit_gift/pubspec.yaml @@ -1,6 +1,6 @@ name: live_uikit_gift description: "live uikit gift component" -version: 4.1.0 +version: 4.1.1 repository: https://github.com/Tencent-RTC/TUILiveKit homepage: https://trtc.io/ @@ -13,8 +13,6 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - tencent_cloud_uikit_core: ^1.7.0 - tencent_cloud_chat_sdk: ^8.0.5901 plugin_platform_interface: ^2.0.2 intl: ">= 0.19.0" protobuf: ^3.1.0 @@ -22,8 +20,8 @@ dependencies: flutter_cache_manager: ^3.4.1 cached_network_image: ^3.4.1 http: ^1.3.0 - atomic_x_core: ^4.1.0 - rtc_room_engine: ^4.1.0 + atomic_x_core: any + rtc_room_engine: any flutter_effect_player: ^3.4.1 dev_dependencies: flutter_test: diff --git a/live/livekit/.gitignore b/live/livekit/.gitignore index b9e7ef28..eabbe7c8 100644 --- a/live/livekit/.gitignore +++ b/live/livekit/.gitignore @@ -51,3 +51,7 @@ app.*.map.json .pub-cache/ .pub/ example/ios/Podfile.lock + +# Re-include the livekit-local .codebuddy (overrides root `**/.codebuddy/`) +!.codebuddy/ +!.codebuddy/** diff --git a/live/livekit/CHANGELOG.md b/live/livekit/CHANGELOG.md index 8829c0f2..a85adbc6 100644 --- a/live/livekit/CHANGELOG.md +++ b/live/livekit/CHANGELOG.md @@ -1,5 +1,18 @@ # TUILiveKit +## 5.0.0 + +- Support viewers to switch to other streamers' live rooms during connected PK +- Support main host feature when starting live broadcast with VideoFixedFloat7Seats +- Add background music function to the video live settings entry +- Add administrator functions including kicking out and muting regular users +- Adapt floating window for landscape small window mode +- Add booth image prompt for scenarios where the streamer is not in the room during video live broadcast +- Adjust the default position of the audio chat room floating window to the middle right side +- Add handling for forced kick-off/logout: exit live broadcast UI +- Add error prompt logs for Android picture-in-picture configuration +- Fix known issues + ## 4.1.0 - Added support for Tencent Effect beauty filters. diff --git a/live/livekit/analysis_options.yaml b/live/livekit/analysis_options.yaml index a5744c1c..797839d8 100644 --- a/live/livekit/analysis_options.yaml +++ b/live/livekit/analysis_options.yaml @@ -1,4 +1,6 @@ include: package:flutter_lints/flutter.yaml +formatter: + page_width: 120 # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/live/livekit/android/build.gradle b/live/livekit/android/build.gradle index 88560040..1e850901 100644 --- a/live/livekit/android/build.gradle +++ b/live/livekit/android/build.gradle @@ -40,7 +40,6 @@ android { dependencies { testImplementation("junit:junit:4.13.2") testImplementation("org.mockito:mockito-core:5.0.0") - api "io.trtc.uikit:common:2.8.0.634" api "com.tencent.liteav.tuikit:tuicore:latest.release" } diff --git a/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/TuilivekitPlugin.java b/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/TuilivekitPlugin.java index 6d922157..3b456d7f 100644 --- a/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/TuilivekitPlugin.java +++ b/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/TuilivekitPlugin.java @@ -18,9 +18,9 @@ import com.tencent.cloud.tuikit.flutter.tuilivekit.utils.ThermalManager; import com.tencent.cloud.tuikit.flutter.tuilivekit.utils.NetworkManager; import com.tencent.cloud.tuikit.flutter.tuilivekit.utils.PictureInPictureManager; -import com.trtc.tuikit.common.foregroundservice.AudioForegroundService; -import com.trtc.tuikit.common.foregroundservice.MediaForegroundService; -import com.trtc.tuikit.common.foregroundservice.VideoForegroundService; +import io.trtc.tuikit.atomicx.foregroundservice.AudioForegroundService; +import io.trtc.tuikit.atomicx.foregroundservice.MediaForegroundService; +import io.trtc.tuikit.atomicx.foregroundservice.VideoForegroundService; import java.lang.reflect.Method; diff --git a/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/utils/PictureInPictureManager.java b/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/utils/PictureInPictureManager.java index 2c520293..bf833c1e 100644 --- a/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/utils/PictureInPictureManager.java +++ b/live/livekit/android/src/main/java/com/tencent/cloud/tuikit/flutter/tuilivekit/utils/PictureInPictureManager.java @@ -2,6 +2,9 @@ import android.app.Activity; import android.app.PictureInPictureParams; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; @@ -18,7 +21,13 @@ public class PictureInPictureManager implements EventChannel.StreamHandler { private static final String STATE_ENTER_PIP = "state_enter_pip"; private static final String STATE_LEAVE_PIP = "state_leave_pip"; + private static final int FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x00400000; + private static final int CANVAS_WIDTH = 720; + private static final int CANVAS_HEIGHT = 1280; + private boolean mEnablePictureInPicture = false; + private int mCanvasWidth = CANVAS_WIDTH; + private int mCanvasHeight = CANVAS_HEIGHT; private EventChannel.EventSink mEventSink; @Override @@ -35,12 +44,24 @@ public boolean enablePictureInPicture(Activity activity, String params) { Log.i(TAG, "enablePictureInPicture, params:" + params); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) { + if (!isActivityDeclaredSupportsPip(activity)) { + logMissingSupportsPipDeclaration(activity); + return false; + } try { JSONObject jsonObject = new JSONObject(params); JSONObject paramsJson = jsonObject.getJSONObject("params"); mEnablePictureInPicture = paramsJson.getBoolean("enable"); + JSONObject canvasJson = paramsJson.getJSONObject("canvas"); + mCanvasWidth = canvasJson.getInt("width"); + mCanvasHeight = canvasJson.getInt("height"); + if (mCanvasWidth <= 0 || mCanvasHeight <= 0) { + mCanvasWidth = CANVAS_WIDTH; + mCanvasHeight = CANVAS_HEIGHT; + } return true; } catch (JSONException e) { + error(activity, e.toString()); return false; } } @@ -49,16 +70,22 @@ public boolean enablePictureInPicture(Activity activity, String params) { public void enterPictureInPicture(Activity activity) { if (!mEnablePictureInPicture) { + error(activity, "mEnablePictureInPicture = " + mEnablePictureInPicture); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Rational aspectRatio = new Rational(9, 16); + if (!isActivityDeclaredSupportsPip(activity)) { + logMissingSupportsPipDeclaration(activity); + return; + } + Rational aspectRatio = new Rational(mCanvasWidth, mCanvasHeight); PictureInPictureParams params = new PictureInPictureParams.Builder().setAspectRatio(aspectRatio).build(); try { boolean ok = activity.enterPictureInPictureMode(params); + info(activity, "enterPictureInPictureMode: " + ok); onEnterPip(ok); } catch (Exception e) { - Log.e(TAG, e.toString()); + error(activity, e.toString()); } } } @@ -89,4 +116,35 @@ private void onEnterPip(boolean success) { } } } + + /** + * Checks whether the current Activity has declared + * android:supportsPictureInPicture="true" in AndroidManifest.xml. + */ + private boolean isActivityDeclaredSupportsPip(Activity activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return false; + } + try { + ComponentName componentName = activity.getComponentName(); + ActivityInfo activityInfo = activity.getPackageManager().getActivityInfo(componentName, 0); + return (activityInfo.flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0; + } catch (PackageManager.NameNotFoundException e) { + error(activity, "isActivityDeclaredSupportsPip, getActivityInfo failed: " + e.getMessage()); + return false; + } + } + + private void logMissingSupportsPipDeclaration(Activity activity) { + String message = "This Activity is missing `android:supportsPictureInPicture=\"true\"` in AndroidManifest.xml"; + error(activity, message); + } + + private void error(Context context, String message) { + LiveKitLog.error(context, "", TAG, 0, message); + } + + private void info(Context context, String message) { + LiveKitLog.info(context, "", TAG, 0, message); + } } \ No newline at end of file diff --git a/live/livekit/assets/images/live_muteImage.png b/live/livekit/assets/images/live_muteImage.png new file mode 100644 index 00000000..daeef1d5 Binary files /dev/null and b/live/livekit/assets/images/live_muteImage.png differ diff --git a/live/livekit/assets/images/live_muteImage_en.png b/live/livekit/assets/images/live_muteImage_en.png new file mode 100644 index 00000000..9a82add4 Binary files /dev/null and b/live/livekit/assets/images/live_muteImage_en.png differ diff --git a/live/livekit/assets/images/live_muteImage_en_land.png b/live/livekit/assets/images/live_muteImage_en_land.png new file mode 100644 index 00000000..0350a2ca Binary files /dev/null and b/live/livekit/assets/images/live_muteImage_en_land.png differ diff --git a/live/livekit/assets/images/live_muteImage_land.png b/live/livekit/assets/images/live_muteImage_land.png new file mode 100644 index 00000000..3e481217 Binary files /dev/null and b/live/livekit/assets/images/live_muteImage_land.png differ diff --git a/live/livekit/assets/images/live_muteImage_small.png b/live/livekit/assets/images/live_muteImage_small.png new file mode 100644 index 00000000..144a4040 Binary files /dev/null and b/live/livekit/assets/images/live_muteImage_small.png differ diff --git a/live/livekit/assets/images/live_revoke_featured_host.png b/live/livekit/assets/images/live_revoke_featured_host.png new file mode 100644 index 00000000..208e532f Binary files /dev/null and b/live/livekit/assets/images/live_revoke_featured_host.png differ diff --git a/live/livekit/assets/images/live_set_featured_host.png b/live/livekit/assets/images/live_set_featured_host.png new file mode 100644 index 00000000..67572b89 Binary files /dev/null and b/live/livekit/assets/images/live_set_featured_host.png differ diff --git a/live/livekit/assets/images/live_settings_item_audio_effect.png b/live/livekit/assets/images/live_settings_item_audio_effect.png new file mode 100644 index 00000000..87f5919c Binary files /dev/null and b/live/livekit/assets/images/live_settings_item_audio_effect.png differ diff --git a/live/livekit/assets/images/live_settings_item_music.png b/live/livekit/assets/images/live_settings_item_music.png index 87f5919c..61b501ef 100644 Binary files a/live/livekit/assets/images/live_settings_item_music.png and b/live/livekit/assets/images/live_settings_item_music.png differ diff --git a/live/livekit/docs/architecture/module-map.md b/live/livekit/docs/architecture/module-map.md new file mode 100644 index 00000000..373cebe5 --- /dev/null +++ b/live/livekit/docs/architecture/module-map.md @@ -0,0 +1,187 @@ +# Module Map — `lib/` 文件级导航 + +> 给「人」和「Agent」找文件用。约 219 个 dart + 5 个 arb,按场景定位最快。 + +## 总览 + +``` +lib/ +├── tencent_live_uikit.dart ← Public API 入口(仅这里 export 的才是公开 API) +├── live_identity_generator.dart ← 用户身份生成工具 +├── live_info_utils.dart ← 直播信息工具 +├── live_navigator_observer.dart ← 路由观察者 +│ +├── live_stream/ ← 视频直播 +├── voice_room/ ← 语音聊天室 +├── seat_grid_widget/ ← 通用麦位网格 +├── component/ ← 跨场景共享组件 +└── common/ ← 基础设施 +``` + +--- + +## 1. `live_stream/` — 视频直播 + +``` +live_stream/ +├── live_define.dart ← 模块内类型定义 +├── api/ ← 对外 API +├── manager/ ← 业务管理(11 文件,按角色拆 Extension) +│ └── observer/ ← TUIRoomEngine 事件接收 +├── state/ ← 响应式状态(RoomState / UserState / MediaState / ...) +└── features/ ← 子功能(72 文件,按交互能力拆分) + ├── live_room_anchor_widget.dart ← 主播端入口(普通版) + ├── live_room_anchor_overlay.dart ← 主播端入口(悬浮窗版) + ├── live_room_audience_widget.dart ← 观众端入口(普通版) + └── live_room_audience_overlay.dart ← 观众端入口(悬浮窗版) +``` + +**关键查询**: + +| 你在找... | 去这里 | +|---|---| +| 主播/观众对外入口 Widget | `features/live_room_{anchor,audience}_{widget,overlay}.dart` | +| 主播开播逻辑 | `manager/live_stream_manager_anchor.dart`(或 `_with_anchor.dart`) | +| 观众观看逻辑 | `manager/live_stream_manager_audience.dart` | +| 房间/媒体/用户状态 | `state/` | +| 连麦 / PK 实现 | `features/`(按子功能浏览) | +| Engine 事件分发 | `manager/observer/` | +| 对外暴露的接口 | `api/` | + +**业务流程**: + +- **主播流程**:`AnchorPrepareWidget`(预览/设置/美颜) → `AnchorBroadcastWidget`(直播中) → `EndStatisticsWidget`(结束统计) +- **观众流程**:`LiveListWidget`(列表) → `AudienceWidget`(观看/弹幕/礼物/申请连麦) + +**互动功能对照**: + +| 功能 | Manager | State | +|---|---|---| +| 连麦 | `CoGuestManager` | `CoGuestState` | +| 跨房连线 | `CoHostManager` | `CoHostState` | +| PK 对战 | `BattleManager` | `BattleState` | +| 悬浮窗 | `FloatWindowManager` | `FloatWindowState` | + +--- + +## 2. `voice_room/` — 语音聊天室 + +``` +voice_room/ +├── voice_room_widget.dart ← 主入口 Widget(普通版) +├── voice_room_overlay.dart ← 主入口 Widget(悬浮窗版) +├── manager/ ← 业务管理 +├── widget/ ← 子 Widget(22 个) +└── index.dart ← 对外导出 +``` + +**关键查询**: + +| 你在找... | 去这里 | +|---|---| +| 进入语音房(普通/悬浮) | `voice_room_widget.dart` / `voice_room_overlay.dart` | +| 麦位 UI | `widget/`(搭配 `seat_grid_widget/`) | +| 业务逻辑 | `manager/` | + +--- + +## 3. `seat_grid_widget/` — 麦位网格通用组件 + +被 `voice_room` 复用,也可独立使用。常见 N 种布局(1v1、3 麦、6 麦、9 麦…)。 + +修改注意:保持向后兼容,避免破坏现有调用方。 + +--- + +## 4. `component/` — 跨场景共享组件 + +``` +component/ +├── audience_list/ ← 观众列表 +├── audio_effect/ ← 音效 +├── beauty/ ← 美颜面板(依赖 te_beauty_kit) +├── bgm/ ← 背景音乐面板 +├── float_window/ ← 悬浮窗 +├── gift_access/ ← 礼物入口(依赖 live_uikit_gift) +├── live_info/ ← 直播间信息卡 +├── network_info/ ← 网络状态展示 +└── index.dart +``` + +**判断要点**: + +- 同一组件被 `live_stream` 和 `voice_room` 都用 → 放 `component/` +- 仅一个场景用 → 放该场景的 `widget/` 下 + +--- + +## 5. `common/` — 基础设施 + +``` +common/ +├── boot/ ← 启动 / 初始化 +├── constants/ ← 常量(颜色 key、尺寸、超时等) +├── error/ ← 错误码与处理 +├── language/ ← i18n(5 个 arb + 生成的 dart) +├── logger/ ← 日志封装(替代 print) +├── platform/ ← 平台桥接(method channel) +├── reporter/ ← 数据埋点 +├── resources/ ← 图片 / 字体资源加载 +├── screen/ ← 屏幕适配 +├── selector/ ← 通用选择器 +├── widget/ ← 通用 Widget(11 个,如按钮、对话框) +└── index.dart +``` + +**关键查询**: + +| 你在找... | 去这里 | +|---|---| +| 加日志 | `logger/` 下的 `Logger` 类 | +| 新增错误码 | `error/` | +| 新增 i18n 文案 | `language/intl_*.arb`(5 个都要改) | +| 加图片资源 | `resources/`(同时更新 `assets/images/` 与 `pubspec.yaml`) | +| 屏幕尺寸适配 | `screen/` | +| 通用按钮 / 对话框 | `widget/` | + +--- + +## 6. 入口文件 `tencent_live_uikit.dart` + +**判断 Public API 的唯一标准**: + +```dart +// lib/tencent_live_uikit.dart +export 'live_stream/api/...'; +export 'voice_room/index.dart'; +export 'component/index.dart'; +// ... +``` + +只要这里没 export,就属于内部实现,**可以自由重构**。 + +新增公开 API 时务必同步更新此文件,并阅读 `.codebuddy/rules/api-design.md`。 + +--- + +## 7. 文件命名速查 + +| 看到这种文件名 | 含义 | +|---|---| +| `xxx_manager.dart` | 业务编排类 | +| `xxx_manager_with_yyy.dart` | Manager 的 Extension(按角色拆分) | +| `xxx_state.dart` | 响应式状态类 | +| `xxx_service.dart` | Engine API 封装 | +| `xxx_observer.dart` | Engine 事件接收者 | +| `xxx_widget.dart` | UI 组件 | +| `xxx_overlay.dart` | 悬浮窗形态 | +| `index.dart` | 目录导出聚合 | +| `intl_*.arb` | i18n 文案 | + +--- + +## 8. 找文件的推荐路径 + +1. **先想场景**:直播 → `live_stream/`,语音房 → `voice_room/`,跨场景 → `component/`,基础设施 → `common/` +2. **再想角色**:UI → `widget/`,状态 → `state/`,业务 → `manager/`,引擎 → `service/` 或 `manager/observer/` +3. **找不到**:用 IDE 全局搜索文件名片段,或搜索类名 diff --git a/live/livekit/docs/architecture/overview.md b/live/livekit/docs/architecture/overview.md new file mode 100644 index 00000000..46a3aad4 --- /dev/null +++ b/live/livekit/docs/architecture/overview.md @@ -0,0 +1,141 @@ +# Architecture Overview + +> 给「人」看的高层架构文档。Agent 路由请用 `.codebuddy/rules/project.md`。 + +## 1. 项目定位 + +`tencent_live_uikit` 是腾讯云直播 UIKit 的 Flutter 实现,基于 `TUIRoomEngine`(底层 TRTC + IM)。 + +它提供两个主场景的开箱即用 UI: + +- **视频直播**(`live_stream`):主播开播、观众观看、连麦、跨房 PK +- **语音聊天室**(`voice_room`):多人语音、麦位管理、礼物互动 + +## 2. 技术栈 + +| 层 | 技术 | +|---|---| +| UI | Flutter Widget + `ValueListenableBuilder` | +| 状态 | `ValueNotifier` / `StreamController.broadcast()` | +| 业务编排 | Manager + Dart Extension | +| 引擎接入 | Service 封装 `TUIRoomEngine` | +| i18n | Flutter `intl` + ARB | +| 主题 | `tuikit_atomic_x` 原子设计 | + +## 3. 三层架构 + +``` +┌────────────────────────────────────────────────────┐ +│ Widget (UI) │ +│ ValueListenableBuilder / StreamBuilder │ +└──────────────────────┬─────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────┐ +│ Manager (Business Logic) │ +│ LiveStreamManager / VoiceRoomManager / ... │ +│ - 编排 State 更新 │ +│ - 调用 Service │ +│ - 通过 Context 与子 Manager 通信(WeakReference) │ +│ - 大类按角色拆为多个 Extension │ +└──────────────────────┬─────────────────────────────┘ + │ + ┌────────┴────────┐ + ▼ ▼ +┌──────────────────────┐ ┌──────────────────────┐ +│ State (Reactive) │ │ Service (API Wrap) │ +│ ValueNotifier │ │ Wraps TUIRoomEngine │ +│ Stream broadcast │ │ Per-domain split │ +└──────────────────────┘ └──────────┬───────────┘ + │ + ▼ + ┌──────────────────────┐ + │ TUIRoomEngine │ + │ (TRTC + IM) │ + └──────────────────────┘ +``` + +### 关键约束 + +1. **Widget 不写业务逻辑**,只负责渲染和事件转发 +2. **Manager 不直接调引擎**,必须经 Service +3. **跨 Manager 通信走 Context**,引用必须 `WeakReference` +4. **Engine 事件**通过 Observer 接收 → 分发给对应 Manager → 更新 State → UI 重建 + +## 4. 引擎事件流 + +``` +TUIRoomEngine event + │ + ▼ +RoomEngineObserver / LiveConnectionObserver / LiveBattleObserver + │ + ▼ +Dispatch to Manager + │ + ▼ +Update State (ValueNotifier.value = ...) + │ + ▼ +ValueListenableBuilder rebuilds Widget +``` + +Observer 文件位于 `lib/{module}/manager/observer/`。 + +## 5. 依赖关系 + +``` +tencent_live_uikit (本包) + │ + ├── tuikit_atomic_x ← 颜色、主题、字体 + ├── atomic_x_core ← 通用工具、基础类 + ├── live_uikit_barrage ← 弹幕组件 + ├── live_uikit_gift ← 礼物组件 + ├── te_beauty_kit ← 美颜 + │ + ├── tencent_rtc_sdk ← RTC 引擎 + ├── tencent_cloud_chat_sdk← IM 信令 + └── tencent_cloud_uikit_core +``` + +⚠️ 上方 5 个本地依赖通过 `path:` 引用,**不要替换为 pub.dev 版本**。 + +## 6. 模块速览 + +| 模块 | 简介 | +|---|---| +| `live_stream/` | 视频直播全流程 | +| `voice_room/` | 语音聊天室 | +| `seat_grid_widget/` | 麦位网格通用组件 | +| `component/` | 跨场景共享组件(礼物、悬浮窗、网络信息等) | +| `common/` | 基础设施(i18n / 日志 / 平台桥接 / 资源 / 适配) | + +详细文件级导航见 [`module-map.md`](./module-map.md)。 + +## 7. 入口文件 + +```dart +// lib/tencent_live_uikit.dart +export 'live_stream/...'; +export 'voice_room/...'; +export 'seat_grid_widget/...'; +// ... +``` + +只有从此处导出的符号才属于 Public API。 + +## 8. 多语言 + +- 模板:`lib/common/language/intl_en.arb` +- 支持:英 / 简中 / 繁中 / 日 / 韩 +- 每个 key 必须 5 个文件同步 + +详见 `.codebuddy/rules/i18n.md`。 + +## 9. 进一步阅读 + +| 主题 | 文档 | +|---|---| +| 文件级导航 | `module-map.md` | +| 状态管理细节 | `.codebuddy/skills/tencent-live-uikit/references/state_management.md` | +| 颜色主题用法 | `.codebuddy/skills/tencent-live-uikit/references/tuikit_atomic_x.md` | diff --git a/live/livekit/example/lib/main.dart b/live/livekit/example/lib/main.dart index 1e36811f..b5e00900 100644 --- a/live/livekit/example/lib/main.dart +++ b/live/livekit/example/lib/main.dart @@ -3,7 +3,6 @@ import 'package:flutter/services.dart'; import 'package:tencent_live_uikit/tencent_live_uikit.dart'; import 'package:tencent_live_uikit_example/generated/l10n.dart'; import 'package:tencent_live_uikit_example/src/view/index.dart'; -import 'package:tuikit_atomic_x/base_component/theme/theme_state.dart'; import 'package:tuikit_atomic_x/atomicx.dart'; void main() { diff --git a/live/livekit/example/lib/src/view/index.dart b/live/livekit/example/lib/src/view/index.dart index d4e609ca..4f1b6549 100644 --- a/live/livekit/example/lib/src/view/index.dart +++ b/live/livekit/example/lib/src/view/index.dart @@ -1,5 +1,3 @@ -library view; - export 'login/login_widget.dart'; export 'login/profile_widget.dart'; export 'scene/video_live_widget.dart'; diff --git a/live/livekit/example/lib/src/view/login/login_widget.dart b/live/livekit/example/lib/src/view/login/login_widget.dart index 1e3f3360..d5d1ba5e 100644 --- a/live/livekit/example/lib/src/view/login/login_widget.dart +++ b/live/livekit/example/lib/src/view/login/login_widget.dart @@ -172,6 +172,7 @@ class _LoginWidgetState extends State { } } else { LiveKitLogger.error("LoginStore login fail, {code:${result.errorCode}, message:${result.errorMessage}"); + if (!mounted) return; makeToast(context, "code:${result.errorCode} message:${result.errorMessage}"); } _isButtonEnabled = true; diff --git a/live/livekit/example/lib/src/view/main/me_widget.dart b/live/livekit/example/lib/src/view/main/me_widget.dart index 8df9f6ab..0935c0ab 100644 --- a/live/livekit/example/lib/src/view/main/me_widget.dart +++ b/live/livekit/example/lib/src/view/main/me_widget.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:tencent_live_uikit/component/float_window/global_float_window_manager.dart'; import 'package:tencent_live_uikit_example/generated/l10n.dart'; import 'package:tencent_live_uikit_example/src/store/app_store.dart'; import 'package:tencent_live_uikit_example/src/view/login/login_widget.dart'; @@ -184,7 +183,6 @@ extension _MeWidgetStateLogicExtension on _MeWidgetState { } void _logout(BuildContext context) { - GlobalFloatWindowManager.instance.overlayManager.closeOverlay(); Future.delayed(const Duration(milliseconds: 500), () => LoginStore.shared.logout()); Navigator.of(context).pop(); NavigatorState navigatorState = Navigator.of(context); diff --git a/live/livekit/example/lib/src/view/main/update_nickname_widget.dart b/live/livekit/example/lib/src/view/main/update_nickname_widget.dart index 840fd2b1..c369fe3f 100644 --- a/live/livekit/example/lib/src/view/main/update_nickname_widget.dart +++ b/live/livekit/example/lib/src/view/main/update_nickname_widget.dart @@ -163,6 +163,7 @@ extension _UpdateNicknameWidgetStateLogicExtension } final result = await AppManager.setSelfInfo(AppStore.userAvatar, _inputNickname); + if (!mounted) return; if (result.code == 0) { Navigator.of(context).pop(); } diff --git a/live/livekit/example/lib/src/view/scene/video_live_widget.dart b/live/livekit/example/lib/src/view/scene/video_live_widget.dart index dbad8395..19654c78 100644 --- a/live/livekit/example/lib/src/view/scene/video_live_widget.dart +++ b/live/livekit/example/lib/src/view/scene/video_live_widget.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:tencent_live_uikit/component/float_window/global_float_window_manager.dart'; import 'package:tencent_live_uikit/tencent_live_uikit.dart'; import 'package:tencent_live_uikit_example/generated/l10n.dart'; import 'package:tencent_live_uikit_example/src/store/app_store.dart'; diff --git a/live/livekit/example/pubspec.yaml b/live/livekit/example/pubspec.yaml index a072ca7b..1d2d2c79 100644 --- a/live/livekit/example/pubspec.yaml +++ b/live/livekit/example/pubspec.yaml @@ -4,10 +4,10 @@ description: "Demonstrates how to use the tuilivekit plugin." # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# EffectPlayer: -# sub_spec: 'NoXMagic' # Default or NoXMagic -# TencentEffect: -# te_sub_spec: 'S1-07' +EffectPlayer: + sub_spec: 'Default' # Default or NoXMagic +TencentEffect: + te_sub_spec: 'S1-07' environment: sdk: '>=3.4.0 <4.0.0' diff --git a/live/livekit/lib/common/boot/boot.dart b/live/livekit/lib/common/boot/boot.dart index 865a2dd1..2a9a51ff 100644 --- a/live/livekit/lib/common/boot/boot.dart +++ b/live/livekit/lib/common/boot/boot.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; class Boot { diff --git a/live/livekit/lib/common/boot/index.dart b/live/livekit/lib/common/boot/index.dart index d18c7a28..1a92c601 100644 --- a/live/livekit/lib/common/boot/index.dart +++ b/live/livekit/lib/common/boot/index.dart @@ -1,3 +1,2 @@ -library boot; export 'boot.dart'; diff --git a/live/livekit/lib/common/constants/constants.dart b/live/livekit/lib/common/constants/constants.dart index 0662dcf9..99f09227 100644 --- a/live/livekit/lib/common/constants/constants.dart +++ b/live/livekit/lib/common/constants/constants.dart @@ -1,5 +1,5 @@ class Constants { - static const String pluginVersion = '4.1.0'; + static const String pluginVersion = '5.0.0'; static const String pluginName = 'tencent_live_uikit'; static const String liveKitLog = 'LiveKitLog'; diff --git a/live/livekit/lib/common/constants/index.dart b/live/livekit/lib/common/constants/index.dart index faf64a52..76513262 100644 --- a/live/livekit/lib/common/constants/index.dart +++ b/live/livekit/lib/common/constants/index.dart @@ -1,3 +1,2 @@ -library constants; export 'constants.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/error/error_handler.dart b/live/livekit/lib/common/error/error_handler.dart index 305ebaf4..bbc3d8bf 100644 --- a/live/livekit/lib/common/error/error_handler.dart +++ b/live/livekit/lib/common/error/error_handler.dart @@ -3,21 +3,24 @@ import 'package:rtc_room_engine/rtc_room_engine.dart'; import '../index.dart'; class ErrorHandler { - static const Set interceptToastOnlyPrintLog = { + static const Set _interceptToastOnlyPrintLogOnLive = { LiveError.freqLimit, - LiveError.repeatOperation, - LiveError.seatNotSupportLinkMic, + LiveError.roomMismatch, + }; + + static const Set _interceptToastOnlyPrintLogOnIM = { + TIMError.errSdkCommApiCallFrequencyLimit, }; static String? convertToErrorMessage(int code, String? message) { LiveKitLogger.info('ErrorHandler :[error: $code, message: $message]'); final liveError = LiveError.fromInt(code); if (liveError != null) { - return liveError.description; + return _interceptToastOnlyPrintLogOnLive.contains(liveError) ? '' : liveError.description; } final imError = TIMError.fromInt(code); if (imError != null) { - return imError.description; + return _interceptToastOnlyPrintLogOnIM.contains(imError) ? '' : imError.description; } return "code: $code, message: $message"; } @@ -401,7 +404,8 @@ enum TIMError { errSdkNetWaitSendTimeoutNoNetwork(9523), errSdkNetWaitAckTimeoutNoNetwork(9524), errSdkNetSendRemainingTimeoutNoNetwork(9525), - errSvrGroupShutUpDeny(10017); + errSvrGroupShutUpDeny(10017), + errSvrSensitiveWordsBan(80001); final int code; @@ -441,6 +445,8 @@ extension TIMErrorWithLocalization on TIMError { case TIMError.errSdkNetWaitAckTimeoutNoNetwork: case TIMError.errSdkNetSendRemainingTimeoutNoNetwork: return LiveKitLocalizations.of(Global.appContext())?.live_barrage_error_network; + case TIMError.errSvrSensitiveWordsBan: + return LiveKitLocalizations.of(Global.appContext())?.common_server_error_im_sensitive_words_ban; default: return '${LiveKitLocalizations.of(Global.appContext())?.common_client_error_failed}, code: $code'; } diff --git a/live/livekit/lib/common/error/index.dart b/live/livekit/lib/common/error/index.dart index 47579317..f4d03785 100644 --- a/live/livekit/lib/common/error/index.dart +++ b/live/livekit/lib/common/error/index.dart @@ -1,3 +1,2 @@ -library error_handler; export 'error_handler.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/index.dart b/live/livekit/lib/common/index.dart index 60a98e47..ca5072d9 100644 --- a/live/livekit/lib/common/index.dart +++ b/live/livekit/lib/common/index.dart @@ -1,4 +1,3 @@ -library common; export 'boot/index.dart'; export 'constants/index.dart'; diff --git a/live/livekit/lib/common/language/gen/livekit_localizations.dart b/live/livekit/lib/common/language/gen/livekit_localizations.dart index 5d309cbc..32d43b12 100644 --- a/live/livekit/lib/common/language/gen/livekit_localizations.dart +++ b/live/livekit/lib/common/language/gen/livekit_localizations.dart @@ -64,24 +64,26 @@ import 'livekit_localizations_zh.dart'; /// be consistent with the languages listed in the LiveKitLocalizations.supportedLocales /// property. abstract class LiveKitLocalizations { - static LiveKitLocalizations? defaultLocalizations; LiveKitLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; + // ---- CUSTOM PATCH (keep after each `flutter gen-l10n`) ---- + // Fallback to English bundle when no Localizations ancestor is found, + // so call sites never crash at runtime. + static LiveKitLocalizations? defaultLocalizations; + static LiveKitLocalizations? of(BuildContext context) { LiveKitLocalizations? localizations = Localizations.of(context, LiveKitLocalizations); if (localizations == null) { - if (defaultLocalizations == null) { - defaultLocalizations = LiveKitLocalizationsEn(); - } + defaultLocalizations ??= LiveKitLocalizationsEn(); return defaultLocalizations; } return localizations; } + // ---- END CUSTOM PATCH ---- - static const LocalizationsDelegate delegate = - _LiveKitLocalizationsDelegate(); + static const LocalizationsDelegate delegate = _LiveKitLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -93,8 +95,7 @@ abstract class LiveKitLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = - >[ + static const List> localizationsDelegates = >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, @@ -1362,8 +1363,7 @@ abstract class LiveKitLocalizations { /// /// In en, this message translates to: /// **'It\'s not allowed to cancel battle for room in battle'** - String - get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle; + String get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle; /// No description provided for @common_server_error_metadata_no_valid_keys. /// @@ -1387,8 +1387,7 @@ abstract class LiveKitLocalizations { /// /// In en, this message translates to: /// **'The size of key in the room\'s Metadata exceeds the maximum byte limit'** - String - get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit; + String get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit; /// No description provided for @common_server_error_metadata_total_size_exceeds_the_limit. /// @@ -1576,6 +1575,12 @@ abstract class LiveKitLocalizations { /// **'The user is already on the seat'** String get common_server_error_user_is_already_on_the_mic_seat; + /// No description provided for @common_server_error_im_sensitive_words_ban. + /// + /// In en, this message translates to: + /// **'The message or document contains sensitive content and is prohibited from being sent.'** + String get common_server_error_im_sensitive_words_ban; + /// No description provided for @common_set_as_background. /// /// In en, this message translates to: @@ -2319,21 +2324,48 @@ abstract class LiveKitLocalizations { /// In en, this message translates to: /// **'Select App and Start Live'** String get common_select_app_to_live; + + /// No description provided for @common_violation_alert_toast. + /// + /// In en, this message translates to: + /// **'Current content may violate platform guidelines, please follow the rules'** + String get common_violation_alert_toast; + + /// No description provided for @live_song_unknown_artist. + /// + /// In en, this message translates to: + /// **'Unknown'** + String get live_song_unknown_artist; + + /// No description provided for @live_anchor_manager_set_featured_host. + /// + /// In en, this message translates to: + /// **'Set as Featured'** + String get live_anchor_manager_set_featured_host; + + /// No description provided for @live_anchor_manager_revoke_featured_host. + /// + /// In en, this message translates to: + /// **'Revoke Featured'** + String get live_anchor_manager_revoke_featured_host; + + /// No description provided for @common_enter_anchor_live_room. + /// + /// In en, this message translates to: + /// **'Enter Live Room'** + String get common_enter_anchor_live_room; } -class _LiveKitLocalizationsDelegate - extends LocalizationsDelegate { +class _LiveKitLocalizationsDelegate extends LocalizationsDelegate { const _LiveKitLocalizationsDelegate(); @override Future load(Locale locale) { - return SynchronousFuture( - lookupLiveKitLocalizations(locale)); + return SynchronousFuture(lookupLiveKitLocalizations(locale)); } @override - bool isSupported(Locale locale) => - ['ar', 'en', 'ja', 'zh'].contains(locale.languageCode); + bool isSupported(Locale locale) => ['ar', 'en', 'ja', 'zh'].contains(locale.languageCode); @override bool shouldReload(_LiveKitLocalizationsDelegate old) => false; @@ -2364,8 +2396,7 @@ LiveKitLocalizations lookupLiveKitLocalizations(Locale locale) { return LiveKitLocalizationsZh(); } - throw FlutterError( - 'LiveKitLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + throw FlutterError('LiveKitLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' 'an issue with the localizations generation tool. Please file an issue ' 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'that was used.'); diff --git a/live/livekit/lib/common/language/gen/livekit_localizations_ar.dart b/live/livekit/lib/common/language/gen/livekit_localizations_ar.dart index dba9a08a..ba79d734 100644 --- a/live/livekit/lib/common/language/gen/livekit_localizations_ar.dart +++ b/live/livekit/lib/common/language/gen/livekit_localizations_ar.dart @@ -9,15 +9,13 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { LiveKitLocalizationsAr([String locale = 'ar']) : super(locale); @override - String get common_disconnect_guest_tips => - 'هل أنت متأكد أنك تريد قطع الاتصال بـ xxx؟'; + String get common_disconnect_guest_tips => 'هل أنت متأكد أنك تريد قطع الاتصال بـ xxx؟'; @override String get common_anchor_battle => 'المنافسة'; @override - String get common_anchor_end_link_tips => - 'أنت حالياً في بث مشترك مع مذيعين آخرين. هل تريد [إنهاء البث]؟'; + String get common_anchor_end_link_tips => 'أنت حالياً في بث مشترك مع مذيعين آخرين. هل تريد [إنهاء البث]؟'; @override String get common_battle_connecting => 'جاري الاتصال'; @@ -39,8 +37,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_request_rejected => 'xxx رفض'; @override - String get common_battle_inviter_cancel => - 'xxx ألغى المنافسة، يرجى المحاولة مرة أخرى'; + String get common_battle_inviter_cancel => 'xxx ألغى المنافسة، يرجى المحاولة مرة أخرى'; @override String get common_battle_inviting => 'xxx يدعوك للمنافسة معاً'; @@ -55,8 +52,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_battle_wait_stop => 'إلغاء'; @override - String get common_connect_conflict => - 'الغرفة المدعو للاتصال بها متصلة بغرفة أخرى.'; + String get common_connect_conflict => 'الغرفة المدعو للاتصال بها متصلة بغرفة أخرى.'; @override String get common_connect_error => 'أخطاء أخرى، لا يمكن الاتصال.'; @@ -77,8 +73,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_connection_list_title => 'المذيعون المتصلون (xxx)'; @override - String get common_connection_room_full => - 'تجاوز عدد البث المشترك الحد الأقصى.'; + String get common_connection_room_full => 'تجاوز عدد البث المشترك الحد الأقصى.'; @override String get common_cover => 'الغلاف'; @@ -93,8 +88,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_disable_video => 'تعطيل الفيديو'; @override - String get common_disconnect_tips => - 'هل أنت متأكد أنك تريد قطع الاتصال بالمذيعين الآخرين؟'; + String get common_disconnect_tips => 'هل أنت متأكد أنك تريد قطع الاتصال بالمذيعين الآخرين؟'; @override String get common_enable_audio => 'تفعيل الصوت'; @@ -109,12 +103,10 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_end_connect => 'قطع الاتصال'; @override - String get common_end_connection_tips => - 'أنت حالياً في بث مشترك. هل تريد [إنهاء البث المشترك] أم [إنهاء البث]؟'; + String get common_end_connection_tips => 'أنت حالياً في بث مشترك. هل تريد [إنهاء البث المشترك] أم [إنهاء البث]؟'; @override - String get common_end_pk_tips => - 'أنت حالياً في وضع المنافسة. هل تريد [إنهاء المنافسة] أم [إنهاء البث]؟'; + String get common_end_pk_tips => 'أنت حالياً في وضع المنافسة. هل تريد [إنهاء المنافسة] أم [إنهاء البث]؟'; @override String get common_kick_out_of_room => 'إزالة'; @@ -150,8 +142,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_send_message_disabled => 'تم كتم صوتك في الغرفة الحالية'; @override - String get common_send_message_enable => - 'تم إلغاء كتم صوتك في الغرفة الحالية'; + String get common_send_message_enable => 'تم إلغاء كتم صوتك في الغرفة الحالية'; @override String get common_setting_done => 'تم'; @@ -166,15 +157,13 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_stop_video => 'إيقاف الفيديو'; @override - String get common_terminate_room_connection_message => - 'هل أنت متأكد أنك تريد قطع الاتصال؟'; + String get common_terminate_room_connection_message => 'هل أنت متأكد أنك تريد قطع الاتصال؟'; @override String get common_un_mute_audio_by_master => 'قام المذيع بإلغاء كتم صوتك'; @override - String get common_un_mute_video_by_master => - 'قام المذيع بتفعيل الفيديو الخاص بك'; + String get common_un_mute_video_by_master => 'قام المذيع بتفعيل الفيديو الخاص بك'; @override String get common_unmute_audio => 'إلغاء كتم الصوت'; @@ -186,28 +175,22 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_link_host => 'مضيف'; @override - String get live_barrage_error_content_is_long => - 'المحتوى طويل جداً، يرجى تقليل المحتوى والمحاولة مرة أخرى'; + String get live_barrage_error_content_is_long => 'المحتوى طويل جداً، يرجى تقليل المحتوى والمحاولة مرة أخرى'; @override - String get live_barrage_error_network => - 'الشبكة غير طبيعية، يرجى المحاولة لاحقاً'; + String get live_barrage_error_network => 'الشبكة غير طبيعية، يرجى المحاولة لاحقاً'; @override - String get live_barrage_error_sensitive_word => - 'تم اكتشاف كلمات حساسة، يرجى التعديل والمحاولة مرة أخرى'; + String get live_barrage_error_sensitive_word => 'تم اكتشاف كلمات حساسة، يرجى التعديل والمحاولة مرة أخرى'; @override - String get live_error_connection_notexit => - 'الغرفة المدعو للاتصال بها غير موجودة'; + String get live_error_connection_notexit => 'الغرفة المدعو للاتصال بها غير موجودة'; @override - String get live_error_connection_retry => - 'خطأ داخلي، يُنصح بالمحاولة مرة أخرى.'; + String get live_error_connection_retry => 'خطأ داخلي، يُنصح بالمحاولة مرة أخرى.'; @override - String get live_error_room_mismatch => - 'أنواع الغرف غير متطابقة؛ يمكن بدء الاتصالات فقط في غرف من نفس النوع.'; + String get live_error_room_mismatch => 'أنواع الغرف غير متطابقة؛ يمكن بدء الاتصالات فقط في غرف من نفس النوع.'; @override String get livelist_loading => 'جاري التحميل...'; @@ -219,19 +202,16 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get livelist_viewed_audience_count => 'شاهده xxx شخص'; @override - String get livelist_exit_float_window_tip => - 'البث المباشر قيد التقدم. يرجى المحاولة مرة أخرى لاحقاً.'; + String get livelist_exit_float_window_tip => 'البث المباشر قيد التقدم. يرجى المحاولة مرة أخرى لاحقاً.'; @override String get livelist_click_enter_room => 'انقر لدخول غرفة البث'; @override - String get livestreamcore_battle_error_conflict => - 'المذيع في منافسة ولا يمكنه بدء منافسة أخرى'; + String get livestreamcore_battle_error_conflict => 'المذيع في منافسة ولا يمكنه بدء منافسة أخرى'; @override - String get livestreamcore_battle_error_other => - 'خطأ آخر، لا يمكن بدء المنافسة'; + String get livestreamcore_battle_error_other => 'خطأ آخر، لا يمكن بدء المنافسة'; @override String get common_audience_end_link_tips => @@ -253,8 +233,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_stream_privacy_status => 'وضع البث:'; @override - String get common_voiceroom_kicked_out_of_seat => - 'تم طردك من المقعد بواسطة صاحب الغرفة'; + String get common_voiceroom_kicked_out_of_seat => 'تم طردك من المقعد بواسطة صاحب الغرفة'; @override String get common_like => 'الإعجابات'; @@ -263,20 +242,16 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_apply_link_mic => 'التقدم لربط الميكروفون'; @override - String get common_client_error_already_in_other_room => - 'المستخدم موجود بالفعل في غرفة أخرى'; + String get common_client_error_already_in_other_room => 'المستخدم موجود بالفعل في غرفة أخرى'; @override - String get common_client_error_camera_device_empty => - 'لا يوجد جهاز كاميرا حالياً، يرجى إدخال جهاز كاميرا'; + String get common_client_error_camera_device_empty => 'لا يوجد جهاز كاميرا حالياً، يرجى إدخال جهاز كاميرا'; @override - String get common_client_error_camera_not_authorized => - 'الكاميرا ليس لديها إذن النظام، تحقق من إذن النظام'; + String get common_client_error_camera_not_authorized => 'الكاميرا ليس لديها إذن النظام، تحقق من إذن النظام'; @override - String get common_client_error_camera_occupied => - 'الكاميرا مشغولة، تحقق مما إذا كانت عملية أخرى تستخدم الكاميرا'; + String get common_client_error_camera_occupied => 'الكاميرا مشغولة، تحقق مما إذا كانت عملية أخرى تستخدم الكاميرا'; @override String get common_client_error_camera_start_fail => @@ -291,39 +266,32 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { 'صاحب الغرفة لا يدعم مغادرة الغرفة، يمكنه فقط إغلاق الغرفة'; @override - String get common_client_error_failed => - 'الخادم يواجه مشكلة مؤقتة، يرجى المحاولة لاحقاً'; + String get common_client_error_failed => 'الخادم يواجه مشكلة مؤقتة، يرجى المحاولة لاحقاً'; @override - String get common_client_error_freq_limit => - 'تم تحديد معدل الطلبات، يرجى المحاولة لاحقاً'; + String get common_client_error_freq_limit => 'تم تحديد معدل الطلبات، يرجى المحاولة لاحقاً'; @override String get common_client_error_get_screen_sharing_target_failed => 'فشل الحصول على مصدر مشاركة الشاشة، تحقق من أذونات تسجيل الشاشة'; @override - String get common_client_error_invalid_parameter => - 'تمرير معلمات غير قانونية عند استدعاء API، تحقق من صحة المعلمات'; + String get common_client_error_invalid_parameter => 'تمرير معلمات غير قانونية عند استدعاء API، تحقق من صحة المعلمات'; @override - String get common_client_error_max_seat_count_limit => - 'الحد الأقصى للمقاعد يتجاوز حد الباقة'; + String get common_client_error_max_seat_count_limit => 'الحد الأقصى للمقاعد يتجاوز حد الباقة'; @override - String get common_client_error_microphone_device_empty => - 'لا يوجد جهاز ميكروفون حالياً'; + String get common_client_error_microphone_device_empty => 'لا يوجد جهاز ميكروفون حالياً'; @override - String get common_client_error_microphone_not_authorized => - 'الميكروفون ليس لديه إذن النظام'; + String get common_client_error_microphone_not_authorized => 'الميكروفون ليس لديه إذن النظام'; @override String get common_client_error_microphone_occupied => 'الميكروفون مشغول'; @override - String get common_client_error_microphone_start_fail => - 'مشكلة في النظام، فشل فتح الميكروفون'; + String get common_client_error_microphone_start_fail => 'مشكلة في النظام، فشل فتح الميكروفون'; @override String get common_client_error_open_camera_need_permission_from_admin => @@ -338,8 +306,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { 'يجب التقدم لصاحب الغرفة أو المسؤول لفتح الميكروفون'; @override - String get common_client_error_open_microphone_need_seat_unlock => - 'صوت المقعد الحالي مقفل'; + String get common_client_error_open_microphone_need_seat_unlock => 'صوت المقعد الحالي مقفل'; @override String get common_client_error_open_screen_share_need_permission_from_admin => @@ -350,98 +317,80 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { 'فيديو موضع الميكروفون الحالي مقفل ويحتاج لفتح القفل من صاحب الغرفة'; @override - String get common_client_error_operation_invalid_before_enter_room => - 'يمكن استخدام هذه الميزة فقط بعد دخول الغرفة'; + String get common_client_error_operation_invalid_before_enter_room => 'يمكن استخدام هذه الميزة فقط بعد دخول الغرفة'; @override String get common_client_error_operation_not_supported_in_current_room_type => 'هذه العملية غير مدعومة في نوع الغرفة الحالي'; @override - String get common_client_error_permission_denied => - 'فشل الحصول على الإذن، تحقق من تفعيل أذونات الجهاز'; + String get common_client_error_permission_denied => 'فشل الحصول على الإذن، تحقق من تفعيل أذونات الجهاز'; @override String get common_client_error_repeat_operation => 'عملية مكررة'; @override - String get common_client_error_request_id_invalid => - 'معرف طلب الإشارة غير صالح أو تمت معالجته'; + String get common_client_error_request_id_invalid => 'معرف طلب الإشارة غير صالح أو تمت معالجته'; @override String get common_client_error_request_id_repeat => 'تكرار طلب الإشارة'; @override - String get common_client_error_request_no_permission => - 'لا يوجد إذن لطلب الإشارة'; + String get common_client_error_request_no_permission => 'لا يوجد إذن لطلب الإشارة'; @override - String get common_client_error_require_payment => - 'تتطلب هذه الميزة باقة إضافية'; + String get common_client_error_require_payment => 'تتطلب هذه الميزة باقة إضافية'; @override - String get common_client_error_room_id_invalid => - 'معرف الغرفة المخصص غير قانوني'; + String get common_client_error_room_id_invalid => 'معرف الغرفة المخصص غير قانوني'; @override String get common_client_error_room_name_invalid => 'اسم الغرفة غير قانوني'; @override - String get common_client_error_room_not_support_preloading => - 'الغرفة الحالية لا تدعم التحميل المسبق'; + String get common_client_error_room_not_support_preloading => 'الغرفة الحالية لا تدعم التحميل المسبق'; @override - String get common_client_error_sdk_app_id_not_found => - 'لم يتم العثور على SDKAppID'; + String get common_client_error_sdk_app_id_not_found => 'لم يتم العثور على SDKAppID'; @override - String get common_client_error_sdk_not_initialized => - 'لم يتم تسجيل الدخول، يرجى استدعاء API تسجيل الدخول'; + String get common_client_error_sdk_not_initialized => 'لم يتم تسجيل الدخول، يرجى استدعاء API تسجيل الدخول'; @override - String get common_client_error_seat_index_not_exist => - 'رقم المقعد التسلسلي غير موجود'; + String get common_client_error_seat_index_not_exist => 'رقم المقعد التسلسلي غير موجود'; @override - String get common_client_error_send_message_disabled_for_all => - 'تم كتم صوت جميع الأعضاء في الغرفة الحالية'; + String get common_client_error_send_message_disabled_for_all => 'تم كتم صوت جميع الأعضاء في الغرفة الحالية'; @override - String get common_client_error_send_message_disabled_for_current => - 'تم كتم صوتك في الغرفة الحالية'; + String get common_client_error_send_message_disabled_for_current => 'تم كتم صوتك في الغرفة الحالية'; @override - String get common_client_error_start_screen_sharing_failed => - 'فشل تفعيل مشاركة الشاشة'; + String get common_client_error_start_screen_sharing_failed => 'فشل تفعيل مشاركة الشاشة'; @override String get common_client_error_success => 'تمت العملية بنجاح'; @override - String get common_client_error_user_need_admin_permission => - 'مطلوب إذن صاحب الغرفة أو المسؤول'; + String get common_client_error_user_need_admin_permission => 'مطلوب إذن صاحب الغرفة أو المسؤول'; @override - String get common_client_error_user_need_owner_permission => - 'مطلوب إذن صاحب الغرفة'; + String get common_client_error_user_need_owner_permission => 'مطلوب إذن صاحب الغرفة'; @override String get common_client_error_user_not_exist => 'المستخدم غير موجود'; @override - String get common_server_error_gift_ability_not_enabled => - 'خدمة الهدايا غير مفعلة بعد'; + String get common_server_error_gift_ability_not_enabled => 'خدمة الهدايا غير مفعلة بعد'; @override String get common_server_error_gift_not_exist => 'الهدية غير موجودة'; @override - String get common_server_error_gift_server_pre_verification_failed => - 'فشل التحقق المسبق من خادم الهدايا'; + String get common_server_error_gift_server_pre_verification_failed => 'فشل التحقق المسبق من خادم الهدايا'; @override - String get common_server_error_call_in_progress => - 'فشلت عملية الجهاز أثناء المكالمة'; + String get common_server_error_call_in_progress => 'فشلت عملية الجهاز أثناء المكالمة'; @override String get common_ear_return_volume => 'مستوى صوت سماعة الأذن'; @@ -450,8 +399,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_fan_count => 'المتابعون'; @override - String get live_barrage_warning_not_empty => - 'لا يمكن أن يكون الإدخال فارغاً!'; + String get live_barrage_warning_not_empty => 'لا يمكن أن يكون الإدخال فارغاً!'; @override String get live_clarity => 'الوضوح'; @@ -568,8 +516,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_hang_up => 'إنهاء المكالمة'; @override - String get common_kick_user_confirm_message => - 'هل أنت متأكد أنك تريد إزالة xxx؟'; + String get common_kick_user_confirm_message => 'هل أنت متأكد أنك تريد إزالة xxx؟'; @override String get common_link_mic_manager => 'إدارة الربط'; @@ -656,24 +603,19 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_go_live => 'ابدأ البث'; @override - String get common_server_error_already_on_the_mic => - 'موجود بالفعل على المقعد'; + String get common_server_error_already_on_the_mic => 'موجود بالفعل على المقعد'; @override - String get common_server_error_already_on_the_mic_queue => - 'موجود بالفعل في قائمة انتظار المقاعد'; + String get common_server_error_already_on_the_mic_queue => 'موجود بالفعل في قائمة انتظار المقاعد'; @override - String get common_server_error_battle_does_not_exist_or_has_ended => - 'المنافسة غير موجودة أو انتهت'; + String get common_server_error_battle_does_not_exist_or_has_ended => 'المنافسة غير موجودة أو انتهت'; @override - String get common_server_error_battle_session_has_ended => - 'انتهت جلسة المنافسة'; + String get common_server_error_battle_session_has_ended => 'انتهت جلسة المنافسة'; @override - String get common_server_error_connection_does_not_exist => - 'الاتصال الحالي غير موجود أو انتهى'; + String get common_server_error_connection_does_not_exist => 'الاتصال الحالي غير موجود أو انتهى'; @override String get common_server_error_creating_battles_too_frequently => @@ -684,40 +626,33 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { 'إنشاء الاتصالات بشكل متكرر جداً. انتظر لحظة وحاول مرة أخرى'; @override - String get common_server_error_creating_rooms_exceeds_the_frequency_limit => - 'تجاوز إنشاء الغرف حد التردد'; + String get common_server_error_creating_rooms_exceeds_the_frequency_limit => 'تجاوز إنشاء الغرف حد التردد'; @override String get common_server_error_exceeds_the_upper_limit => 'تجاوز الحد الأعلى'; @override - String - get common_server_error_has_exceeded_the_limit_in_connection_or_battle => - 'تجاوز رقم الغرفة الحد في الاتصال أو المنافسة'; + String get common_server_error_has_exceeded_the_limit_in_connection_or_battle => + 'تجاوز رقم الغرفة الحد في الاتصال أو المنافسة'; @override - String get common_server_error_in_other_battle => - 'الغرفة موجودة بالفعل في منافسة أخرى'; + String get common_server_error_in_other_battle => 'الغرفة موجودة بالفعل في منافسة أخرى'; @override - String get common_server_error_insufficient_operation_permissions => - 'لا يمكنك حالياً تنفيذ هذه العملية'; + String get common_server_error_insufficient_operation_permissions => 'لا يمكنك حالياً تنفيذ هذه العملية'; @override String get common_server_error_invalid_room_type => 'نوع غرفة غير صالح'; @override - String get common_server_error_is_connecting_with_other_rooms => - 'الغرفة الحالية متصلة بغرف أخرى'; + String get common_server_error_is_connecting_with_other_rooms => 'الغرفة الحالية متصلة بغرف أخرى'; @override - String - get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => - 'لا يُسمح بإلغاء المنافسة للغرفة في المنافسة'; + String get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => + 'لا يُسمح بإلغاء المنافسة للغرفة في المنافسة'; @override - String get common_server_error_metadata_no_valid_keys => - 'لا توجد مفاتيح صالحة عند حذف البيانات الوصفية'; + String get common_server_error_metadata_no_valid_keys => 'لا توجد مفاتيح صالحة عند حذف البيانات الوصفية'; @override String get common_server_error_metadata_number_of_keys_exceeds_the_limit => @@ -728,32 +663,27 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { 'حجم القيمة في البيانات الوصفية للغرفة يتجاوز الحد الأقصى للبايت'; @override - String - get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => - 'حجم المفتاح في البيانات الوصفية للغرفة يتجاوز الحد الأقصى للبايت'; + String get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => + 'حجم المفتاح في البيانات الوصفية للغرفة يتجاوز الحد الأقصى للبايت'; @override String get common_server_error_metadata_total_size_exceeds_the_limit => 'الحجم الإجمالي لجميع القيم في البيانات الوصفية للغرفة يتجاوز الحد الأقصى للبايت'; @override - String get common_server_error_mic_seat_is_locked => - 'المقعد مقفل. يمكنك تجربة مقعد آخر'; + String get common_server_error_mic_seat_is_locked => 'المقعد مقفل. يمكنك تجربة مقعد آخر'; @override - String get common_server_error_no_payment_information => - 'لا توجد معلومات دفع، تحتاج لشراء باقة'; + String get common_server_error_no_payment_information => 'لا توجد معلومات دفع، تحتاج لشراء باقة'; @override - String get common_server_error_no_rooms_in_the_battle_is_valid => - 'لا توجد غرفة صالحة في المنافسة'; + String get common_server_error_no_rooms_in_the_battle_is_valid => 'لا توجد غرفة صالحة في المنافسة'; @override String get common_server_error_not_a_room_member => 'ليس عضواً في الغرفة'; @override - String get common_server_error_not_on_the_mic_queue => - 'ليس في قائمة انتظار المقاعد'; + String get common_server_error_not_on_the_mic_queue => 'ليس في قائمة انتظار المقاعد'; @override String get common_server_error_not_on_the_mic_seat => 'ليس على المقعد'; @@ -765,36 +695,28 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_server_error_param_illegal => 'المعلمة غير قانونية'; @override - String get common_server_error_requires_password => - 'الغرفة الحالية تتطلب كلمة مرور للدخول'; + String get common_server_error_requires_password => 'الغرفة الحالية تتطلب كلمة مرور للدخول'; @override - String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => - 'عدد المسؤولين يتجاوز الحد الأعلى'; + String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => 'عدد المسؤولين يتجاوز الحد الأعلى'; @override - String get common_server_error_room_does_not_exist => - 'الغرفة غير موجودة أو تم حلها'; + String get common_server_error_room_does_not_exist => 'الغرفة غير موجودة أو تم حلها'; @override - String get common_server_error_room_does_not_support_mic_ability => - 'الغرفة لا تدعم ميزة المقاعد'; + String get common_server_error_room_does_not_support_mic_ability => 'الغرفة لا تدعم ميزة المقاعد'; @override - String get common_server_error_room_entry_password_error => - 'خطأ في كلمة مرور دخول الغرفة'; + String get common_server_error_room_entry_password_error => 'خطأ في كلمة مرور دخول الغرفة'; @override - String get common_server_error_room_id_exists => - 'معرف الغرفة موجود بالفعل. يرجى اختيار معرف آخر'; + String get common_server_error_room_id_exists => 'معرف الغرفة موجود بالفعل. يرجى اختيار معرف آخر'; @override - String get common_server_error_room_id_has_been_occupied_by_chat => - 'معرف الغرفة مشغول بواسطة الدردشة'; + String get common_server_error_room_id_has_been_occupied_by_chat => 'معرف الغرفة مشغول بواسطة الدردشة'; @override - String get common_server_error_room_id_has_been_used => - 'تم استخدام معرف الغرفة، والمشغل هو صاحب الغرفة'; + String get common_server_error_room_id_has_been_used => 'تم استخدام معرف الغرفة، والمشغل هو صاحب الغرفة'; @override String get common_server_error_room_is_full => 'الغرفة ممتلئة'; @@ -803,51 +725,43 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_server_error_room_is_in_connection => 'الغرفة متصلة بالفعل'; @override - String get common_server_error_seat_is_already_occupied => - 'المقعد الحالي مشغول بالفعل'; + String get common_server_error_seat_is_already_occupied => 'المقعد الحالي مشغول بالفعل'; @override String get common_server_error_signal_request_conflict => 'تعارض طلب الإشارة'; @override - String get common_server_error_system_internal_error => - 'خطأ داخلي في الخادم، يرجى إعادة المحاولة'; + String get common_server_error_system_internal_error => 'خطأ داخلي في الخادم، يرجى إعادة المحاولة'; @override - String get common_server_error_tag_quantity_exceeds_upper_limit => - 'عدد العلامات يتجاوز الحد الأعلى'; + String get common_server_error_tag_quantity_exceeds_upper_limit => 'عدد العلامات يتجاوز الحد الأعلى'; @override - String get common_server_error_the_room_is_not_in_the_battle => - 'الغرفة ليست في المنافسة'; + String get common_server_error_the_room_is_not_in_the_battle => 'الغرفة ليست في المنافسة'; @override - String get common_server_error_the_seat_list_is_empty => - 'قائمة المقاعد فارغة'; + String get common_server_error_the_seat_list_is_empty => 'قائمة المقاعد فارغة'; @override - String get common_server_error_the_seats_are_all_taken => - 'جميع المقاعد مشغولة.'; + String get common_server_error_the_seats_are_all_taken => 'جميع المقاعد مشغولة.'; @override - String get common_server_error_there_is_a_pending_battle_request => - 'يوجد طلب منافسة معلق لهذه الغرفة'; + String get common_server_error_there_is_a_pending_battle_request => 'يوجد طلب منافسة معلق لهذه الغرفة'; @override - String get common_server_error_there_is_a_pending_connection_request => - 'يوجد طلب اتصال معلق لهذه الغرفة'; + String get common_server_error_there_is_a_pending_connection_request => 'يوجد طلب اتصال معلق لهذه الغرفة'; @override - String get common_server_error_this_member_has_been_banned => - 'تم حظر هذا العضو'; + String get common_server_error_this_member_has_been_banned => 'تم حظر هذا العضو'; @override - String get common_server_error_this_member_has_been_muted => - 'تم كتم صوت هذا العضو'; + String get common_server_error_this_member_has_been_muted => 'تم كتم صوت هذا العضو'; @override - String get common_server_error_user_is_already_on_the_mic_seat => - 'المستخدم موجود بالفعل على المقعد'; + String get common_server_error_user_is_already_on_the_mic_seat => 'المستخدم موجود بالفعل على المقعد'; + + @override + String get common_server_error_im_sensitive_words_ban => 'تحتوي الرسالة أو الوثيقة على محتوى حساس ويُحظر إرسالها.'; @override String get common_set_as_background => 'تعيين كخلفية'; @@ -895,8 +809,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_text_link_mic_video => 'التقدم لربط الفيديو'; @override - String get common_tips_apply_link_mic => - 'سيتم تطبيق تأثير الشاشة تلقائياً بعد الاتصال'; + String get common_tips_apply_link_mic => 'سيتم تطبيق تأثير الشاشة تلقائياً بعد الاتصال'; @override String get common_title_link_mic_selector => 'اختر وضع الربط'; @@ -905,8 +818,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_title_link_video_settings => 'ضبط شاشة ربط الفيديو'; @override - String get common_toast_apply_link_mic => - 'لقد قدمت طلب ربط الميكروفون، يرجى انتظار موافقة المذيع'; + String get common_toast_apply_link_mic => 'لقد قدمت طلب ربط الميكروفون، يرجى انتظار موافقة المذيع'; @override String get common_unfollow_anchor => 'إلغاء المتابعة'; @@ -937,8 +849,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { 'بعد التفعيل، سيدخل التطبيق تلقائياً وضع صورة داخل صورة عند الانتقال للخلفية'; @override - String get common_voiceroom_empty_view => - 'لا يوجد مستخدمون في المقعد، اذهب للدعوة'; + String get common_voiceroom_empty_view => 'لا يوجد مستخدمون في المقعد، اذهب للدعوة'; @override String get common_voiceroom_invite => 'دعوة'; @@ -1025,12 +936,10 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_video_capture_closed => 'تم تعطيل التقاط الفيديو'; @override - String get common_audio_tips_proper_volume => - 'مستوى الصوت المناسب يضمن تجربة مشاهدة جيدة'; + String get common_audio_tips_proper_volume => 'مستوى الصوت المناسب يضمن تجربة مشاهدة جيدة'; @override - String get common_audio_tips_regular_checks => - 'الفحوصات المنتظمة تضمن تجربة مشاهدة جيدة'; + String get common_audio_tips_regular_checks => 'الفحوصات المنتظمة تضمن تجربة مشاهدة جيدة'; @override String get common_network_switch_tips => 'تجنب التبديل المتكرر للشبكة'; @@ -1172,8 +1081,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get seat_invite_battle => 'دعوة للمنافسة'; @override - String get seat_repeat_invite_tips => - 'دعوة الآخرين، يمكنك سحب الدعوة وإرسال دعوة جديدة'; + String get seat_repeat_invite_tips => 'دعوة الآخرين، يمكنك سحب الدعوة وإرسال دعوة جديدة'; @override String get seat_end_Battle => 'إنهاء المنافسة'; @@ -1206,12 +1114,10 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_remove => 'إزالة'; @override - String get common_host_kick_user_after_connect => - 'تم الاتصال بنجاح. تمت إزالة المستخدمين خارج أول 6 مقاعد.'; + String get common_host_kick_user_after_connect => 'تم الاتصال بنجاح. تمت إزالة المستخدمين خارج أول 6 مقاعد.'; @override - String get common_audience_kicked_re_apply => - 'يعرض الاتصال أول 6 مقاعد فقط. تمت إزالتك.'; + String get common_audience_kicked_re_apply => 'يعرض الاتصال أول 6 مقاعد فقط. تمت إزالتك.'; @override String get common_game_live => 'بث مباشر للألعاب'; @@ -1220,8 +1126,7 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_live_game => 'بث مباشر للعبة'; @override - String get common_end_live_by_server => - 'تم إيقاف البث المباشر بسبب انتهاكات للوائح.'; + String get common_end_live_by_server => 'تم إيقاف البث المباشر بسبب انتهاكات للوائح.'; @override String get common_go_to_enable => 'الذهاب للتفعيل'; @@ -1230,6 +1135,20 @@ class LiveKitLocalizationsAr extends LiveKitLocalizations { String get common_live_screen => 'بث الشاشة مباشرةً'; @override - String get common_select_app_to_live => - 'بث مباشر للعبةاختر التطبيق وابدأ البث المباشر'; + String get common_select_app_to_live => 'بث مباشر للعبةاختر التطبيق وابدأ البث المباشر'; + + @override + String get common_violation_alert_toast => 'المحتوى الحالي قد ينتهك إرشادات المنصة، يرجى الالتزام بالقواعد'; + + @override + String get live_song_unknown_artist => 'غير معروف'; + + @override + String get live_anchor_manager_set_featured_host => 'تعيين كمضيف رئيسي'; + + @override + String get live_anchor_manager_revoke_featured_host => 'إلغاء المضيف الرئيسي'; + + @override + String get common_enter_anchor_live_room => 'الذهاب إلى غرفة البث'; } diff --git a/live/livekit/lib/common/language/gen/livekit_localizations_en.dart b/live/livekit/lib/common/language/gen/livekit_localizations_en.dart index 6e676e82..0fa8010b 100644 --- a/live/livekit/lib/common/language/gen/livekit_localizations_en.dart +++ b/live/livekit/lib/common/language/gen/livekit_localizations_en.dart @@ -9,8 +9,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { LiveKitLocalizationsEn([String locale = 'en']) : super(locale); @override - String get common_disconnect_guest_tips => - 'Are you sure you want to disconnect xxx?'; + String get common_disconnect_guest_tips => 'Are you sure you want to disconnect xxx?'; @override String get common_anchor_battle => 'Battle'; @@ -30,8 +29,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Are you sure you want to end the battle? The current result will be the final result after the end'; @override - String get common_battle_invitation_timeout => - 'Battle request has been timeout'; + String get common_battle_invitation_timeout => 'Battle request has been timeout'; @override String get common_battle_invitee_reject => 'xxx rejected battle'; @@ -40,8 +38,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_request_rejected => 'xxx rejected'; @override - String get common_battle_inviter_cancel => - 'xxx canceled battle, please try to initiate it again'; + String get common_battle_inviter_cancel => 'xxx canceled battle, please try to initiate it again'; @override String get common_battle_inviting => 'xxx invite you to battle together'; @@ -56,8 +53,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_battle_wait_stop => 'Cancel'; @override - String get common_connect_conflict => - 'The room you are invited to connect to is connected to another room.'; + String get common_connect_conflict => 'The room you are invited to connect to is connected to another room.'; @override String get common_connect_error => 'Other errors, cannot connect.'; @@ -66,8 +62,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_connect_inviting => 'Waiting'; @override - String get common_connect_inviting_append => - 'xxx invite you to host together'; + String get common_connect_inviting_append => 'xxx invite you to host together'; @override String get common_connect_invitation_timeout => 'Invitation has timed out'; @@ -79,8 +74,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_connection_list_title => 'Connecting Streamers(xxx)'; @override - String get common_connection_room_full => - 'The number of co-hosting has exceeded the maximum limit.'; + String get common_connection_room_full => 'The number of co-hosting has exceeded the maximum limit.'; @override String get common_cover => 'Cover'; @@ -95,8 +89,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_disable_video => 'Disable Video'; @override - String get common_disconnect_tips => - 'Are you sure you want to disconnect from other streamers?'; + String get common_disconnect_tips => 'Are you sure you want to disconnect from other streamers?'; @override String get common_enable_audio => 'Enable Audio'; @@ -115,15 +108,13 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'You are currently co-hosting with other streamers. Would you like to [End Co-host] or [End Live] ?'; @override - String get common_end_pk_tips => - 'You are currently in PK mode. Would you like to [End PK] or [End Live] ?'; + String get common_end_pk_tips => 'You are currently in PK mode. Would you like to [End PK] or [End Live] ?'; @override String get common_kick_out_of_room => 'Remove'; @override - String get common_kicked_out_of_room_by_owner => - 'You have been kicked out of the room'; + String get common_kicked_out_of_room_by_owner => 'You have been kicked out of the room'; @override String get common_more => 'More'; @@ -150,12 +141,10 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_link_mic_up_title => 'Current Mic'; @override - String get common_send_message_disabled => - 'You have been muted in the current room'; + String get common_send_message_disabled => 'You have been muted in the current room'; @override - String get common_send_message_enable => - 'You have been unmuted in the current room'; + String get common_send_message_enable => 'You have been unmuted in the current room'; @override String get common_setting_done => 'Done'; @@ -170,8 +159,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_stop_video => 'Stop Video'; @override - String get common_terminate_room_connection_message => - 'Are you sure you want to disconnect?'; + String get common_terminate_room_connection_message => 'Are you sure you want to disconnect?'; @override String get common_un_mute_audio_by_master => 'The anchor has unmuted you'; @@ -189,24 +177,19 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_link_host => 'Host'; @override - String get live_barrage_error_content_is_long => - 'The content is too long, please reduce the content and try again'; + String get live_barrage_error_content_is_long => 'The content is too long, please reduce the content and try again'; @override - String get live_barrage_error_network => - 'The network is abnormal, please try again later'; + String get live_barrage_error_network => 'The network is abnormal, please try again later'; @override - String get live_barrage_error_sensitive_word => - 'Sensitive words are detected, please modify it and try again'; + String get live_barrage_error_sensitive_word => 'Sensitive words are detected, please modify it and try again'; @override - String get live_error_connection_notexit => - 'The room you are invited to connect to does not exist'; + String get live_error_connection_notexit => 'The room you are invited to connect to does not exist'; @override - String get live_error_connection_retry => - 'Internal error, it is recommended to try again.'; + String get live_error_connection_retry => 'Internal error, it is recommended to try again.'; @override String get live_error_room_mismatch => @@ -222,19 +205,16 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get livelist_viewed_audience_count => 'xxx people viewed'; @override - String get livelist_exit_float_window_tip => - 'Live streaming in progress. Please try again later.'; + String get livelist_exit_float_window_tip => 'Live streaming in progress. Please try again later.'; @override String get livelist_click_enter_room => 'Click to enter the live room'; @override - String get livestreamcore_battle_error_conflict => - 'The anchor is in the battle and cannot initiate the battle'; + String get livestreamcore_battle_error_conflict => 'The anchor is in the battle and cannot initiate the battle'; @override - String get livestreamcore_battle_error_other => - 'The other error, cannot initiate the battle'; + String get livestreamcore_battle_error_other => 'The other error, cannot initiate the battle'; @override String get common_audience_end_link_tips => @@ -256,8 +236,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_stream_privacy_status => 'Live Mode:'; @override - String get common_voiceroom_kicked_out_of_seat => - 'Kicked out of seat by room owner'; + String get common_voiceroom_kicked_out_of_seat => 'Kicked out of seat by room owner'; @override String get common_like => 'Likes'; @@ -278,8 +257,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Camera has No System Authorization, Check System Authorization'; @override - String get common_client_error_camera_occupied => - 'Camera is Occupied, Check if Other Process is Using Camera'; + String get common_client_error_camera_occupied => 'Camera is Occupied, Check if Other Process is Using Camera'; @override String get common_client_error_camera_start_fail => @@ -294,12 +272,10 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Room Owner Does Not Support Leaving the Room, Room Owner Can Only Close the Room'; @override - String get common_client_error_failed => - 'Server is having a hiccup, please try again later'; + String get common_client_error_failed => 'Server is having a hiccup, please try again later'; @override - String get common_client_error_freq_limit => - 'Request Rate Limited, Please Try Again Later'; + String get common_client_error_freq_limit => 'Request Rate Limited, Please Try Again Later'; @override String get common_client_error_get_screen_sharing_target_failed => @@ -310,12 +286,10 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Passing illegal parameters when calling API, check if the parameters are legal'; @override - String get common_client_error_max_seat_count_limit => - 'Maximum Seat Exceeds Package Quantity Limit'; + String get common_client_error_max_seat_count_limit => 'Maximum Seat Exceeds Package Quantity Limit'; @override - String get common_client_error_microphone_device_empty => - 'No Mic Device Currently'; + String get common_client_error_microphone_device_empty => 'No Mic Device Currently'; @override String get common_client_error_microphone_not_authorized => @@ -341,8 +315,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Need to Apply to Room Owner or Administrator to Open Mic'; @override - String get common_client_error_open_microphone_need_seat_unlock => - 'Current Seat Audio is Locked'; + String get common_client_error_open_microphone_need_seat_unlock => 'Current Seat Audio is Locked'; @override String get common_client_error_open_screen_share_need_permission_from_admin => @@ -368,12 +341,10 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_client_error_repeat_operation => 'Repeat Operation'; @override - String get common_client_error_request_id_invalid => - 'Signaling Request ID is Invalid or Has Been Processed'; + String get common_client_error_request_id_invalid => 'Signaling Request ID is Invalid or Has Been Processed'; @override - String get common_client_error_request_id_repeat => - 'Signal request repetition'; + String get common_client_error_request_id_repeat => 'Signal request repetition'; @override String get common_client_error_request_no_permission => @@ -392,28 +363,23 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Illegal Room Name, Maximum 30 Bytes, Must Be UTF-8 Encoding if Contains Chinese Characters'; @override - String get common_client_error_room_not_support_preloading => - 'The current room does not support preloading'; + String get common_client_error_room_not_support_preloading => 'The current room does not support preloading'; @override String get common_client_error_sdk_app_id_not_found => 'Not Found SDKAppID, Please Confirm Application Info in TRTC Console'; @override - String get common_client_error_sdk_not_initialized => - 'Not Logged In, Please Call Login API'; + String get common_client_error_sdk_not_initialized => 'Not Logged In, Please Call Login API'; @override - String get common_client_error_seat_index_not_exist => - 'Seat Serial Number Does Not Exist'; + String get common_client_error_seat_index_not_exist => 'Seat Serial Number Does Not Exist'; @override - String get common_client_error_send_message_disabled_for_all => - 'All Members Muted in the Current Room'; + String get common_client_error_send_message_disabled_for_all => 'All Members Muted in the Current Room'; @override - String get common_client_error_send_message_disabled_for_current => - 'You Have Been Muted in the Current Room'; + String get common_client_error_send_message_disabled_for_current => 'You Have Been Muted in the Current Room'; @override String get common_client_error_start_screen_sharing_failed => @@ -427,8 +393,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Room Owner or Administrator Permission Required for Operation'; @override - String get common_client_error_user_need_owner_permission => - 'Room Owner Permission Required for Operation'; + String get common_client_error_user_need_owner_permission => 'Room Owner Permission Required for Operation'; @override String get common_client_error_user_not_exist => 'User is not exist'; @@ -445,8 +410,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Gift server pre-verification failed please check console configuration'; @override - String get common_server_error_call_in_progress => - 'The device operation failed while in a call'; + String get common_server_error_call_in_progress => 'The device operation failed while in a call'; @override String get common_ear_return_volume => 'Ear Monitor Volume'; @@ -572,8 +536,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_hang_up => 'Hang Up'; @override - String get common_kick_user_confirm_message => - 'Are you sure you want to remove xxx?'; + String get common_kick_user_confirm_message => 'Are you sure you want to remove xxx?'; @override String get common_link_mic_manager => 'Link Management'; @@ -663,20 +626,16 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_server_error_already_on_the_mic => 'Already on the seat'; @override - String get common_server_error_already_on_the_mic_queue => - 'Already on the seat queue'; + String get common_server_error_already_on_the_mic_queue => 'Already on the seat queue'; @override - String get common_server_error_battle_does_not_exist_or_has_ended => - 'The battle does not exist or has ended'; + String get common_server_error_battle_does_not_exist_or_has_ended => 'The battle does not exist or has ended'; @override - String get common_server_error_battle_session_has_ended => - 'The battle session has ended'; + String get common_server_error_battle_session_has_ended => 'The battle session has ended'; @override - String get common_server_error_connection_does_not_exist => - 'The current connection does not exist or has ended'; + String get common_server_error_connection_does_not_exist => 'The current connection does not exist or has ended'; @override String get common_server_error_creating_battles_too_frequently => @@ -695,13 +654,11 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'Exceeds the upper limit, for example, the number of microphone seats, the number of PK match rooms, etc., exceeds the payment limit'; @override - String - get common_server_error_has_exceeded_the_limit_in_connection_or_battle => - 'The room number has exceeded the limit in connection or battle'; + String get common_server_error_has_exceeded_the_limit_in_connection_or_battle => + 'The room number has exceeded the limit in connection or battle'; @override - String get common_server_error_in_other_battle => - 'The room is already in other battle'; + String get common_server_error_in_other_battle => 'The room is already in other battle'; @override String get common_server_error_insufficient_operation_permissions => @@ -711,17 +668,14 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_server_error_invalid_room_type => 'Invalid room type'; @override - String get common_server_error_is_connecting_with_other_rooms => - 'The current room is connecting with other rooms'; + String get common_server_error_is_connecting_with_other_rooms => 'The current room is connecting with other rooms'; @override - String - get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => - 'It\'s not allowed to cancel battle for room in battle'; + String get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => + 'It\'s not allowed to cancel battle for room in battle'; @override - String get common_server_error_metadata_no_valid_keys => - 'There is no valid keys when delete metadata'; + String get common_server_error_metadata_no_valid_keys => 'There is no valid keys when delete metadata'; @override String get common_server_error_metadata_number_of_keys_exceeds_the_limit => @@ -740,38 +694,33 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'The total size of all value in the room\'s Metadata exceeds the maximum byte limit'; @override - String get common_server_error_mic_seat_is_locked => - 'The seat is locked. You can try another seat'; + String get common_server_error_mic_seat_is_locked => 'The seat is locked. You can try another seat'; @override String get common_server_error_no_payment_information => 'No payment information, you need to purchase a package in the console'; @override - String get common_server_error_no_rooms_in_the_battle_is_valid => - 'None of the rooms in the battle is valid'; + String get common_server_error_no_rooms_in_the_battle_is_valid => 'None of the rooms in the battle is valid'; @override String get common_server_error_not_a_room_member => 'Not a room member'; @override - String get common_server_error_not_on_the_mic_queue => - 'Not on the seat queue'; + String get common_server_error_not_on_the_mic_queue => 'Not on the seat queue'; @override String get common_server_error_not_on_the_mic_seat => 'Not on the seat'; @override - String get common_server_error_not_started_yet => - 'The battle has not started yet'; + String get common_server_error_not_started_yet => 'The battle has not started yet'; @override String get common_server_error_param_illegal => 'The parameter is illegal. Check whether the request is correct according to the error description'; @override - String get common_server_error_requires_password => - 'The current room requires a password for entry'; + String get common_server_error_requires_password => 'The current room requires a password for entry'; @override String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => @@ -782,16 +731,13 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'The room does not exist, or it once existed but has now been dissolved'; @override - String get common_server_error_room_does_not_support_mic_ability => - 'The room does not support seat ability'; + String get common_server_error_room_does_not_support_mic_ability => 'The room does not support seat ability'; @override - String get common_server_error_room_entry_password_error => - 'Room Entry Password Error'; + String get common_server_error_room_entry_password_error => 'Room Entry Password Error'; @override - String get common_server_error_room_id_exists => - 'The room ID already exists. Please select another room ID'; + String get common_server_error_room_id_exists => 'The room ID already exists. Please select another room ID'; @override String get common_server_error_room_id_has_been_occupied_by_chat => @@ -805,56 +751,48 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_server_error_room_is_full => 'The room is full'; @override - String get common_server_error_room_is_in_connection => - 'The room is already in connection'; + String get common_server_error_room_is_in_connection => 'The room is already in connection'; @override - String get common_server_error_seat_is_already_occupied => - 'The current seat is already occupied'; + String get common_server_error_seat_is_already_occupied => 'The current seat is already occupied'; @override - String get common_server_error_signal_request_conflict => - 'Signal request conflict'; + String get common_server_error_signal_request_conflict => 'Signal request conflict'; @override - String get common_server_error_system_internal_error => - 'Server internal error, please retry'; + String get common_server_error_system_internal_error => 'Server internal error, please retry'; @override - String get common_server_error_tag_quantity_exceeds_upper_limit => - 'Tag quantity Exceeds Upper limit'; + String get common_server_error_tag_quantity_exceeds_upper_limit => 'Tag quantity Exceeds Upper limit'; @override - String get common_server_error_the_room_is_not_in_the_battle => - 'The room isn‘t in the battle'; + String get common_server_error_the_room_is_not_in_the_battle => 'The room isn‘t in the battle'; @override - String get common_server_error_the_seat_list_is_empty => - 'The seat list is empty'; + String get common_server_error_the_seat_list_is_empty => 'The seat list is empty'; @override - String get common_server_error_the_seats_are_all_taken => - 'The seats are all taken.'; + String get common_server_error_the_seats_are_all_taken => 'The seats are all taken.'; @override - String get common_server_error_there_is_a_pending_battle_request => - 'There is a pending battle request for this room'; + String get common_server_error_there_is_a_pending_battle_request => 'There is a pending battle request for this room'; @override String get common_server_error_there_is_a_pending_connection_request => 'There is a pending connection request for this room'; @override - String get common_server_error_this_member_has_been_banned => - 'This member has been banned'; + String get common_server_error_this_member_has_been_banned => 'This member has been banned'; @override - String get common_server_error_this_member_has_been_muted => - 'This member has been muted'; + String get common_server_error_this_member_has_been_muted => 'This member has been muted'; @override - String get common_server_error_user_is_already_on_the_mic_seat => - 'The user is already on the seat'; + String get common_server_error_user_is_already_on_the_mic_seat => 'The user is already on the seat'; + + @override + String get common_server_error_im_sensitive_words_ban => + 'The message or document contains sensitive content and is prohibited from being sent.'; @override String get common_set_as_background => 'Set as background'; @@ -887,8 +825,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_stream_privacy_status_privacy => 'Privacy'; @override - String get common_text_cancel_link_mic_apply => - 'Cancel application for link mic'; + String get common_text_cancel_link_mic_apply => 'Cancel application for link mic'; @override String get common_text_close_link_mic => 'End Link'; @@ -903,8 +840,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_text_link_mic_video => 'Apply for video link'; @override - String get common_tips_apply_link_mic => - 'The screen effect will automatically take effect after connecting'; + String get common_tips_apply_link_mic => 'The screen effect will automatically take effect after connecting'; @override String get common_title_link_mic_selector => 'Choose Link Mode'; @@ -945,19 +881,16 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { 'After enabling, the app will automatically enter picture-in-picture mode when moved to the background'; @override - String get common_voiceroom_empty_view => - 'No users in the seat, go to invite'; + String get common_voiceroom_empty_view => 'No users in the seat, go to invite'; @override String get common_voiceroom_invite => 'Invite'; @override - String get common_voiceroom_invite_seat_canceled => - 'Seat invitation has been canceled'; + String get common_voiceroom_invite_seat_canceled => 'Seat invitation has been canceled'; @override - String get common_voiceroom_invite_seat_rejected => - 'Seat invitation has been rejected'; + String get common_voiceroom_invite_seat_rejected => 'Seat invitation has been rejected'; @override String get common_voiceroom_invite_seat_timeout => 'Seat invitation timeout'; @@ -972,19 +905,16 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_voiceroom_need_agree => 'Require owner\'s consent to speak'; @override - String get common_voiceroom_receive_seat_invitation => - 'xxx invites you to take seat'; + String get common_voiceroom_receive_seat_invitation => 'xxx invites you to take seat'; @override String get common_voiceroom_take_seat => 'Take Seat'; @override - String get common_voiceroom_take_seat_rejected => - 'Take seat application has been rejected'; + String get common_voiceroom_take_seat_rejected => 'Take seat application has been rejected'; @override - String get common_voiceroom_take_seat_timeout => - 'Take seat application timeout'; + String get common_voiceroom_take_seat_timeout => 'Take seat application timeout'; @override String get common_voiceroom_unlock => 'Unlock Seat'; @@ -1038,12 +968,10 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_video_capture_closed => 'Video capture disabled'; @override - String get common_audio_tips_proper_volume => - 'Proper volume ensures good viewing experience'; + String get common_audio_tips_proper_volume => 'Proper volume ensures good viewing experience'; @override - String get common_audio_tips_regular_checks => - 'Regular checks ensure good viewing experience'; + String get common_audio_tips_regular_checks => 'Regular checks ensure good viewing experience'; @override String get common_network_switch_tips => 'Avoid frequent network switching'; @@ -1185,8 +1113,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get seat_invite_battle => 'Invite Battle'; @override - String get seat_repeat_invite_tips => - 'Inviting others you can withdraw the invitation and send a new one'; + String get seat_repeat_invite_tips => 'Inviting others you can withdraw the invitation and send a new one'; @override String get seat_end_Battle => 'End Battle'; @@ -1233,8 +1160,7 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { String get common_live_game => 'Live Game'; @override - String get common_end_live_by_server => - 'The live stream was shut down due to violations of regulations.'; + String get common_end_live_by_server => 'The live stream was shut down due to violations of regulations.'; @override String get common_go_to_enable => 'Go to Enable'; @@ -1244,4 +1170,19 @@ class LiveKitLocalizationsEn extends LiveKitLocalizations { @override String get common_select_app_to_live => 'Select App and Start Live'; + + @override + String get common_violation_alert_toast => 'Current content may violate platform guidelines, please follow the rules'; + + @override + String get live_song_unknown_artist => 'Unknown'; + + @override + String get live_anchor_manager_set_featured_host => 'Set as Featured'; + + @override + String get live_anchor_manager_revoke_featured_host => 'Revoke Featured'; + + @override + String get common_enter_anchor_live_room => 'Enter Live Room'; } diff --git a/live/livekit/lib/common/language/gen/livekit_localizations_ja.dart b/live/livekit/lib/common/language/gen/livekit_localizations_ja.dart index ee8731cb..46c7931c 100644 --- a/live/livekit/lib/common/language/gen/livekit_localizations_ja.dart +++ b/live/livekit/lib/common/language/gen/livekit_localizations_ja.dart @@ -15,8 +15,7 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_anchor_battle => 'アンカーPK'; @override - String get common_anchor_end_link_tips => - '現在、接続されている州では、「ライブブロードキャストルームを閉じる」必要がありますか?'; + String get common_anchor_end_link_tips => '現在、接続されている州では、「ライブブロードキャストルームを閉じる」必要がありますか?'; @override String get common_battle_connecting => '接続'; @@ -103,12 +102,10 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_end_connect => '切断された'; @override - String get common_end_connection_tips => - '現在、接続されている州では、「ライブブロードキャストルームを閉じる」または「閉じる」必要がありますか?'; + String get common_end_connection_tips => '現在、接続されている州では、「ライブブロードキャストルームを閉じる」または「閉じる」必要がありますか?'; @override - String get common_end_pk_tips => - '現在、PKステータスでは、「PKを終了」する必要がありますか、それとも「ライブブロードキャストルームを閉じる」必要がありますか?'; + String get common_end_pk_tips => '現在、PKステータスでは、「PKを終了」する必要がありますか、それとも「ライブブロードキャストルームを閉じる」必要がありますか?'; @override String get common_kick_out_of_room => '取り除く'; @@ -177,15 +174,13 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_link_host => 'アンカーでさえ'; @override - String get live_barrage_error_content_is_long => - 'コンテンツが長すぎるので、試す前にコンテンツを減らしてください'; + String get live_barrage_error_content_is_long => 'コンテンツが長すぎるので、試す前にコンテンツを減らしてください'; @override String get live_barrage_error_network => 'ネットワークの異常、後でもう一度やり直してください'; @override - String get live_barrage_error_sensitive_word => - '敏感な単語が検出されました。それらを変更して、もう一度やり直してください'; + String get live_barrage_error_sensitive_word => '敏感な単語が検出されました。それらを変更して、もう一度やり直してください'; @override String get live_error_connection_notexit => '招待状リンクが存在しない部屋'; @@ -218,8 +213,7 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get livestreamcore_battle_error_other => 'PKを開始できない他のエラー'; @override - String get common_audience_end_link_tips => - '現在、接続されているマイク状態で、「接続されたマイクを切断する」または「ライブブロードキャストルームを終了する」必要がありますか?'; + String get common_audience_end_link_tips => '現在、接続されているマイク状態で、「接続されたマイクを切断する」または「ライブブロードキャストルームを終了する」必要がありますか?'; @override String get common_exit_live => 'ライブブロードキャストルームを出ます'; @@ -250,24 +244,19 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { '現在のユーザーは別の部屋にいます。単一のルームエンジンインスタンスは、ユーザーのみが1つの部屋に入るのをサポートします。別の部屋に入りたい場合は、最初にチェックアウトするか、新しいルームエンジンインスタンスを使用してください。'; @override - String get common_client_error_camera_device_empty => - '現在、カメラデバイスはありません。カメラデバイスを挿入して問題を解決してください'; + String get common_client_error_camera_device_empty => '現在、カメラデバイスはありません。カメラデバイスを挿入して問題を解決してください'; @override - String get common_client_error_camera_not_authorized => - 'カメラにはシステム承認がありません。システム認証を確認してください'; + String get common_client_error_camera_not_authorized => 'カメラにはシステム承認がありません。システム認証を確認してください'; @override - String get common_client_error_camera_occupied => - 'カメラが占有されています。カメラを使用して他のプロセスがあるかどうかを確認してください'; + String get common_client_error_camera_occupied => 'カメラが占有されています。カメラを使用して他のプロセスがあるかどうかを確認してください'; @override - String get common_client_error_camera_start_fail => - 'システムの問題は、カメラをオンにすることができませんでした。カメラデバイスが正常かどうかを確認してください'; + String get common_client_error_camera_start_fail => 'システムの問題は、カメラをオンにすることができませんでした。カメラデバイスが正常かどうかを確認してください'; @override - String get common_client_error_connection_connecting => - '接続するために招待されている部屋は、すでに招待リストに載っているか、接続されています。'; + String get common_client_error_connection_connecting => '接続するために招待されている部屋は、すでに招待リストに載っているか、接続されています。'; @override String get common_client_error_exit_not_supported_for_room_owner => @@ -280,62 +269,51 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_client_error_freq_limit => 'リクエストは制限されています。後でもう一度やり直してください'; @override - String get common_client_error_get_screen_sharing_target_failed => - '画面共有ソース(画面とウィンドウ)を取得し、画面録音の許可を確認できませんでした'; + String get common_client_error_get_screen_sharing_target_failed => '画面共有ソース(画面とウィンドウ)を取得し、画面録音の許可を確認できませんでした'; @override - String get common_client_error_invalid_parameter => - 'APIを呼び出すと、合格したパラメーターは違法です。パラメーターが合法かどうかを確認してください。'; + String get common_client_error_invalid_parameter => 'APIを呼び出すと、合格したパラメーターは違法です。パラメーターが合法かどうかを確認してください。'; @override - String get common_client_error_max_seat_count_limit => - 'クロワッサンの最大位置は、パッケージパッケージの数の制限を超えています'; + String get common_client_error_max_seat_count_limit => 'クロワッサンの最大位置は、パッケージパッケージの数の制限を超えています'; @override String get common_client_error_microphone_device_empty => '現在、マイクデバイスはありません'; @override - String get common_client_error_microphone_not_authorized => - 'マイクにはシステム認証がありません。システム認証を確認してください'; + String get common_client_error_microphone_not_authorized => 'マイクにはシステム認証がありません。システム認証を確認してください'; @override String get common_client_error_microphone_occupied => 'マイクが占有されました'; @override - String get common_client_error_microphone_start_fail => - 'システムの問題は、マイクをオンにすることができませんでした。マイクデバイスが正常かどうかを確認してください'; + String get common_client_error_microphone_start_fail => 'システムの問題は、マイクをオンにすることができませんでした。マイクデバイスが正常かどうかを確認してください'; @override - String get common_client_error_open_camera_need_permission_from_admin => - '住宅所有者または管理者に申請してカメラをオンにする必要があります'; + String get common_client_error_open_camera_need_permission_from_admin => '住宅所有者または管理者に申請してカメラをオンにする必要があります'; @override String get common_client_error_open_camera_need_seat_unlock => '現在のマイクのビデオはロックされており、住宅所有者はカメラをオンにする前にマイクのロックを解除する必要があります。'; @override - String get common_client_error_open_microphone_need_permission_from_admin => - '住宅所有者または管理者に申請し、マイクをオンにする必要があります'; + String get common_client_error_open_microphone_need_permission_from_admin => '住宅所有者または管理者に申請し、マイクをオンにする必要があります'; @override - String get common_client_error_open_microphone_need_seat_unlock => - '現在のマイクオーディオがロックされています'; + String get common_client_error_open_microphone_need_seat_unlock => '現在のマイクオーディオがロックされています'; @override - String get common_client_error_open_screen_share_need_permission_from_admin => - '住宅所有者または管理者に申請し、画面を開いて共有する必要があります'; + String get common_client_error_open_screen_share_need_permission_from_admin => '住宅所有者または管理者に申請し、画面を開いて共有する必要があります'; @override String get common_client_error_open_screen_share_need_seat_unlock => '現在のマイクビデオがロックされています。住宅所有者は、画面を開いて共有する前にマイクのロックを解除する必要があります。'; @override - String get common_client_error_operation_invalid_before_enter_room => - 'この機能を使用する前に部屋に入る必要があります'; + String get common_client_error_operation_invalid_before_enter_room => 'この機能を使用する前に部屋に入る必要があります'; @override - String get common_client_error_operation_not_supported_in_current_room_type => - 'この操作は現在の部屋の種類ではサポートされていません'; + String get common_client_error_operation_not_supported_in_current_room_type => 'この操作は現在の部屋の種類ではサポートされていません'; @override String get common_client_error_permission_denied => @@ -351,32 +329,28 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_client_error_request_id_repeat => 'シグナリングリクエストを繰り返します'; @override - String get common_client_error_request_no_permission => - 'シグナリングリクエストには、自分で開始されない招待状のキャンセルなど、権限がありません'; + String get common_client_error_request_no_permission => 'シグナリングリクエストには、自分で開始されない招待状のキャンセルなど、権限がありません'; @override String get common_client_error_require_payment => 'この関数では、追加のパッケージを有効にする必要があります。 Tencent CloudビデオキューブSDKコンソールで必要に応じて、対応するパッケージを有効にしてください'; @override - String get common_client_error_room_id_invalid => - '部屋IDの作成は違法であり、カスタムIDは印刷可能なASCII文字(0x20-0x7E)で、最大48バイトである必要があります'; + String get common_client_error_room_id_invalid => '部屋IDの作成は違法であり、カスタムIDは印刷可能なASCII文字(0x20-0x7E)で、最大48バイトである必要があります'; @override String get common_client_error_room_name_invalid => '部屋の名前は違法で、名前の長さは最大30バイトです。中国語が含まれている場合は、キャラクターエンコードがUTF-8でなければなりません'; @override - String get common_client_error_room_not_support_preloading => - 'プリロードは現在の部屋ではサポートされていません'; + String get common_client_error_room_not_support_preloading => 'プリロードは現在の部屋ではサポートされていません'; @override String get common_client_error_sdk_app_id_not_found => 'sdkappidは見つかりませんでした、Tencent CloudビデオキューブSDKコンソールのアプリケーション情報を確認してください'; @override - String get common_client_error_sdk_not_initialized => - 'ログインしていないので、ログインインターフェイスに電話してください'; + String get common_client_error_sdk_not_initialized => 'ログインしていないので、ログインインターフェイスに電話してください'; @override String get common_client_error_seat_index_not_exist => '位置番号は存在しません'; @@ -385,37 +359,31 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_client_error_send_message_disabled_for_all => '部屋は禁止されています'; @override - String get common_client_error_send_message_disabled_for_current => - 'あなたは現在の部屋で禁止されています'; + String get common_client_error_send_message_disabled_for_current => 'あなたは現在の部屋で禁止されています'; @override - String get common_client_error_start_screen_sharing_failed => - '画面共有を有効にすることができませんでした、部屋の誰かが画面を共有しているかどうかを確認しました'; + String get common_client_error_start_screen_sharing_failed => '画面共有を有効にすることができませんでした、部屋の誰かが画面を共有しているかどうかを確認しました'; @override String get common_client_error_success => '操作は成功しました'; @override - String get common_client_error_user_need_admin_permission => - '運営するには、住宅所有者または管理者からの許可が必要です'; + String get common_client_error_user_need_admin_permission => '運営するには、住宅所有者または管理者からの許可が必要です'; @override - String get common_client_error_user_need_owner_permission => - '運営するには、住宅所有者からの許可が必要です'; + String get common_client_error_user_need_owner_permission => '運営するには、住宅所有者からの許可が必要です'; @override String get common_client_error_user_not_exist => 'ユーザーは存在しません'; @override - String get common_server_error_gift_ability_not_enabled => - 'ギフト機能サービスはまだ有効になっていません。パッケージバージョンを確認してください'; + String get common_server_error_gift_ability_not_enabled => 'ギフト機能サービスはまだ有効になっていません。パッケージバージョンを確認してください'; @override String get common_server_error_gift_not_exist => '贈り物は存在しません'; @override - String get common_server_error_gift_server_pre_verification_failed => - 'ギフトサーバーの事前確認が失敗しました。コンソールの構成を確認してください'; + String get common_server_error_gift_server_pre_verification_failed => 'ギフトサーバーの事前確認が失敗しました。コンソールの構成を確認してください'; @override String get common_server_error_call_in_progress => '呼び出しでは、デバイスの操作に失敗しました'; @@ -637,8 +605,7 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_server_error_already_on_the_mic_queue => 'すでにフェリー状態にあります'; @override - String get common_server_error_battle_does_not_exist_or_has_ended => - '戦いは存在しないか、終わっていません'; + String get common_server_error_battle_does_not_exist_or_has_ended => '戦いは存在しないか、終わっていません'; @override String get common_server_error_battle_session_has_ended => '戦いは終わりました'; @@ -647,25 +614,20 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_server_error_connection_does_not_exist => '現在の接続は存在しません'; @override - String get common_server_error_creating_battles_too_frequently => - '頻繁に戦闘を短時間で開始し、試してみる前に少し待ってください'; + String get common_server_error_creating_battles_too_frequently => '頻繁に戦闘を短時間で開始し、試してみる前に少し待ってください'; @override - String get common_server_error_creating_connections_too_frequent => - '短期間で頻繁に接続しすぎて、試してみる前にしばらく待ちます'; + String get common_server_error_creating_connections_too_frequent => '短期間で頻繁に接続しすぎて、試してみる前にしばらく待ちます'; @override String get common_server_error_creating_rooms_exceeds_the_frequency_limit => '頻度は制限を超えます。たとえば、部屋の作成は制限を超え、同じ部屋IDは1秒に1回しか作成できません'; @override - String get common_server_error_exceeds_the_upper_limit => - '支払い制限は、クロークの最大数、PKセッションの数などを超えて支払い制限を超えます。'; + String get common_server_error_exceeds_the_upper_limit => '支払い制限は、クロークの最大数、PKセッションの数などを超えて支払い制限を超えます。'; @override - String - get common_server_error_has_exceeded_the_limit_in_connection_or_battle => - '接続部屋とバトルルームの最大数を超えました'; + String get common_server_error_has_exceeded_the_limit_in_connection_or_battle => '接続部屋とバトルルームの最大数を超えました'; @override String get common_server_error_in_other_battle => 'この部屋は他の戦闘セッションにあります'; @@ -678,46 +640,35 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_server_error_invalid_room_type => '無効な部屋の種類'; @override - String get common_server_error_is_connecting_with_other_rooms => - '現在の部屋は他の部屋に接続されています'; + String get common_server_error_is_connecting_with_other_rooms => '現在の部屋は他の部屋に接続されています'; @override - String - get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => - '部屋は戦いにあります'; + String get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => '部屋は戦いにあります'; @override - String get common_server_error_metadata_no_valid_keys => - 'ルームメタデータを削除するとき、削除されたキーはどれも存在しません'; + String get common_server_error_metadata_no_valid_keys => 'ルームメタデータを削除するとき、削除されたキーはどれも存在しません'; @override - String get common_server_error_metadata_number_of_keys_exceeds_the_limit => - 'ルームメタデータのキーの数は上限を超えています'; + String get common_server_error_metadata_number_of_keys_exceeds_the_limit => 'ルームメタデータのキーの数は上限を超えています'; @override - String get common_server_error_metadata_size_of_value_exceeds_the_limit => - 'ルームメタデータの単一キーに対応するVALは、バイト制限の最大数を超えています'; + String get common_server_error_metadata_size_of_value_exceeds_the_limit => 'ルームメタデータの単一キーに対応するVALは、バイト制限の最大数を超えています'; @override - String - get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => - 'ルームメタデータのキーサイズは、バイト制限の最大数を超えています'; + String get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => + 'ルームメタデータのキーサイズは、バイト制限の最大数を超えています'; @override - String get common_server_error_metadata_total_size_exceeds_the_limit => - 'ルームメタデータのすべてのキーに対応するバルの合計は、バイト制限の最大数を超えています'; + String get common_server_error_metadata_total_size_exceeds_the_limit => 'ルームメタデータのすべてのキーに対応するバルの合計は、バイト制限の最大数を超えています'; @override - String get common_server_error_mic_seat_is_locked => - 'マットレスの位置がロックされています、マットレスの位置を変更してみることができます'; + String get common_server_error_mic_seat_is_locked => 'マットレスの位置がロックされています、マットレスの位置を変更してみることができます'; @override - String get common_server_error_no_payment_information => - '有料情報はありません、コンソールでパッケージを購入する必要があります'; + String get common_server_error_no_payment_information => '有料情報はありません、コンソールでパッケージを購入する必要があります'; @override - String get common_server_error_no_rooms_in_the_battle_is_valid => - '開始された戦いには有効な部屋はありません'; + String get common_server_error_no_rooms_in_the_battle_is_valid => '開始された戦いには有効な部屋はありません'; @override String get common_server_error_not_a_room_member => '部屋以外のメンバー'; @@ -732,40 +683,32 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_server_error_not_started_yet => '戦いはまだ始まっていません'; @override - String get common_server_error_param_illegal => - '違法なパラメーターをご覧ください。エラーの説明に従ってリクエストが正しいかどうかを確認してください。'; + String get common_server_error_param_illegal => '違法なパラメーターをご覧ください。エラーの説明に従ってリクエストが正しいかどうかを確認してください。'; @override - String get common_server_error_requires_password => - '現在の部屋では、入力するにはパスワードが必要です'; + String get common_server_error_requires_password => '現在の部屋では、入力するにはパスワードが必要です'; @override - String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => - '管理者の数は制限を超えています'; + String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => '管理者の数は制限を超えています'; @override - String get common_server_error_room_does_not_exist => - '部屋は存在せず、存在していませんが、解散しました'; + String get common_server_error_room_does_not_exist => '部屋は存在せず、存在していませんが、解散しました'; @override - String get common_server_error_room_does_not_support_mic_ability => - 'この部屋はMSをサポートしていません'; + String get common_server_error_room_does_not_support_mic_ability => 'この部屋はMSをサポートしていません'; @override - String get common_server_error_room_entry_password_error => - '部屋に入るためのパスワードが誤っていません'; + String get common_server_error_room_entry_password_error => '部屋に入るためのパスワードが誤っていません'; @override - String get common_server_error_room_id_exists => - '部屋IDが使用されました、別の部屋IDを選択してください'; + String get common_server_error_room_id_exists => '部屋IDが使用されました、別の部屋IDを選択してください'; @override String get common_server_error_room_id_has_been_occupied_by_chat => '部屋IDはIMによって占有されています。別の部屋IDで使用するか、最初にIMインターフェイスを介してグループを解散させることができます。'; @override - String get common_server_error_room_id_has_been_used => - '部屋IDが使用されており、オペレーターは住宅所有者であり、直接使用できます'; + String get common_server_error_room_id_has_been_used => '部屋IDが使用されており、オペレーターは住宅所有者であり、直接使用できます'; @override String get common_server_error_room_is_full => '部屋のメンバーはいっぱいです'; @@ -780,16 +723,13 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_server_error_signal_request_conflict => 'シグナリングリクエストの競合'; @override - String get common_server_error_system_internal_error => - '内部サーバーエラー、もう一度やり直してください'; + String get common_server_error_system_internal_error => '内部サーバーエラー、もう一度やり直してください'; @override - String get common_server_error_tag_quantity_exceeds_upper_limit => - 'タグの数は上限を超えています'; + String get common_server_error_tag_quantity_exceeds_upper_limit => 'タグの数は上限を超えています'; @override - String get common_server_error_the_room_is_not_in_the_battle => - '部屋はもはや戦いにありません'; + String get common_server_error_the_room_is_not_in_the_battle => '部屋はもはや戦いにありません'; @override String get common_server_error_the_seat_list_is_empty => '接続されたマイクのリストは空です'; @@ -798,24 +738,22 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_server_error_the_seats_are_all_taken => '座席はいっぱいです'; @override - String get common_server_error_there_is_a_pending_battle_request => - 'この部屋には、保留中の戦闘リクエストがあります'; + String get common_server_error_there_is_a_pending_battle_request => 'この部屋には、保留中の戦闘リクエストがあります'; @override - String get common_server_error_there_is_a_pending_connection_request => - 'この部屋には保留中の接続リクエストがあります'; + String get common_server_error_there_is_a_pending_connection_request => 'この部屋には保留中の接続リクエストがあります'; @override - String get common_server_error_this_member_has_been_banned => - 'このメンバーは禁止されています'; + String get common_server_error_this_member_has_been_banned => 'このメンバーは禁止されています'; @override - String get common_server_error_this_member_has_been_muted => - 'このメンバーは禁止されています'; + String get common_server_error_this_member_has_been_muted => 'このメンバーは禁止されています'; @override - String get common_server_error_user_is_already_on_the_mic_seat => - 'マイクにはすでにユーザーがいます'; + String get common_server_error_user_is_already_on_the_mic_seat => 'マイクにはすでにユーザーがいます'; + + @override + String get common_server_error_im_sensitive_words_ban => 'このメッセージまたは文書には機密情報が含まれているため、送信は禁止されています。'; @override String get common_set_as_background => '背景として設定します'; @@ -872,8 +810,7 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_title_link_video_settings => 'ビデオ接続画面を調整します'; @override - String get common_toast_apply_link_mic => - 'あなたは電気通信申請書を提出しました、アンカーが同意するのを待ってください'; + String get common_toast_apply_link_mic => 'あなたは電気通信申請書を提出しました、アンカーが同意するのを待ってください'; @override String get common_unfollow_anchor => '解除してください'; @@ -900,8 +837,7 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_pip_toggle => 'ピクチャーインピクチャーの切り替え'; @override - String get common_pip_toggle_description => - 'オンにすると、アプリがバックグラウンドに移動したときに自動的にピクチャーインピクチャーモードに入ります'; + String get common_pip_toggle_description => 'オンにすると、アプリがバックグラウンドに移動したときに自動的にピクチャーインピクチャーモードに入ります'; @override String get common_voiceroom_empty_view => 'マイクにはユーザーがいません。'; @@ -910,8 +846,7 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_voiceroom_invite => '招待する'; @override - String get common_voiceroom_invite_seat_canceled => - 'Shangmaiへの招待状がキャンセルされました'; + String get common_voiceroom_invite_seat_canceled => 'Shangmaiへの招待状がキャンセルされました'; @override String get common_voiceroom_invite_seat_rejected => 'Shangmaiへの招待状が拒否されました'; @@ -1170,12 +1105,10 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { String get common_remove => '取り除く'; @override - String get common_host_kick_user_after_connect => - '接続に成功しました。先着6席以外のユーザーはマイクを降りました。'; + String get common_host_kick_user_after_connect => '接続に成功しました。先着6席以外のユーザーはマイクを降りました。'; @override - String get common_audience_kicked_re_apply => - '接続は先着6席のみ表示されます。あなたはマイクから降ろされました。'; + String get common_audience_kicked_re_apply => '接続は先着6席のみ表示されます。あなたはマイクから降ろされました。'; @override String get common_game_live => 'スマホゲーム実況'; @@ -1194,4 +1127,19 @@ class LiveKitLocalizationsJa extends LiveKitLocalizations { @override String get common_select_app_to_live => 'アプリを選択してライブを開始'; + + @override + String get common_violation_alert_toast => '現在のコンテンツにはプラットフォーム規約に違反するリスクがあります。ご注意ください'; + + @override + String get live_song_unknown_artist => '不明'; + + @override + String get live_anchor_manager_set_featured_host => 'メインホストに設定'; + + @override + String get live_anchor_manager_revoke_featured_host => 'メインホストを解除'; + + @override + String get common_enter_anchor_live_room => '配信ルームへ'; } diff --git a/live/livekit/lib/common/language/gen/livekit_localizations_zh.dart b/live/livekit/lib/common/language/gen/livekit_localizations_zh.dart index 1dbc1185..55a52ff6 100644 --- a/live/livekit/lib/common/language/gen/livekit_localizations_zh.dart +++ b/live/livekit/lib/common/language/gen/livekit_localizations_zh.dart @@ -244,8 +244,7 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { '当前用户已在别的房间内,单个 roomEngine 实例只支持用户进入一个房间,如果要进入不同的房间请先退房或者使用新的 roomEngine 实例'; @override - String get common_client_error_camera_device_empty => - '当前无摄像头设备,请插入摄像头设备解决该问题'; + String get common_client_error_camera_device_empty => '当前无摄像头设备,请插入摄像头设备解决该问题'; @override String get common_client_error_camera_not_authorized => '摄像头没有系统授权, 检查系统授权'; @@ -254,16 +253,13 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_client_error_camera_occupied => '摄像头被占用,检查是否有其他进程使用摄像头'; @override - String get common_client_error_camera_start_fail => - '系统问题,打开摄像头失败。检查摄像头设备是否正常'; + String get common_client_error_camera_start_fail => '系统问题,打开摄像头失败。检查摄像头设备是否正常'; @override - String get common_client_error_connection_connecting => - '被邀请连线的房间已在邀请列表或者已连线。'; + String get common_client_error_connection_connecting => '被邀请连线的房间已在邀请列表或者已连线。'; @override - String get common_client_error_exit_not_supported_for_room_owner => - '房主不支持退房操作,房主只能解散房间'; + String get common_client_error_exit_not_supported_for_room_owner => '房主不支持退房操作,房主只能解散房间'; @override String get common_client_error_failed => '服务器开小差啦,请稍后重试'; @@ -272,12 +268,10 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_client_error_freq_limit => '请求被限频,请稍后重试'; @override - String get common_client_error_get_screen_sharing_target_failed => - '获取屏幕分享源(屏幕和窗口)失败,检查屏幕录制权限'; + String get common_client_error_get_screen_sharing_target_failed => '获取屏幕分享源(屏幕和窗口)失败,检查屏幕录制权限'; @override - String get common_client_error_invalid_parameter => - '调用 API 时,传入的参数不合法,检查入参是否合法'; + String get common_client_error_invalid_parameter => '调用 API 时,传入的参数不合法,检查入参是否合法'; @override String get common_client_error_max_seat_count_limit => '最大麦位超出套餐包数量限制'; @@ -286,50 +280,40 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_client_error_microphone_device_empty => '当前无麦克风设备'; @override - String get common_client_error_microphone_not_authorized => - '麦克风没有系统授权,检查系统授权'; + String get common_client_error_microphone_not_authorized => '麦克风没有系统授权,检查系统授权'; @override String get common_client_error_microphone_occupied => '麦克风被占用'; @override - String get common_client_error_microphone_start_fail => - '系统问题,打开麦克风失败。检查麦克风设备是否正常'; + String get common_client_error_microphone_start_fail => '系统问题,打开麦克风失败。检查麦克风设备是否正常'; @override - String get common_client_error_open_camera_need_permission_from_admin => - '需要向房主或管理员申请后打开摄像头'; + String get common_client_error_open_camera_need_permission_from_admin => '需要向房主或管理员申请后打开摄像头'; @override - String get common_client_error_open_camera_need_seat_unlock => - '当前麦位视频被锁, 需要由房主解锁麦位后,才能打开摄像头'; + String get common_client_error_open_camera_need_seat_unlock => '当前麦位视频被锁, 需要由房主解锁麦位后,才能打开摄像头'; @override - String get common_client_error_open_microphone_need_permission_from_admin => - '需要向房主或管理员申请后打开麦克风'; + String get common_client_error_open_microphone_need_permission_from_admin => '需要向房主或管理员申请后打开麦克风'; @override String get common_client_error_open_microphone_need_seat_unlock => '当前麦位音频被锁'; @override - String get common_client_error_open_screen_share_need_permission_from_admin => - '需要向房主或管理员申请后打开屏幕分享'; + String get common_client_error_open_screen_share_need_permission_from_admin => '需要向房主或管理员申请后打开屏幕分享'; @override - String get common_client_error_open_screen_share_need_seat_unlock => - '当前麦位视频被锁, 需要由房主解锁麦位后,才能打开屏幕分享'; + String get common_client_error_open_screen_share_need_seat_unlock => '当前麦位视频被锁, 需要由房主解锁麦位后,才能打开屏幕分享'; @override - String get common_client_error_operation_invalid_before_enter_room => - '需要进房后才可使用此功能'; + String get common_client_error_operation_invalid_before_enter_room => '需要进房后才可使用此功能'; @override - String get common_client_error_operation_not_supported_in_current_room_type => - '当前房间类型下不支持该操作'; + String get common_client_error_operation_not_supported_in_current_room_type => '当前房间类型下不支持该操作'; @override - String get common_client_error_permission_denied => - '获取权限失败,当前未授权音/视频权限,请查看是否开启设备权限'; + String get common_client_error_permission_denied => '获取权限失败,当前未授权音/视频权限,请查看是否开启设备权限'; @override String get common_client_error_repeat_operation => '重复操作'; @@ -341,27 +325,22 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_client_error_request_id_repeat => '信令请求重复'; @override - String get common_client_error_request_no_permission => - '信令请求无权限,例如取消非自己发起的邀请'; + String get common_client_error_request_no_permission => '信令请求无权限,例如取消非自己发起的邀请'; @override - String get common_client_error_require_payment => - '该功能需要开通额外的套餐,请在 腾讯云视立方 SDK 控制台 按需开通对应套餐'; + String get common_client_error_require_payment => '该功能需要开通额外的套餐,请在 腾讯云视立方 SDK 控制台 按需开通对应套餐'; @override - String get common_client_error_room_id_invalid => - '创建房间 ID 非法,自定义 ID 必须为可打印 ASCII 字符(0x20-0x7e),最长48个字节'; + String get common_client_error_room_id_invalid => '创建房间 ID 非法,自定义 ID 必须为可打印 ASCII 字符(0x20-0x7e),最长48个字节'; @override - String get common_client_error_room_name_invalid => - '房间名称非法,名称最长30字节,字符编码必须是 UTF-8 ,如果包含中文'; + String get common_client_error_room_name_invalid => '房间名称非法,名称最长30字节,字符编码必须是 UTF-8 ,如果包含中文'; @override String get common_client_error_room_not_support_preloading => '当前房间不支持预加载'; @override - String get common_client_error_sdk_app_id_not_found => - '未找到 SDKAppID,请在 腾讯云视立方 SDK 控制台 确认应用信息'; + String get common_client_error_sdk_app_id_not_found => '未找到 SDKAppID,请在 腾讯云视立方 SDK 控制台 确认应用信息'; @override String get common_client_error_sdk_not_initialized => '未登录,请调用 Login 接口'; @@ -373,19 +352,16 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_client_error_send_message_disabled_for_all => '当前房间已开启全员禁言'; @override - String get common_client_error_send_message_disabled_for_current => - '当前房间内,您已被禁言'; + String get common_client_error_send_message_disabled_for_current => '当前房间内,您已被禁言'; @override - String get common_client_error_start_screen_sharing_failed => - '开启屏幕分享失败,检查房间内是否有人正在屏幕分享'; + String get common_client_error_start_screen_sharing_failed => '开启屏幕分享失败,检查房间内是否有人正在屏幕分享'; @override String get common_client_error_success => '操作成功'; @override - String get common_client_error_user_need_admin_permission => - '需要房主或者管理员权限才能操作'; + String get common_client_error_user_need_admin_permission => '需要房主或者管理员权限才能操作'; @override String get common_client_error_user_need_owner_permission => '需要房主权限才能操作'; @@ -394,15 +370,13 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_client_error_user_not_exist => '用户不存在'; @override - String get common_server_error_gift_ability_not_enabled => - '礼物功能服务暂未开启,请检查套餐包版本'; + String get common_server_error_gift_ability_not_enabled => '礼物功能服务暂未开启,请检查套餐包版本'; @override String get common_server_error_gift_not_exist => '礼物不存在'; @override - String get common_server_error_gift_server_pre_verification_failed => - '礼物服务器预验证失败,请检查控制台配置'; + String get common_server_error_gift_server_pre_verification_failed => '礼物服务器预验证失败,请检查控制台配置'; @override String get common_server_error_call_in_progress => '正在通话中,设备操作失败'; @@ -624,8 +598,7 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_server_error_already_on_the_mic_queue => '已经处于排麦状态'; @override - String get common_server_error_battle_does_not_exist_or_has_ended => - '该场次 battle 不存在或已结束'; + String get common_server_error_battle_does_not_exist_or_has_ended => '该场次 battle 不存在或已结束'; @override String get common_server_error_battle_session_has_ended => '该 battle 场次已经结束'; @@ -634,65 +607,50 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_server_error_connection_does_not_exist => '当前连线不存在或结束'; @override - String get common_server_error_creating_battles_too_frequently => - '短时间内频繁发起 battle, 稍等一会再试'; + String get common_server_error_creating_battles_too_frequently => '短时间内频繁发起 battle, 稍等一会再试'; @override - String get common_server_error_creating_connections_too_frequent => - '短时间内连线过于频繁,稍等一会再试'; + String get common_server_error_creating_connections_too_frequent => '短时间内连线过于频繁,稍等一会再试'; @override - String get common_server_error_creating_rooms_exceeds_the_frequency_limit => - '频率超过限制,例如创建房间超过频率超限,同一房间 ID, 1秒内只能创建一次'; + String get common_server_error_creating_rooms_exceeds_the_frequency_limit => '频率超过限制,例如创建房间超过频率超限,同一房间 ID, 1秒内只能创建一次'; @override - String get common_server_error_exceeds_the_upper_limit => - '超过付费上限,例如麦位数,pk场次房间数量等超过付费限制'; + String get common_server_error_exceeds_the_upper_limit => '超过付费上限,例如麦位数,pk场次房间数量等超过付费限制'; @override - String - get common_server_error_has_exceeded_the_limit_in_connection_or_battle => - '超过连线和 battle 房间数量上限'; + String get common_server_error_has_exceeded_the_limit_in_connection_or_battle => '超过连线和 battle 房间数量上限'; @override String get common_server_error_in_other_battle => '该房间处于其他的 battle 场次中'; @override - String get common_server_error_insufficient_operation_permissions => - '您当前无法执行此操作(可能是无权限,或受场景限制等原因)'; + String get common_server_error_insufficient_operation_permissions => '您当前无法执行此操作(可能是无权限,或受场景限制等原因)'; @override String get common_server_error_invalid_room_type => '无效的房间类型'; @override - String get common_server_error_is_connecting_with_other_rooms => - '当前房间与其他房间连线中'; + String get common_server_error_is_connecting_with_other_rooms => '当前房间与其他房间连线中'; @override - String - get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => - '该房间处于 battle 中'; + String get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => '该房间处于 battle 中'; @override - String get common_server_error_metadata_no_valid_keys => - '删除房间 meta 数据时候,被删除的 key 没有一个存在'; + String get common_server_error_metadata_no_valid_keys => '删除房间 meta 数据时候,被删除的 key 没有一个存在'; @override - String get common_server_error_metadata_number_of_keys_exceeds_the_limit => - '房间 meta 数据中的 key 数量超过上限'; + String get common_server_error_metadata_number_of_keys_exceeds_the_limit => '房间 meta 数据中的 key 数量超过上限'; @override - String get common_server_error_metadata_size_of_value_exceeds_the_limit => - '房间 meta 数据中单个 key 对应的 val 超过最大字节数限制'; + String get common_server_error_metadata_size_of_value_exceeds_the_limit => '房间 meta 数据中单个 key 对应的 val 超过最大字节数限制'; @override - String - get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => - '房间 meta 数据中的 key 大小超过了最大字节数限制'; + String get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => + '房间 meta 数据中的 key 大小超过了最大字节数限制'; @override - String get common_server_error_metadata_total_size_exceeds_the_limit => - '房间 meta数据中所有 key 对应的 val 总和超过最大字节数限制'; + String get common_server_error_metadata_total_size_exceeds_the_limit => '房间 meta数据中所有 key 对应的 val 总和超过最大字节数限制'; @override String get common_server_error_mic_seat_is_locked => '麦位已锁定,可以尝试换一个麦位'; @@ -701,8 +659,7 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_server_error_no_payment_information => '无付费信息,需在控制台购买套餐包'; @override - String get common_server_error_no_rooms_in_the_battle_is_valid => - '发起的 battle 里没有一个有效的房间'; + String get common_server_error_no_rooms_in_the_battle_is_valid => '发起的 battle 里没有一个有效的房间'; @override String get common_server_error_not_a_room_member => '非房间成员'; @@ -723,16 +680,13 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_server_error_requires_password => '当前房间需要密码才能进入'; @override - String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => - '管理员数量超过上限'; + String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => '管理员数量超过上限'; @override - String get common_server_error_room_does_not_exist => - '房间不存在,或者曾经存在过,但是目前已经被解散'; + String get common_server_error_room_does_not_exist => '房间不存在,或者曾经存在过,但是目前已经被解散'; @override - String get common_server_error_room_does_not_support_mic_ability => - '该房间不支持连麦'; + String get common_server_error_room_does_not_support_mic_ability => '该房间不支持连麦'; @override String get common_server_error_room_entry_password_error => '进房密码错误'; @@ -741,12 +695,10 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_server_error_room_id_exists => '房间ID 已被使用,请选择别的房间ID'; @override - String get common_server_error_room_id_has_been_occupied_by_chat => - '房间 ID 已被 IM 占用,可以换一个房间 ID 使用,或者先通过 IM 接口解散该群'; + String get common_server_error_room_id_has_been_occupied_by_chat => '房间 ID 已被 IM 占用,可以换一个房间 ID 使用,或者先通过 IM 接口解散该群'; @override - String get common_server_error_room_id_has_been_used => - '房间 ID 已被使用,并且操作者为房主,可以直接使用'; + String get common_server_error_room_id_has_been_used => '房间 ID 已被使用,并且操作者为房主,可以直接使用'; @override String get common_server_error_room_is_full => '房间成员已满'; @@ -767,8 +719,7 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_server_error_tag_quantity_exceeds_upper_limit => '标签数量超上限'; @override - String get common_server_error_the_room_is_not_in_the_battle => - '该房间已经不在 battle 中'; + String get common_server_error_the_room_is_not_in_the_battle => '该房间已经不在 battle 中'; @override String get common_server_error_the_seat_list_is_empty => '连麦列表为空'; @@ -777,12 +728,10 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_server_error_the_seats_are_all_taken => '麦位已满'; @override - String get common_server_error_there_is_a_pending_battle_request => - '该房间存在待处理的 battle 请求'; + String get common_server_error_there_is_a_pending_battle_request => '该房间存在待处理的 battle 请求'; @override - String get common_server_error_there_is_a_pending_connection_request => - '该房间存在待处理的连线请求'; + String get common_server_error_there_is_a_pending_connection_request => '该房间存在待处理的连线请求'; @override String get common_server_error_this_member_has_been_banned => '该成员已经被封禁'; @@ -793,6 +742,9 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { @override String get common_server_error_user_is_already_on_the_mic_seat => '已经有用户在麦位上'; + @override + String get common_server_error_im_sensitive_words_ban => '消息或者资料中文本存在敏感内容,禁止下发。'; + @override String get common_set_as_background => '设为背景'; @@ -1079,8 +1031,7 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get live_video_resolution_changed => '清晰度已切换至'; @override - String get common_template_601_ui_exception_toast => - '该布局模板在特定机型上会显示异常,建议更换其他布局模板'; + String get common_template_601_ui_exception_toast => '该布局模板在特定机型上会显示异常,建议更换其他布局模板'; @override String get seat_locked => '已锁定'; @@ -1143,12 +1094,10 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { String get common_remove => '移除'; @override - String get common_host_kick_user_after_connect => - '连线成功。前 6 个麦位以外的用户已被移除。如需互动,请重新邀请。'; + String get common_host_kick_user_after_connect => '连线成功。前 6 个麦位以外的用户已被移除。如需互动,请重新邀请。'; @override - String get common_audience_kicked_re_apply => - '连线仅显示前 6 个麦位。您已被移除,如有需要请重新申请上麦。'; + String get common_audience_kicked_re_apply => '连线仅显示前 6 个麦位。您已被移除,如有需要请重新申请上麦。'; @override String get common_game_live => '手游直播'; @@ -1167,6 +1116,21 @@ class LiveKitLocalizationsZh extends LiveKitLocalizations { @override String get common_select_app_to_live => '选择应用并开始直播'; + + @override + String get common_violation_alert_toast => '当前画面或内容存在违规风险,请注意平台规范'; + + @override + String get live_song_unknown_artist => '未知'; + + @override + String get live_anchor_manager_set_featured_host => '设为主咖'; + + @override + String get live_anchor_manager_revoke_featured_host => '取消主咖'; + + @override + String get common_enter_anchor_live_room => '去TA的直播间'; } /// The translations for Chinese, using the Han script (`zh_Hant`). @@ -1409,8 +1373,7 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { '當前用戶已在別的房間內,單個 roomEngine 實例只支持用戶進入一個房間,如果要進入不同的房間請先退房或者使用新的 roomEngine 實例'; @override - String get common_client_error_camera_device_empty => - '當前無攝像頭設備,請插入攝像頭設備解決該問題'; + String get common_client_error_camera_device_empty => '當前無攝像頭設備,請插入攝像頭設備解決該問題'; @override String get common_client_error_camera_not_authorized => '攝像頭沒有系統授權, 檢查系統授權'; @@ -1419,16 +1382,13 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_client_error_camera_occupied => '攝像頭被佔用,檢查是否有其他進程使用攝像頭'; @override - String get common_client_error_camera_start_fail => - '系統問題,打開攝像頭失敗。檢查攝像頭設備是否正常'; + String get common_client_error_camera_start_fail => '系統問題,打開攝像頭失敗。檢查攝像頭設備是否正常'; @override - String get common_client_error_connection_connecting => - '被邀請連線的房間已在邀請列表或者已連線。'; + String get common_client_error_connection_connecting => '被邀請連線的房間已在邀請列表或者已連線。'; @override - String get common_client_error_exit_not_supported_for_room_owner => - '房主不支持退房操作,房主只能解散房間'; + String get common_client_error_exit_not_supported_for_room_owner => '房主不支持退房操作,房主只能解散房間'; @override String get common_client_error_failed => '伺服器開小差啦,請稍後重試'; @@ -1437,12 +1397,10 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_client_error_freq_limit => '請求被限頻,請稍後重試'; @override - String get common_client_error_get_screen_sharing_target_failed => - '獲取屏幕分享源(屏幕和窗口)失敗,檢查屏幕錄製權限'; + String get common_client_error_get_screen_sharing_target_failed => '獲取屏幕分享源(屏幕和窗口)失敗,檢查屏幕錄製權限'; @override - String get common_client_error_invalid_parameter => - '調用 API 時,傳入的參數不合法,檢查入參是否合法'; + String get common_client_error_invalid_parameter => '調用 API 時,傳入的參數不合法,檢查入參是否合法'; @override String get common_client_error_max_seat_count_limit => '最大麥位超出套餐包數量限制'; @@ -1451,50 +1409,40 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_client_error_microphone_device_empty => '當前無麥克風設備'; @override - String get common_client_error_microphone_not_authorized => - '麥克風沒有系統授權,檢查系統授權'; + String get common_client_error_microphone_not_authorized => '麥克風沒有系統授權,檢查系統授權'; @override String get common_client_error_microphone_occupied => '麥克風被佔用'; @override - String get common_client_error_microphone_start_fail => - '系統問題,打開麥克風失敗。檢查麥克風設備是否正常'; + String get common_client_error_microphone_start_fail => '系統問題,打開麥克風失敗。檢查麥克風設備是否正常'; @override - String get common_client_error_open_camera_need_permission_from_admin => - '需要向房主或管理員申請後打開攝像頭'; + String get common_client_error_open_camera_need_permission_from_admin => '需要向房主或管理員申請後打開攝像頭'; @override - String get common_client_error_open_camera_need_seat_unlock => - '當前麥位視頻被鎖, 需要由房主解鎖麥位後,才能打開攝像頭'; + String get common_client_error_open_camera_need_seat_unlock => '當前麥位視頻被鎖, 需要由房主解鎖麥位後,才能打開攝像頭'; @override - String get common_client_error_open_microphone_need_permission_from_admin => - '需要向房主或管理員申請後打開麥克風'; + String get common_client_error_open_microphone_need_permission_from_admin => '需要向房主或管理員申請後打開麥克風'; @override String get common_client_error_open_microphone_need_seat_unlock => '當前麥位音頻被鎖'; @override - String get common_client_error_open_screen_share_need_permission_from_admin => - '需要向房主或管理員申請後打開屏幕分享'; + String get common_client_error_open_screen_share_need_permission_from_admin => '需要向房主或管理員申請後打開屏幕分享'; @override - String get common_client_error_open_screen_share_need_seat_unlock => - '當前麥位視頻被鎖, 需要由房主解鎖麥位後,才能打開屏幕分享'; + String get common_client_error_open_screen_share_need_seat_unlock => '當前麥位視頻被鎖, 需要由房主解鎖麥位後,才能打開屏幕分享'; @override - String get common_client_error_operation_invalid_before_enter_room => - '需要進房後才可使用此功能'; + String get common_client_error_operation_invalid_before_enter_room => '需要進房後才可使用此功能'; @override - String get common_client_error_operation_not_supported_in_current_room_type => - '當前房間類型下不支持該操作'; + String get common_client_error_operation_not_supported_in_current_room_type => '當前房間類型下不支持該操作'; @override - String get common_client_error_permission_denied => - '獲取權限失敗,當前未授權音/視頻權限,請查看是否開啟設備權限'; + String get common_client_error_permission_denied => '獲取權限失敗,當前未授權音/視頻權限,請查看是否開啟設備權限'; @override String get common_client_error_repeat_operation => '重複操作'; @@ -1506,27 +1454,22 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_client_error_request_id_repeat => '信令請求重複'; @override - String get common_client_error_request_no_permission => - '信令請求無權限,例如取消非自己發起的邀請'; + String get common_client_error_request_no_permission => '信令請求無權限,例如取消非自己發起的邀請'; @override - String get common_client_error_require_payment => - '該功能需要開通額外的套餐,請在 騰訊雲視立方 SDK 控制臺 按需開通對應套餐'; + String get common_client_error_require_payment => '該功能需要開通額外的套餐,請在 騰訊雲視立方 SDK 控制臺 按需開通對應套餐'; @override - String get common_client_error_room_id_invalid => - '創建房間 ID 非法,自定義 ID 必須為可列印 ASCII 字符(0x20-0x7e),最長48個字節'; + String get common_client_error_room_id_invalid => '創建房間 ID 非法,自定義 ID 必須為可列印 ASCII 字符(0x20-0x7e),最長48個字節'; @override - String get common_client_error_room_name_invalid => - '房間名稱非法,名稱最長30位元組,字符編碼必須是 UTF-8 ,如果包含中文'; + String get common_client_error_room_name_invalid => '房間名稱非法,名稱最長30位元組,字符編碼必須是 UTF-8 ,如果包含中文'; @override String get common_client_error_room_not_support_preloading => '當前房間不支持預加載'; @override - String get common_client_error_sdk_app_id_not_found => - '未找到 SDKAppID,請在 騰訊雲視立方 SDK 控制臺 確認應用信息'; + String get common_client_error_sdk_app_id_not_found => '未找到 SDKAppID,請在 騰訊雲視立方 SDK 控制臺 確認應用信息'; @override String get common_client_error_sdk_not_initialized => '未登錄,請調用 Login 接口'; @@ -1538,19 +1481,16 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_client_error_send_message_disabled_for_all => '當前房間已開啟全員禁言'; @override - String get common_client_error_send_message_disabled_for_current => - '當前房間內,您已被禁言'; + String get common_client_error_send_message_disabled_for_current => '當前房間內,您已被禁言'; @override - String get common_client_error_start_screen_sharing_failed => - '開啟屏幕分享失敗,檢查房間內是否有人正在屏幕分享'; + String get common_client_error_start_screen_sharing_failed => '開啟屏幕分享失敗,檢查房間內是否有人正在屏幕分享'; @override String get common_client_error_success => '操作成功'; @override - String get common_client_error_user_need_admin_permission => - '需要房主或者管理員權限才能操作'; + String get common_client_error_user_need_admin_permission => '需要房主或者管理員權限才能操作'; @override String get common_client_error_user_need_owner_permission => '需要房主權限才能操作'; @@ -1559,15 +1499,13 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_client_error_user_not_exist => '用戶不存在'; @override - String get common_server_error_gift_ability_not_enabled => - '禮物功能服務暫未開啟,請檢查套餐包版本'; + String get common_server_error_gift_ability_not_enabled => '禮物功能服務暫未開啟,請檢查套餐包版本'; @override String get common_server_error_gift_not_exist => '禮物不存在'; @override - String get common_server_error_gift_server_pre_verification_failed => - '禮物伺服器預驗證失敗,請檢查控制台配置'; + String get common_server_error_gift_server_pre_verification_failed => '禮物伺服器預驗證失敗,請檢查控制台配置'; @override String get common_server_error_call_in_progress => '正在通話中,設備操作失敗'; @@ -1789,8 +1727,7 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_server_error_already_on_the_mic_queue => '已經處於排麥狀態'; @override - String get common_server_error_battle_does_not_exist_or_has_ended => - '該場次 battle 不存在或已結束'; + String get common_server_error_battle_does_not_exist_or_has_ended => '該場次 battle 不存在或已結束'; @override String get common_server_error_battle_session_has_ended => '該 battle 場次已經結束'; @@ -1799,65 +1736,50 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_server_error_connection_does_not_exist => '當前連線不存在或結束'; @override - String get common_server_error_creating_battles_too_frequently => - '短時間內頻繁發起 battle, 稍等一會再試'; + String get common_server_error_creating_battles_too_frequently => '短時間內頻繁發起 battle, 稍等一會再試'; @override - String get common_server_error_creating_connections_too_frequent => - '短時間內連線過於頻繁,稍等一會再試'; + String get common_server_error_creating_connections_too_frequent => '短時間內連線過於頻繁,稍等一會再試'; @override - String get common_server_error_creating_rooms_exceeds_the_frequency_limit => - '頻率超過限制,例如創建房間超過頻率超限,同一房間 ID, 1秒內只能創建一次'; + String get common_server_error_creating_rooms_exceeds_the_frequency_limit => '頻率超過限制,例如創建房間超過頻率超限,同一房間 ID, 1秒內只能創建一次'; @override - String get common_server_error_exceeds_the_upper_limit => - '超過付費上限,例如麥位數,pk場次房間數量等超過付費限制'; + String get common_server_error_exceeds_the_upper_limit => '超過付費上限,例如麥位數,pk場次房間數量等超過付費限制'; @override - String - get common_server_error_has_exceeded_the_limit_in_connection_or_battle => - '超過連線和 battle 房間數量上限'; + String get common_server_error_has_exceeded_the_limit_in_connection_or_battle => '超過連線和 battle 房間數量上限'; @override String get common_server_error_in_other_battle => '該房間處於其他的 battle 場次中'; @override - String get common_server_error_insufficient_operation_permissions => - '您當前無法執行此操作(可能是無權限,或受場景限制等原因)'; + String get common_server_error_insufficient_operation_permissions => '您當前無法執行此操作(可能是無權限,或受場景限制等原因)'; @override String get common_server_error_invalid_room_type => '無效的房間類型'; @override - String get common_server_error_is_connecting_with_other_rooms => - '當前房間與其他房間連線中'; + String get common_server_error_is_connecting_with_other_rooms => '當前房間與其他房間連線中'; @override - String - get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => - '該房間處於 battle 中'; + String get common_server_error_is_not_allowed_to_cancel_battle_for_room_in_battle => '該房間處於 battle 中'; @override - String get common_server_error_metadata_no_valid_keys => - '刪除房間 meta 數據時候,被刪除的 key 沒有一個存在'; + String get common_server_error_metadata_no_valid_keys => '刪除房間 meta 數據時候,被刪除的 key 沒有一個存在'; @override - String get common_server_error_metadata_number_of_keys_exceeds_the_limit => - '房間 meta 數據中的 key 數量超過上限'; + String get common_server_error_metadata_number_of_keys_exceeds_the_limit => '房間 meta 數據中的 key 數量超過上限'; @override - String get common_server_error_metadata_size_of_value_exceeds_the_limit => - '房間 meta 數據中單個 key 對應的 val 超過最大字節數限制'; + String get common_server_error_metadata_size_of_value_exceeds_the_limit => '房間 meta 數據中單個 key 對應的 val 超過最大字節數限制'; @override - String - get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => - '房間 meta 數據中的 key 大小超過了最大字節數限制'; + String get common_server_error_metadata_the_size_of_key_exceeds_the_maximum_byte_limit => + '房間 meta 數據中的 key 大小超過了最大字節數限制'; @override - String get common_server_error_metadata_total_size_exceeds_the_limit => - '房間 meta數據中所有 key 對應的 val 總和超過最大字節數限制'; + String get common_server_error_metadata_total_size_exceeds_the_limit => '房間 meta數據中所有 key 對應的 val 總和超過最大字節數限制'; @override String get common_server_error_mic_seat_is_locked => '麥位已鎖定,可以嘗試換一個麥位'; @@ -1866,8 +1788,7 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_server_error_no_payment_information => '無付費信息,需在控制臺購買套餐包'; @override - String get common_server_error_no_rooms_in_the_battle_is_valid => - '發起的 battle 裡沒有一個有效的房間'; + String get common_server_error_no_rooms_in_the_battle_is_valid => '發起的 battle 裡沒有一個有效的房間'; @override String get common_server_error_not_a_room_member => '非房間成員'; @@ -1888,16 +1809,13 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_server_error_requires_password => '當前房間需要密碼才能進入'; @override - String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => - '管理員數量超過上限'; + String get common_server_error_room_admin_quantity_exceeds_the_upper_limit => '管理員數量超過上限'; @override - String get common_server_error_room_does_not_exist => - '房間不存在,或者曾經存在過,但是目前已經被解散'; + String get common_server_error_room_does_not_exist => '房間不存在,或者曾經存在過,但是目前已經被解散'; @override - String get common_server_error_room_does_not_support_mic_ability => - '該房間不支持連麥'; + String get common_server_error_room_does_not_support_mic_ability => '該房間不支持連麥'; @override String get common_server_error_room_entry_password_error => '進房密碼錯誤'; @@ -1906,12 +1824,10 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_server_error_room_id_exists => '房間ID 已被使用,請選擇別的房間ID'; @override - String get common_server_error_room_id_has_been_occupied_by_chat => - '房間 ID 已被 IM 佔用,可以換一個房間 ID 使用,或者先通過 IM 接口解散該群'; + String get common_server_error_room_id_has_been_occupied_by_chat => '房間 ID 已被 IM 佔用,可以換一個房間 ID 使用,或者先通過 IM 接口解散該群'; @override - String get common_server_error_room_id_has_been_used => - '房間 ID 已被使用,並且操作者為房主,可以直接使用'; + String get common_server_error_room_id_has_been_used => '房間 ID 已被使用,並且操作者為房主,可以直接使用'; @override String get common_server_error_room_is_full => '房間成員已滿'; @@ -1932,8 +1848,7 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_server_error_tag_quantity_exceeds_upper_limit => '標籤數量超上限'; @override - String get common_server_error_the_room_is_not_in_the_battle => - '該房間已經不在 battle 中'; + String get common_server_error_the_room_is_not_in_the_battle => '該房間已經不在 battle 中'; @override String get common_server_error_the_seat_list_is_empty => '連麥列表為空'; @@ -1942,12 +1857,10 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_server_error_the_seats_are_all_taken => '麥位已滿'; @override - String get common_server_error_there_is_a_pending_battle_request => - '該房間存在待處理的 battle 請求'; + String get common_server_error_there_is_a_pending_battle_request => '該房間存在待處理的 battle 請求'; @override - String get common_server_error_there_is_a_pending_connection_request => - '該房間存在待處理的連線請求'; + String get common_server_error_there_is_a_pending_connection_request => '該房間存在待處理的連線請求'; @override String get common_server_error_this_member_has_been_banned => '該成員已經被封禁'; @@ -1958,6 +1871,9 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { @override String get common_server_error_user_is_already_on_the_mic_seat => '已經有用戶在麥位上'; + @override + String get common_server_error_im_sensitive_words_ban => '訊息或資料中文字存在敏感內容,禁止下發。'; + @override String get common_set_as_background => '設為背景'; @@ -2244,8 +2160,7 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get live_video_resolution_changed => '清晰度已切換至'; @override - String get common_template_601_ui_exception_toast => - '此佈局模板在特定機型上會顯示異常,建議更換其他佈局模板'; + String get common_template_601_ui_exception_toast => '此佈局模板在特定機型上會顯示異常,建議更換其他佈局模板'; @override String get seat_locked => '已鎖定'; @@ -2308,12 +2223,10 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { String get common_remove => '移除'; @override - String get common_host_kick_user_after_connect => - '連線成功。前 6 個麥位以外的使用者已被移除。如需互動,請重新邀請。'; + String get common_host_kick_user_after_connect => '連線成功。前 6 個麥位以外的使用者已被移除。如需互動,請重新邀請。'; @override - String get common_audience_kicked_re_apply => - '連線僅顯示前 6 個麥位。您已被移除,如有需要請重新申請上麥。'; + String get common_audience_kicked_re_apply => '連線僅顯示前 6 個麥位。您已被移除,如有需要請重新申請上麥。'; @override String get common_game_live => '手遊直播'; @@ -2332,4 +2245,19 @@ class LiveKitLocalizationsZhHant extends LiveKitLocalizationsZh { @override String get common_select_app_to_live => '選擇應用並開始直播'; + + @override + String get common_violation_alert_toast => '當前畫面或內容存在違規風險,請注意平台規範'; + + @override + String get live_song_unknown_artist => '未知'; + + @override + String get live_anchor_manager_set_featured_host => '設為主咖'; + + @override + String get live_anchor_manager_revoke_featured_host => '取消主咖'; + + @override + String get common_enter_anchor_live_room => '去TA的直播間'; } diff --git a/live/livekit/lib/common/language/i10n/livekit_ar.arb b/live/livekit/lib/common/language/i10n/livekit_ar.arb index 71c2b069..706269c5 100644 --- a/live/livekit/lib/common/language/i10n/livekit_ar.arb +++ b/live/livekit/lib/common/language/i10n/livekit_ar.arb @@ -244,6 +244,7 @@ "common_server_error_this_member_has_been_banned": "تم حظر هذا العضو", "common_server_error_this_member_has_been_muted": "تم كتم صوت هذا العضو", "common_server_error_user_is_already_on_the_mic_seat": "المستخدم موجود بالفعل على المقعد", + "common_server_error_im_sensitive_words_ban": "تحتوي الرسالة أو الوثيقة على محتوى حساس ويُحظر إرسالها.", "common_set_as_background": "تعيين كخلفية", "common_set_as_cover": "تعيين كغلاف", "common_settings": "الإعدادات", @@ -367,5 +368,10 @@ "common_end_live_by_server": "تم إيقاف البث المباشر بسبب انتهاكات للوائح.", "common_go_to_enable": "الذهاب للتفعيل", "common_live_screen": "بث الشاشة مباشرةً", - "common_select_app_to_live": "بث مباشر للعبةاختر التطبيق وابدأ البث المباشر" + "common_select_app_to_live": "بث مباشر للعبةاختر التطبيق وابدأ البث المباشر", + "common_violation_alert_toast": "المحتوى الحالي قد ينتهك إرشادات المنصة، يرجى الالتزام بالقواعد", + "live_song_unknown_artist": "غير معروف", + "live_anchor_manager_set_featured_host": "تعيين كمضيف رئيسي", + "live_anchor_manager_revoke_featured_host": "إلغاء المضيف الرئيسي", + "common_enter_anchor_live_room": "الذهاب إلى غرفة البث" } \ No newline at end of file diff --git a/live/livekit/lib/common/language/i10n/livekit_en.arb b/live/livekit/lib/common/language/i10n/livekit_en.arb index b46740c0..12db740a 100644 --- a/live/livekit/lib/common/language/i10n/livekit_en.arb +++ b/live/livekit/lib/common/language/i10n/livekit_en.arb @@ -142,10 +142,10 @@ "common_beauty_panel_title": "One-click beauty", "common_cancel": "Cancel", "common_change_voice": "Voice changer", - "common_change_voice_child": "Naughty child", + "common_change_voice_child": "Kid", "common_change_voice_ethereal": "Ethereal", - "common_change_voice_girl": "Loli", - "common_change_voice_none": "Original", + "common_change_voice_girl": "Girl", + "common_change_voice_none": "Default", "common_change_voice_uncle": "Uncle", "common_common_gift_income": "Gift Income", "common_common_like_count": "Likes Count", @@ -188,7 +188,7 @@ "common_reverb_karaoke": "KTV", "common_reverb_loud_and_loud": "Loud", "common_reverb_low": "Low", - "common_reverb_metallic_sound": "Metallic sound", + "common_reverb_metallic_sound": "Metallic", "common_reverb_none": "No effect", "common_room_destroy": "Broadcast has been ended", "common_room_info_liveroom_id": "Live Room ID:", @@ -244,6 +244,7 @@ "common_server_error_this_member_has_been_banned": "This member has been banned", "common_server_error_this_member_has_been_muted": "This member has been muted", "common_server_error_user_is_already_on_the_mic_seat": "The user is already on the seat", + "common_server_error_im_sensitive_words_ban": "The message or document contains sensitive content and is prohibited from being sent.", "common_set_as_background": "Set as background", "common_set_as_cover": "Set as cover", "common_settings": "Settings", @@ -367,5 +368,10 @@ "common_end_live_by_server": "The live stream was shut down due to violations of regulations.", "common_go_to_enable": "Go to Enable", "common_live_screen": "Live Screen", - "common_select_app_to_live": "Select App and Start Live" + "common_select_app_to_live": "Select App and Start Live", + "common_violation_alert_toast": "Current content may violate platform guidelines, please follow the rules", + "live_song_unknown_artist": "Unknown", + "live_anchor_manager_set_featured_host": "Set as Featured", + "live_anchor_manager_revoke_featured_host": "Revoke Featured", + "common_enter_anchor_live_room": "Enter Live Room" } \ No newline at end of file diff --git a/live/livekit/lib/common/language/i10n/livekit_ja.arb b/live/livekit/lib/common/language/i10n/livekit_ja.arb index 7cbbc968..fc4d4aac 100644 --- a/live/livekit/lib/common/language/i10n/livekit_ja.arb +++ b/live/livekit/lib/common/language/i10n/livekit_ja.arb @@ -244,6 +244,7 @@ "common_server_error_this_member_has_been_banned": "このメンバーは禁止されています", "common_server_error_this_member_has_been_muted": "このメンバーは禁止されています", "common_server_error_user_is_already_on_the_mic_seat": "マイクにはすでにユーザーがいます", + "common_server_error_im_sensitive_words_ban": "このメッセージまたは文書には機密情報が含まれているため、送信は禁止されています。", "common_set_as_background": "背景として設定します", "common_set_as_cover": "カバーとして設定します", "common_settings": "設定", @@ -367,5 +368,10 @@ "common_end_live_by_server": "規則違反のため、ライブ配信は停止されました。", "common_go_to_enable": "有効にする", "common_live_screen": "画面をライブ配信", - "common_select_app_to_live": "アプリを選択してライブを開始" + "common_select_app_to_live": "アプリを選択してライブを開始", + "common_violation_alert_toast": "現在のコンテンツにはプラットフォーム規約に違反するリスクがあります。ご注意ください", + "live_song_unknown_artist": "不明", + "live_anchor_manager_set_featured_host": "メインホストに設定", + "live_anchor_manager_revoke_featured_host": "メインホストを解除", + "common_enter_anchor_live_room": "配信ルームへ" } \ No newline at end of file diff --git a/live/livekit/lib/common/language/i10n/livekit_zh.arb b/live/livekit/lib/common/language/i10n/livekit_zh.arb index 619dc1aa..991df320 100644 --- a/live/livekit/lib/common/language/i10n/livekit_zh.arb +++ b/live/livekit/lib/common/language/i10n/livekit_zh.arb @@ -244,6 +244,7 @@ "common_server_error_this_member_has_been_banned": "该成员已经被封禁", "common_server_error_this_member_has_been_muted": "该成员已经被禁言", "common_server_error_user_is_already_on_the_mic_seat": "已经有用户在麦位上", + "common_server_error_im_sensitive_words_ban": "消息或者资料中文本存在敏感内容,禁止下发。", "common_set_as_background": "设为背景", "common_set_as_cover": "设为封面", "common_settings": "设置", @@ -367,5 +368,10 @@ "common_end_live_by_server": "直播内容违规已被强制关播", "common_go_to_enable": "去开启", "common_live_screen": "直播屏幕", - "common_select_app_to_live": "选择应用并开始直播" + "common_select_app_to_live": "选择应用并开始直播", + "common_violation_alert_toast": "当前画面或内容存在违规风险,请注意平台规范", + "live_song_unknown_artist": "未知", + "live_anchor_manager_set_featured_host": "设为主咖", + "live_anchor_manager_revoke_featured_host": "取消主咖", + "common_enter_anchor_live_room": "去TA的直播间" } \ No newline at end of file diff --git a/live/livekit/lib/common/language/i10n/livekit_zh_Hant.arb b/live/livekit/lib/common/language/i10n/livekit_zh_Hant.arb index 060626da..5805bd75 100644 --- a/live/livekit/lib/common/language/i10n/livekit_zh_Hant.arb +++ b/live/livekit/lib/common/language/i10n/livekit_zh_Hant.arb @@ -244,6 +244,7 @@ "common_server_error_this_member_has_been_banned": "該成員已經被封禁", "common_server_error_this_member_has_been_muted": "該成員已經被禁言", "common_server_error_user_is_already_on_the_mic_seat": "已經有用戶在麥位上", + "common_server_error_im_sensitive_words_ban": "訊息或資料中文字存在敏感內容,禁止下發。", "common_set_as_background": "設為背景", "common_set_as_cover": "設為封面", "common_settings": "設置", @@ -367,5 +368,10 @@ "common_end_live_by_server": "直播內容違規已強制關播", "common_go_to_enable": "去開啟", "common_live_screen": "直播螢幕", - "common_select_app_to_live": "選擇應用並開始直播" + "common_select_app_to_live": "選擇應用並開始直播", + "common_violation_alert_toast": "當前畫面或內容存在違規風險,請注意平台規範", + "live_song_unknown_artist": "未知", + "live_anchor_manager_set_featured_host": "設為主咖", + "live_anchor_manager_revoke_featured_host": "取消主咖", + "common_enter_anchor_live_room": "去TA的直播間" } \ No newline at end of file diff --git a/live/livekit/lib/common/language/index.dart b/live/livekit/lib/common/language/index.dart index 7c9adc4f..00aabc78 100644 --- a/live/livekit/lib/common/language/index.dart +++ b/live/livekit/lib/common/language/index.dart @@ -1,4 +1,3 @@ -library languages; export './gen/livekit_localizations.dart'; export 'device_language.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/logger/index.dart b/live/livekit/lib/common/logger/index.dart index 70658a93..82de3044 100644 --- a/live/livekit/lib/common/logger/index.dart +++ b/live/livekit/lib/common/logger/index.dart @@ -1,3 +1,2 @@ -library logger; export './logger.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/platform/index.dart b/live/livekit/lib/common/platform/index.dart index 17432e8d..ecf11650 100644 --- a/live/livekit/lib/common/platform/index.dart +++ b/live/livekit/lib/common/platform/index.dart @@ -1,3 +1,2 @@ -library platform; export './rtc_live_tuikit_platform_interface.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/platform/rtc_live_tuikit_method_channel.dart b/live/livekit/lib/common/platform/rtc_live_tuikit_method_channel.dart index 4ee4a824..15975dfa 100644 --- a/live/livekit/lib/common/platform/rtc_live_tuikit_method_channel.dart +++ b/live/livekit/lib/common/platform/rtc_live_tuikit_method_channel.dart @@ -17,8 +17,8 @@ class MethodChannelTUILiveKit extends TUILiveKitPlatform { static const _pipEventChannel = EventChannel('tuilivekit_pip_events'); static const _screenCaptureEventChannel = EventChannel('tuilivekit_screen_capture_events'); - static const String STATE_ENTER_PIP = "state_enter_pip"; - static const String STATE_LEAVE_PIP = "state_leave_pip"; + static const String stateEnterPip = "state_enter_pip"; + static const String stateLeavePip = "state_leave_pip"; @override Stream get onPipModeChanged { @@ -26,7 +26,7 @@ class MethodChannelTUILiveKit extends TUILiveKitPlatform { final rawData = _pipEventChannel.receiveBroadcastStream(); return rawData.map((value) { if (value is String) { - return value == STATE_ENTER_PIP ? true : false; + return value == stateEnterPip ? true : false; } else { return false; } diff --git a/live/livekit/lib/common/reporter/index.dart b/live/livekit/lib/common/reporter/index.dart index e0ec65af..49022ed0 100644 --- a/live/livekit/lib/common/reporter/index.dart +++ b/live/livekit/lib/common/reporter/index.dart @@ -1,3 +1,2 @@ -library reporpter; export 'key_metrics.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/resources/images.dart b/live/livekit/lib/common/resources/images.dart index f394a36d..7c2856ff 100644 --- a/live/livekit/lib/common/resources/images.dart +++ b/live/livekit/lib/common/resources/images.dart @@ -49,6 +49,7 @@ class LiveImages { static const selectRuddy = 'assets/images/live_select_ruddy.png'; static const settingsItemBeauty = 'assets/images/live_settings_item_beauty.png'; + static const settingsItemAudioEffect = 'assets/images/live_settings_item_audio_effect.png'; static const settingsItemMusic = 'assets/images/live_settings_item_music.png'; static const settingsItemFlip = 'assets/images/live_settings_item_flip.png'; static const settingsItemMirror = 'assets/images/live_settings_item_mirror.png'; @@ -143,6 +144,8 @@ class LiveImages { static const closeCamera = 'assets/images/live_close_camera.png'; static const disableCamera = 'assets/images/live_disable_camera.png'; static const leaveSeat = 'assets/images/live_leave_seat.png'; + static const setFeaturedHost = 'assets/images/live_set_featured_host.png'; + static const revokeFeaturedHost = 'assets/images/live_revoke_featured_host.png'; static const seatOwner = 'assets/images/live_seat_owner.png'; static const seatAudioLockedIcon = 'assets/images/live_seat_audio_locked.png'; @@ -153,4 +156,10 @@ class LiveImages { static const liveDoubleColumn = 'assets/images/live_double_column.png'; static const endByServer = 'assets/images/live_end_by_server.png'; -} \ No newline at end of file + + static const muteImage = 'assets/images/live_muteImage.png'; + static const muteImageEn = 'assets/images/live_muteImage_en.png'; + static const muteImageLand = 'assets/images/live_muteImage_land.png'; + static const muteImageEnLand = 'assets/images/live_muteImage_en_land.png'; + static const muteImageSmall = 'assets/images/live_muteImage_small.png'; +} diff --git a/live/livekit/lib/common/resources/index.dart b/live/livekit/lib/common/resources/index.dart index 07ba4b1e..b275ffc6 100644 --- a/live/livekit/lib/common/resources/index.dart +++ b/live/livekit/lib/common/resources/index.dart @@ -1,4 +1,3 @@ -library style; export './colors.dart'; export './images.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/resources/live_theme_manager.dart b/live/livekit/lib/common/resources/live_theme_manager.dart index f210ccc2..a759f909 100644 --- a/live/livekit/lib/common/resources/live_theme_manager.dart +++ b/live/livekit/lib/common/resources/live_theme_manager.dart @@ -1,6 +1,8 @@ import 'package:flutter/widgets.dart'; import 'package:tuikit_atomic_x/base_component/theme/theme_state.dart'; +import '../logger/logger.dart'; + /// LiveKit theme manager. /// Switches theme when entering LiveKit scene and restores when exiting. /// Also supports pausing theme in float window mode. @@ -19,11 +21,13 @@ class LiveThemeManager { /// Safely set theme mode, avoiding calls during build phase. void _safeSetThemeMode(ThemeState themeState, ThemeType themeType) { + LiveKitLogger.info("_safeSetThemeMode, currentType=${themeState.currentType}, newType=$themeType"); if (themeState.currentType == themeType) { return; } WidgetsBinding.instance.addPostFrameCallback((_) { if (themeState.currentType != themeType) { + LiveKitLogger.info("_safeSetThemeMode:$themeType"); themeState.setThemeMode(themeType); } }); @@ -33,22 +37,25 @@ class LiveThemeManager { /// [context] is used to get ThemeState. /// [targetTheme] is the target theme, defaults to dark. void enterLiveKitScene(BuildContext context, {ThemeType targetTheme = ThemeType.dark}) { + LiveKitLogger.info("enterLiveKitScene, _referenceCount=$_referenceCount, _isPaused=$_isPaused"); _referenceCount++; _targetThemeType = targetTheme; - if (_referenceCount == 1) { + // First time entering: capture the original theme as the one to restore on exit. + if (_themeState == null || _previousThemeType == null) { _themeState = BaseThemeProvider.of(context); _previousThemeType = _themeState?.currentType; - _isPaused = false; - - if (_themeState != null) { - _safeSetThemeMode(_themeState!, targetTheme); - } + LiveKitLogger.info("ThemeState current ThemeType: $_previousThemeType"); + } + _isPaused = false; + if (_themeState != null) { + _safeSetThemeMode(_themeState!, targetTheme); } } /// Called when exiting LiveKit scene. void exitLiveKitScene() { + LiveKitLogger.info("exitLiveKitScene, _referenceCount=$_referenceCount"); if (_referenceCount > 0) { _referenceCount--; } @@ -63,6 +70,7 @@ class LiveThemeManager { /// Called when entering float window mode, temporarily restores the previous theme. void pauseTheme() { + LiveKitLogger.info("pauseTheme"); if (_isPaused || _themeState == null || _previousThemeType == null) { return; } @@ -72,6 +80,7 @@ class LiveThemeManager { /// Called when returning from float window to fullscreen, restores LiveKit theme. void resumeTheme() { + LiveKitLogger.info("resumeTheme"); if (!_isPaused || _themeState == null) { return; } @@ -81,6 +90,7 @@ class LiveThemeManager { /// Force reset state (for exceptional cases). void forceReset() { + LiveKitLogger.info("forceReset"); if (_previousThemeType != null && _themeState != null) { _safeSetThemeMode(_themeState!, _previousThemeType!); } diff --git a/live/livekit/lib/common/screen/index.dart b/live/livekit/lib/common/screen/index.dart index ba6ab78a..6cf9d5e6 100644 --- a/live/livekit/lib/common/screen/index.dart +++ b/live/livekit/lib/common/screen/index.dart @@ -1,3 +1,2 @@ -library screen; export 'screen_adapter.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/selector/index.dart b/live/livekit/lib/common/selector/index.dart index 0e8fa2bd..b0771943 100644 --- a/live/livekit/lib/common/selector/index.dart +++ b/live/livekit/lib/common/selector/index.dart @@ -1,3 +1,2 @@ -library selector; export 'value_selector.dart'; \ No newline at end of file diff --git a/live/livekit/lib/common/widget/float_window/float_window_controller.dart b/live/livekit/lib/common/widget/float_window/float_window_controller.dart index 6f332d49..1a6fd449 100644 --- a/live/livekit/lib/common/widget/float_window/float_window_controller.dart +++ b/live/livekit/lib/common/widget/float_window/float_window_controller.dart @@ -4,6 +4,7 @@ class FloatWindowController { final ValueChanged onTapSwitchFloatWindowInApp; final ValueChanged onSwitchFloatWindowOutOfApp; final ValueListenable isFullScreen = ValueNotifier(true); + final ValueListenable isLandscape = ValueNotifier(false); FloatWindowController({required this.onTapSwitchFloatWindowInApp, required this.onSwitchFloatWindowOutOfApp}); @@ -12,4 +13,10 @@ class FloatWindowController { (this.isFullScreen as ValueNotifier).value = isFullScreen; } } + + void setScreenOrientation(bool isLandscape) { + if (this.isLandscape is ValueNotifier) { + (this.isLandscape as ValueNotifier).value = isLandscape; + } + } } diff --git a/live/livekit/lib/common/widget/float_window/float_window_widget.dart b/live/livekit/lib/common/widget/float_window/float_window_widget.dart index 3d32d40f..8edbfd7e 100644 --- a/live/livekit/lib/common/widget/float_window/float_window_widget.dart +++ b/live/livekit/lib/common/widget/float_window/float_window_widget.dart @@ -10,6 +10,8 @@ typedef ContentWidgetBuilder = Widget Function(BuildContext context, FloatWindow class FloatWindowWidget extends StatefulWidget { final ContentWidgetBuilder builder; final double? padding; + final double? x; + final double? y; final Size? size; final BorderRadius? borderRadius; @@ -17,6 +19,8 @@ class FloatWindowWidget extends StatefulWidget { super.key, required this.builder, this.padding, + this.x, + this.y, this.size, this.borderRadius, }); @@ -31,9 +35,10 @@ class _FloatWindowWidgetState extends State with SingleTicker final double floatWindowRadius = 15.radius; late final double floatWindowPadding = widget.padding ?? 10.width; final Size fullScreenSize = Size(min(1.screenWidth, 1.screenHeight), max(1.screenWidth, 1.screenHeight)); - late final Size floatWindowSize = widget.size ?? + late final Size floatWindowPortraitSize = widget.size ?? Size(fullScreenSize.width * 0.3 + 2 * floatWindowPadding, fullScreenSize.width * 0.3 * 16 / 9 + 2 * floatWindowPadding); + late Size floatWindowSize = floatWindowPortraitSize; late final BorderRadius floatWindowBorderRadius = widget.borderRadius ?? BorderRadius.all(Radius.circular(floatWindowRadius)); late Rect fullScreenRect = Rect.fromLTWH(0, 0, fullScreenSize.width, fullScreenSize.height); @@ -91,6 +96,7 @@ class _FloatWindowWidgetState extends State with SingleTicker padding: (_isFullScreen.value || _isPipMode.value) ? EdgeInsets.zero : EdgeInsets.all(floatWindowPadding), child: ClipRRect( + clipBehavior: (_isFullScreen.value || _isPipMode.value) ? Clip.antiAlias : Clip.antiAliasWithSaveLayer, borderRadius: (_isFullScreen.value || _isPipMode.value) ? BorderRadius.zero : floatWindowBorderRadius, child: Stack( children: [ @@ -121,10 +127,12 @@ class _FloatWindowWidgetState extends State with SingleTicker void _onEnterOverlayMode() { setState(() { + final isLandscape = floatWindowController.isLandscape.value; + floatWindowSize = isLandscape ? floatWindowPortraitSize.flipped : floatWindowPortraitSize; size = floatWindowSize; Size screenSize = _getCurrentScreenSize(); - final left = screenSize.width - floatWindowSize.width; - final top = screenSize.height - floatWindowSize.height; + final left = widget.x ?? screenSize.width - floatWindowSize.width; + final top = widget.y ?? screenSize.height - floatWindowSize.height; rect = Rect.fromLTWH(left, top, floatWindowSize.width, floatWindowSize.height); _onTapCallback = _onTap; _setFullScreen(false); diff --git a/live/livekit/lib/common/widget/float_window/index.dart b/live/livekit/lib/common/widget/float_window/index.dart index 0bbbe9e3..1bd0ff05 100644 --- a/live/livekit/lib/common/widget/float_window/index.dart +++ b/live/livekit/lib/common/widget/float_window/index.dart @@ -1,4 +1,3 @@ -library float_window; export 'float_window_mode.dart'; export 'float_window_widget.dart'; diff --git a/live/livekit/lib/common/widget/index.dart b/live/livekit/lib/common/widget/index.dart index 895f2cfd..694191e3 100644 --- a/live/livekit/lib/common/widget/index.dart +++ b/live/livekit/lib/common/widget/index.dart @@ -1,4 +1,3 @@ -library widget; export 'alert.dart'; export 'debounce_gesture_recognizer.dart'; diff --git a/live/livekit/lib/common/widget/toast.dart b/live/livekit/lib/common/widget/toast.dart index 04f40a14..635dd3ed 100644 --- a/live/livekit/lib/common/widget/toast.dart +++ b/live/livekit/lib/common/widget/toast.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:tuikit_atomic_x/base_component/basic_controls/toast.dart'; void makeToast(BuildContext context, String message, {ToastType? type, bool useRootOverlay = false}) { + if (message.isEmpty) return; if (type == null) { Toast.simple(context, message, useRootOverlay: useRootOverlay); } else { diff --git a/live/livekit/lib/component/audience_list/index.dart b/live/livekit/lib/component/audience_list/index.dart index 36826206..f6d4720c 100644 --- a/live/livekit/lib/component/audience_list/index.dart +++ b/live/livekit/lib/component/audience_list/index.dart @@ -1,3 +1,2 @@ -library audience_list; export 'audience_list_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/component/audio_effect/audio_effect_panel_widget.dart b/live/livekit/lib/component/audio_effect/audio_effect_panel_widget.dart index 40644809..c0592944 100644 --- a/live/livekit/lib/component/audio_effect/audio_effect_panel_widget.dart +++ b/live/livekit/lib/component/audio_effect/audio_effect_panel_widget.dart @@ -1,9 +1,7 @@ -import 'package:atomic_x_core/api/device/audio_effect_store.dart'; import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../../../../common/constants/index.dart'; import '../../../../common/language/index.dart'; import '../../../../common/resources/index.dart'; import '../../../../common/widget/index.dart'; @@ -306,4 +304,4 @@ extension on _AudioEffectPanelWidgetState { void _setVoiceVolume(double volume) { _deviceStore.setOutputVolume(volume.toInt()); } -} +} \ No newline at end of file diff --git a/live/livekit/lib/component/audio_effect/index.dart b/live/livekit/lib/component/audio_effect/index.dart index d13d8a93..6d470627 100644 --- a/live/livekit/lib/component/audio_effect/index.dart +++ b/live/livekit/lib/component/audio_effect/index.dart @@ -1,3 +1,2 @@ -library audio_effect; export 'audio_effect_panel_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/component/beauty/base/base_beauty_panel_widget.dart b/live/livekit/lib/component/beauty/base/base_beauty_panel_widget.dart index da122f15..6974a660 100644 --- a/live/livekit/lib/component/beauty/base/base_beauty_panel_widget.dart +++ b/live/livekit/lib/component/beauty/base/base_beauty_panel_widget.dart @@ -52,22 +52,6 @@ class _BaseBeautyPanelWidgetState extends State { width: 1.screenWidth, child: Stack( children: [ - Positioned( - left: 24.width, - child: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: SizedBox( - width: 24.height, - height: 24.height, - child: Image.asset( - LiveImages.returnArrow, - package: Constants.pluginName, - ), - ), - ), - ), Center( child: Text( LiveKitLocalizations.of(Global.appContext())!.common_beauty_panel_title, @@ -163,9 +147,15 @@ class _BaseBeautyPanelWidgetState extends State { ), ), SizedBox(height: 2.height), - Text( - list[index].title, - style: const TextStyle(color: LiveColors.designStandardG6, fontSize: 12), + SizedBox( + width: 56.width, + child: Text( + list[index].title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: const TextStyle(color: LiveColors.designStandardG6, fontSize: 12), + ), ), ], ), @@ -211,8 +201,6 @@ extension on _BaseBeautyPanelWidgetState { case BeautyItemType.ruddy: sliderValue.value = _baseBeautyStore.baseBeautyState.ruddyLevel.value.toInt(); break; - default: - break; } } diff --git a/live/livekit/lib/component/beauty/index.dart b/live/livekit/lib/component/beauty/index.dart index 9a060a48..2307f552 100644 --- a/live/livekit/lib/component/beauty/index.dart +++ b/live/livekit/lib/component/beauty/index.dart @@ -1,4 +1,3 @@ -library new_beauty; export 'beauty_panel_widget.dart'; export 'live_beauty_store.dart'; diff --git a/live/livekit/lib/component/beauty/live_beauty_store.dart b/live/livekit/lib/component/beauty/live_beauty_store.dart index 1203161e..e644c914 100644 --- a/live/livekit/lib/component/beauty/live_beauty_store.dart +++ b/live/livekit/lib/component/beauty/live_beauty_store.dart @@ -36,7 +36,7 @@ class LiveBeautyStore { Widget? getTEBeautyPanel(Color backgroundColor) { Map param = { - 'backgroundColor' : backgroundColor.value, + 'backgroundColor' : backgroundColor.toARGB32(), }; const beautyPanel = 'beautyPanel'; TUICore.instance.callService(kTEBeautyService, kGetBeautyPanel, param); diff --git a/live/livekit/lib/component/bgm/bgm_panel_widget.dart b/live/livekit/lib/component/bgm/bgm_panel_widget.dart new file mode 100644 index 00000000..f52fff6b --- /dev/null +++ b/live/livekit/lib/component/bgm/bgm_panel_widget.dart @@ -0,0 +1,72 @@ +// Copyright (c) 2026 Tencent. All rights reserved. +// BGM panel widget aligned with Android native BGMPanelView. + +import 'package:flutter/material.dart'; +import 'package:tencent_live_uikit/common/index.dart'; + +import 'store/bgm_store.dart'; +import 'widget/bgm_list_widget.dart'; + +/// BGM panel widget. Displays the built-in music list and lets the anchor +/// play/stop background music in the room. +class BGMPanelWidget extends StatefulWidget { + final String roomId; + + const BGMPanelWidget({super.key, required this.roomId}); + + @override + State createState() => _BGMPanelWidgetState(); +} + +class _BGMPanelWidgetState extends State { + /// Key metric IDs, kept in sync with Android native BGMPanelView. + static const int _kMetricsPanelShowLiveRoomMusic = 190018; + static const int _kMetricsPanelShowVoiceRoomMusic = 191016; + + late final BGMStore _bgmStore; + + @override + void initState() { + super.initState(); + _bgmStore = BGMStore(widget.roomId); + _reportData(); + } + + void _reportData() { + final bool isVoiceRoom = widget.roomId.startsWith('voice_'); + KeyMetrics.reportKeyMetrics( + isVoiceRoom ? _kMetricsPanelShowVoiceRoomMusic : _kMetricsPanelShowLiveRoomMusic, + ); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: SizedBox( + height: 350.height, + child: Column( + children: [ + SizedBox(height: 20.height), + _buildTitle(), + SizedBox(height: 20.height), + Expanded(child: BGMListWidget(bgmStore: _bgmStore)), + ], + ), + ), + ); + } + + Widget _buildTitle() { + return SizedBox( + height: 24.height, + width: 1.screenWidth, + child: Center( + child: Text( + LiveKitLocalizations.of(Global.appContext())!.common_music, + style: const TextStyle(color: LiveColors.designStandardG7, fontSize: 16), + ), + ), + ); + } +} diff --git a/live/livekit/lib/component/bgm/index.dart b/live/livekit/lib/component/bgm/index.dart new file mode 100644 index 00000000..bd36757d --- /dev/null +++ b/live/livekit/lib/component/bgm/index.dart @@ -0,0 +1 @@ +export 'bgm_panel_widget.dart'; diff --git a/live/livekit/lib/component/bgm/store/bgm_store.dart b/live/livekit/lib/component/bgm/store/bgm_store.dart new file mode 100644 index 00000000..15d8881b --- /dev/null +++ b/live/livekit/lib/component/bgm/store/bgm_store.dart @@ -0,0 +1,107 @@ +// Copyright (c) 2026 Tencent. All rights reserved. +// BGM data layer aligned with Android native BGMStore. + +import 'package:atomic_x_core/atomicxcore.dart'; +import 'package:flutter/foundation.dart'; +import 'package:tencent_live_uikit/common/index.dart'; + +/// Music item that describes a single BGM entry in the panel. +class BGMInfo { + static const int invalidId = -1; + + final int id; + final String name; + final String path; + + /// Pitch adjustment value, range -12 ~ 12. + final ValueNotifier pitch; + + BGMInfo({ + this.id = invalidId, + this.name = '', + this.path = '', + double pitch = 0.0, + }) : pitch = ValueNotifier(pitch); +} + +/// BGM panel state container. +class BGMState { + final ValueNotifier currentMusicInfo = ValueNotifier(BGMInfo()); + final List musicList = []; +} + +/// BGM store, encapsulates [MusicStore] for the BGM panel UI. +class BGMStore { + static const String _tag = 'BGMStore'; + + final String roomId; + final BGMState bgmState = BGMState(); + final MusicStore _musicStore; + + BGMStore(this.roomId) : _musicStore = MusicStore.create(roomID: roomId); + + /// Whether the given [musicInfo] is currently playing. + /// Both LOADING and PLAYING states are treated as "playing". + /// + /// Aligned with Android native [BGMStore.isMusicPlaying]. + bool isMusicPlaying(BGMInfo musicInfo) { + final BGMInfo current = bgmState.currentMusicInfo.value; + if (current.id == BGMInfo.invalidId || current.id != musicInfo.id) { + return false; + } + final MusicPlayStatus status = _musicStore.musicState.playStatus.value; + return status == MusicPlayStatus.playing || status == MusicPlayStatus.loading; + } + + /// Toggle play/stop for [musicInfo], aligned with Android native + /// [BGMStore.operatePlayMusic]: + /// 1. If a different track is currently selected, stop it first. + /// 2. Update the current selection to [musicInfo]. + /// 3. If [musicInfo] is already playing, stop it; otherwise start it. + /// + /// Note on Flutter-specific timing: [_musicStore.stopPlay] is implemented + /// asynchronously and the underlying [MusicPlayStatus] does not flip to idle + /// synchronously. After step 1, [isMusicPlaying] would still observe the + /// previous track's status in the same microtask. To preserve the Android + /// semantics, we capture "is the same track and was playing" before the + /// stop call and reuse it for the branching decision. + void operatePlayMusic(BGMInfo musicInfo) { + final BGMInfo currentMusicInfo = bgmState.currentMusicInfo.value; + final bool wasPlayingTappedTrack = isMusicPlaying(musicInfo); + + if (currentMusicInfo.id != BGMInfo.invalidId && currentMusicInfo.id != musicInfo.id) { + _stopMusic(); + } + bgmState.currentMusicInfo.value = musicInfo; + LiveKitLogger.info('$_tag operatePlayMusic:[isPlaying:$wasPlayingTappedTrack]'); + if (wasPlayingTappedTrack) { + _stopMusic(); + } else { + _startMusic(musicInfo); + } + } + + /// Refresh [BGMState.currentMusicInfo] from the underlying [MusicStore] + /// when the panel is opened. + void refreshCurrentMusicInfo() { + final String? currentPlayUrl = _musicStore.musicState.playURL.value; + bgmState.currentMusicInfo.value = bgmState.musicList.firstWhere( + (BGMInfo info) => info.path == currentPlayUrl, + orElse: () => BGMInfo(), + ); + } + + /// Listenable to playback status changes, exposed for UI rebuild. + ValueListenable get playStatusListenable => _musicStore.musicState.playStatus; + + void _startMusic(BGMInfo musicInfo) { + LiveKitLogger.info('$_tag [$roomId] startMusic:[id:${musicInfo.id},name:${musicInfo.name}]'); + _musicStore.setPitch(musicInfo.pitch.value); + _musicStore.startPlay(musicInfo.path); + } + + void _stopMusic() { + LiveKitLogger.info('$_tag [$roomId] stopMusic'); + _musicStore.stopPlay(); + } +} diff --git a/live/livekit/lib/component/bgm/widget/bgm_list_widget.dart b/live/livekit/lib/component/bgm/widget/bgm_list_widget.dart new file mode 100644 index 00000000..86bdb192 --- /dev/null +++ b/live/livekit/lib/component/bgm/widget/bgm_list_widget.dart @@ -0,0 +1,126 @@ +// Copyright (c) 2026 Tencent. All rights reserved. +// BGM list widget aligned with Android native BGMListAdapter. + +import 'package:atomic_x_core/atomicxcore.dart'; +import 'package:flutter/material.dart'; +import 'package:tencent_live_uikit/common/index.dart'; + +import '../store/bgm_store.dart'; + +/// Built-in BGM URLs, kept in sync with Android native [BGMListAdapter]. +const String _kMusicUrlCheerful = + 'https://dldir1.qq.com/hudongzhibo/TUIKit/resource/music/PositiveHappyAdvertising.mp3'; +const String _kMusicUrlMelancholy = 'https://dldir1.qq.com/hudongzhibo/TUIKit/resource/music/SadCinematicPiano.mp3'; +const String _kMusicUrlWonderWorld = 'https://dldir1.qq.com/hudongzhibo/TUIKit/resource/music/WonderWorld.mp3'; + +class BGMListWidget extends StatefulWidget { + final BGMStore bgmStore; + + const BGMListWidget({super.key, required this.bgmStore}); + + @override + State createState() => _BGMListWidgetState(); +} + +class _BGMListWidgetState extends State { + late final BGMStore _bgmStore; + + @override + void initState() { + super.initState(); + _bgmStore = widget.bgmStore; + _initData(); + _bgmStore.refreshCurrentMusicInfo(); + } + + void _initData() { + if (_bgmStore.bgmState.musicList.isNotEmpty) return; + final l10n = LiveKitLocalizations.of(Global.appContext())!; + _bgmStore.bgmState.musicList.addAll([ + BGMInfo(id: 1, name: l10n.common_music_cheerful, path: _kMusicUrlCheerful), + BGMInfo(id: 2, name: l10n.common_music_melancholy, path: _kMusicUrlMelancholy), + BGMInfo(id: 3, name: l10n.common_music_wonder_world, path: _kMusicUrlWonderWorld), + ]); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _bgmStore.bgmState.currentMusicInfo, + builder: (context, _, __) { + return ValueListenableBuilder( + valueListenable: _bgmStore.playStatusListenable, + builder: (context, _, __) { + final list = _bgmStore.bgmState.musicList; + return ListView.builder( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: list.length, + itemBuilder: (context, index) => _buildItem(list[index]), + ); + }, + ); + }, + ); + } + + Widget _buildItem(BGMInfo info) { + final bool playing = _bgmStore.isMusicPlaying(info); + return SizedBox( + height: 60.height, + child: Stack( + children: [ + Positioned.fill( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.width), + child: Row( + children: [ + Expanded( + child: Text( + info.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: LiveColors.designStandardG7, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + Positioned( + right: 12.width, + top: 0, + bottom: 0, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _bgmStore.operatePlayMusic(info), + child: SizedBox( + width: 40.width + 24.width, + child: Center( + child: Image.asset( + playing ? LiveImages.musicPause : LiveImages.musicStart, + package: Constants.pluginName, + width: 16.width, + height: 16.height, + ), + ), + ), + ), + ), + Positioned( + left: 24.width, + right: 24.width, + bottom: 0, + child: Container( + height: 0.5.height, + color: LiveColors.designStandardG3, + ), + ), + ], + ), + ); + } +} diff --git a/live/livekit/lib/component/float_window/global_float_window_manager.dart b/live/livekit/lib/component/float_window/global_float_window_manager.dart index 0550adb0..f5011238 100644 --- a/live/livekit/lib/component/float_window/global_float_window_manager.dart +++ b/live/livekit/lib/component/float_window/global_float_window_manager.dart @@ -2,8 +2,6 @@ import 'package:flutter/cupertino.dart'; import 'package:tencent_live_uikit/common/index.dart'; import 'package:tencent_live_uikit/common/widget/float_window/float_window_mode.dart'; -import 'package:tencent_live_uikit/common/platform/rtc_live_tuikit_platform_interface.dart'; -import 'package:tencent_live_uikit/common/widget/global.dart'; import 'global_float_window_state.dart'; /// Two scenarios: diff --git a/live/livekit/lib/component/float_window/index.dart b/live/livekit/lib/component/float_window/index.dart index 3640a882..30401ba8 100644 --- a/live/livekit/lib/component/float_window/index.dart +++ b/live/livekit/lib/component/float_window/index.dart @@ -1,4 +1,3 @@ -library global_float_window; export 'global_float_window_manager.dart'; export 'global_float_window_state.dart'; diff --git a/live/livekit/lib/component/gift_access/index.dart b/live/livekit/lib/component/gift_access/index.dart index 7b277911..ede88ba2 100644 --- a/live/livekit/lib/component/gift_access/index.dart +++ b/live/livekit/lib/component/gift_access/index.dart @@ -1,3 +1,2 @@ -library gift_access; export 'gift_barrage_item_builder.dart'; \ No newline at end of file diff --git a/live/livekit/lib/component/index.dart b/live/livekit/lib/component/index.dart index 9b032827..703added 100644 --- a/live/livekit/lib/component/index.dart +++ b/live/livekit/lib/component/index.dart @@ -1,7 +1,9 @@ -library component; export 'audience_list/index.dart'; export 'audio_effect/index.dart'; export 'beauty/index.dart'; +export 'bgm/index.dart'; +export 'float_window/index.dart'; export 'gift_access/index.dart'; export 'live_info/index.dart'; +export 'network_info/index.dart'; diff --git a/live/livekit/lib/component/live_info/index.dart b/live/livekit/lib/component/live_info/index.dart index baa0129a..be26fc92 100644 --- a/live/livekit/lib/component/live_info/index.dart +++ b/live/livekit/lib/component/live_info/index.dart @@ -1,3 +1,2 @@ -library live_info; export 'live_info_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/component/network_info/index.dart b/live/livekit/lib/component/network_info/index.dart index 2c9ae429..c3b94e3e 100644 --- a/live/livekit/lib/component/network_info/index.dart +++ b/live/livekit/lib/component/network_info/index.dart @@ -1,4 +1,3 @@ -library network_info; export 'network_info_button.dart'; export 'network_info_widget.dart'; diff --git a/live/livekit/lib/component/network_info/manager/network_info_manager.dart b/live/livekit/lib/component/network_info/manager/network_info_manager.dart index 532faee4..bfda694f 100644 --- a/live/livekit/lib/component/network_info/manager/network_info_manager.dart +++ b/live/livekit/lib/component/network_info/manager/network_info_manager.dart @@ -34,13 +34,14 @@ class NetworkInfoManager extends TUIRoomObserver { return; } final matchedInfo = networkMap[userId]; - state.rtt.value = matchedInfo!.delay; - state.downLoss.value = matchedInfo!.downLoss; - state.upLoss.value = matchedInfo!.upLoss; + if (matchedInfo == null) return; + state.rtt.value = matchedInfo.delay; + state.downLoss.value = matchedInfo.downLoss; + state.upLoss.value = matchedInfo.upLoss; if (_isNetworkAvailable) { - state.networkQuality.value = matchedInfo!.quality; + state.networkQuality.value = matchedInfo.quality; } - _handleNetworkQualityChanged(matchedInfo!.quality); + _handleNetworkQualityChanged(matchedInfo.quality); }; super.onUserAudioStateChanged = (userId, hasAudio, reason) { @@ -85,11 +86,6 @@ extension NetowrkInfoManagerWithCallback on NetworkInfoManager { _poorNetworkTimer = null; state.showToast.value = false; } - - void onAudioQualityChanged(TUIAudioQuality quality) { - _service.updateAudioQuality(quality); - state.audioQuality.value = quality; - } } extension on NetworkInfoManager { diff --git a/live/livekit/lib/component/network_info/manager/network_info_service.dart b/live/livekit/lib/component/network_info/manager/network_info_service.dart index f35520bb..1f0e608f 100644 --- a/live/livekit/lib/component/network_info/manager/network_info_service.dart +++ b/live/livekit/lib/component/network_info/manager/network_info_service.dart @@ -45,10 +45,6 @@ class NetworkInfoService { final trtcCloud = await _getTRTCCloud(); trtcCloud.setVideoEncoderParam(params); } - - void updateAudioQuality(TUIAudioQuality quality) { - roomEngine.updateAudioQuality(quality); - } } extension on NetworkInfoService { diff --git a/live/livekit/lib/component/network_info/network_info_widget.dart b/live/livekit/lib/component/network_info/network_info_widget.dart index 9854e7e3..377fef7a 100644 --- a/live/livekit/lib/component/network_info/network_info_widget.dart +++ b/live/livekit/lib/component/network_info/network_info_widget.dart @@ -73,10 +73,6 @@ class _NetworkInfoWidgetState extends State { statusText = LiveKitLocalizations.of(context)!.common_exception; iconName = LiveImages.networkInfoVideoError; break; - default: - statusText = LiveKitLocalizations.of(context)!.common_exception; - iconName = LiveImages.networkInfoVideoError; - break; } return Row( children: [ @@ -109,9 +105,6 @@ class _NetworkInfoWidgetState extends State { case VideoState.exception: detailText = LiveKitLocalizations.of(context)!.common_video_stream_freezing; break; - default: - detailText = LiveKitLocalizations.of(context)!.common_video_stream_freezing; - break; } final videoResolution = widget.manager.state.videoResolution.value; return Padding( @@ -155,9 +148,6 @@ class _NetworkInfoWidgetState extends State { case AudioState.exception: statusText = LiveKitLocalizations.of(context)!.common_exception; break; - default: - statusText = LiveKitLocalizations.of(context)!.common_exception; - break; } return Row( children: [ @@ -176,81 +166,51 @@ class _NetworkInfoWidgetState extends State { ), Visibility( visible: !widget.isAudience, - child: ValueListenableBuilder( - valueListenable: widget.manager.state.audioQuality, - builder: (context, audioQuality, _) { - final audioQuality = widget.manager.state.audioQuality.value; - late String qualityText; - switch (audioQuality) { - case TUIAudioQuality.audioProfileDefault: - qualityText = LiveKitLocalizations.of(context)!.common_audio_mode_default; - break; - case TUIAudioQuality.audioProfileSpeech: - qualityText = LiveKitLocalizations.of(context)!.common_audio_mode_speech; - break; - case TUIAudioQuality.audioProfileMusic: - qualityText = LiveKitLocalizations.of(context)!.common_audio_mode_music; - break; - default: - qualityText = LiveKitLocalizations.of(context)!.common_audio_mode_default; - break; - } - return Padding( - padding: EdgeInsets.only(left: 26.width, top: 4.height), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('${LiveKitLocalizations.of(context)!.common_audio_tips_proper_volume} ', + child: Padding( + padding: EdgeInsets.only(left: 26.width, top: 4.height), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('${LiveKitLocalizations.of(context)!.common_audio_tips_proper_volume} ', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: LiveColors.designStandardFlowkitWhite.withAlpha(0x8C))), + SizedBox(height: 7.height), + ValueListenableBuilder( + valueListenable: widget.manager.state.volume, + builder: (context, volume, _) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 280.width, + height: 14.height, + child: Slider( + value: volume.toDouble(), + min: 0, + max: 100, + activeColor: LiveColors.designSliderColorFilled, + inactiveColor: LiveColors.designSliderColorEmpty, + thumbColor: LiveColors.designStandardFlowkitWhite, + onChanged: (value) { + widget.manager.handleAudioSliderChanged(value.toInt()); + }, + ), + ), + Text( + '$volume', style: TextStyle( - fontSize: 12, + fontSize: 14, fontWeight: FontWeight.w400, - color: LiveColors.designStandardFlowkitWhite.withAlpha(0x8C))), - GestureDetector( - onTap: () => _closeMusicMode(), - child: Text('| $qualityText >', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w400, - color: LiveColors.designStandardFlowkitWhite.withAlpha(0x8C))), - ), - ]), - SizedBox(height: 7.height), - ValueListenableBuilder( - valueListenable: widget.manager.state.volume, - builder: (context, volume, _) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: 280.width, - height: 14.height, - child: Slider( - value: volume.toDouble(), - min: 0, - max: 100, - activeColor: LiveColors.designSliderColorFilled, - inactiveColor: LiveColors.designSliderColorEmpty, - thumbColor: LiveColors.designStandardFlowkitWhite, - onChanged: (value) { - widget.manager.handleAudioSliderChanged(value.toInt()); - }, - ), - ), - Text( - '$volume', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: LiveColors.designStandardFlowkitWhite.withAlpha(0x8C)), - ), - ], - ); - }) - ], - ), - ); - }), + color: LiveColors.designStandardFlowkitWhite.withAlpha(0x8C)), + ), + ], + ); + }) + ], + ), + ), ), SizedBox(height: 24.height), ValueListenableBuilder( @@ -488,27 +448,4 @@ class _NetworkInfo extends StatelessWidget { ], ); } -} - -extension on _NetworkInfoWidgetState { - void _closeMusicMode() { - BaseBottomSheet.showWithHandler( - context, - actions: [ - ActionSheetItem( - title: LiveKitLocalizations.of(context)!.common_audio_mode_default, - onTap: () => widget.manager.onAudioQualityChanged(TUIAudioQuality.audioProfileDefault), - ), - ActionSheetItem( - title: LiveKitLocalizations.of(context)!.common_audio_mode_music, - onTap: () => widget.manager.onAudioQualityChanged(TUIAudioQuality.audioProfileMusic), - ), - ActionSheetItem( - title: LiveKitLocalizations.of(context)!.common_audio_mode_speech, - onTap: () => widget.manager.onAudioQualityChanged(TUIAudioQuality.audioProfileSpeech), - ), - ], - cancelText: LiveKitLocalizations.of(context)!.common_cancel, - ); - } -} +} \ No newline at end of file diff --git a/live/livekit/lib/component/network_info/state/network_info_state.dart b/live/livekit/lib/component/network_info/state/network_info_state.dart index 829b0fc1..c4947f12 100644 --- a/live/livekit/lib/component/network_info/state/network_info_state.dart +++ b/live/livekit/lib/component/network_info/state/network_info_state.dart @@ -6,7 +6,6 @@ class NetWorkInfoState { ValueNotifier videoResolution = ValueNotifier(720); ValueNotifier audioState = ValueNotifier(AudioState.normal); - ValueNotifier audioQuality = ValueNotifier(TUIAudioQuality.audioProfileDefault); ValueNotifier volume = ValueNotifier(50); ValueNotifier rtt = ValueNotifier(0); diff --git a/live/livekit/lib/live_navigator_observer.dart b/live/livekit/lib/live_navigator_observer.dart index 887d59d1..35b8baf1 100644 --- a/live/livekit/lib/live_navigator_observer.dart +++ b/live/livekit/lib/live_navigator_observer.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:tencent_live_uikit/component/float_window/global_float_window_manager.dart'; -import 'package:tencent_live_uikit/live_stream/features/live_room_audience_widget.dart'; -import 'package:tencent_live_uikit/live_stream/features/live_room_audience_overlay.dart'; import 'package:tencent_live_uikit/live_stream/features/index.dart'; import 'package:tencent_live_uikit/voice_room/index.dart'; import 'package:atomic_x_core/atomicxcore.dart'; @@ -23,10 +21,13 @@ class TUILiveKitNavigatorObserver extends RouteObserver { static bool isRepeatClick = false; + final ValueNotifier enteringRoomID = ValueNotifier(''); + late final VoidCallback _onCurrentLiveListener = _onCurrentLiveChanged; + TUILiveKitNavigatorObserver._internal() { LiveKitLogger.info('TUILiveKitNavigatorObserver Init'); Boot.instance; - LiveListStore.shared; + LiveListStore.shared.liveState.currentLive.addListener(_onCurrentLiveListener); } BuildContext getContext() { @@ -44,15 +45,15 @@ class TUILiveKitNavigatorObserver extends RouteObserver { GlobalFloatWindowManager floatWindowManager = GlobalFloatWindowManager.instance; GlobalFloatWindowState state = floatWindowManager.state; if (floatWindowManager.isFloating()) { - if (state.ownerId.value == TUIRoomEngine.getSelfInfo().userId) { - makeToast(context, LiveKitLocalizations.of(Global.appContext())!.livelist_exit_float_window_tip, - type: ToastType.warning); - return false; - } if (state.roomId.value == liveInfo.liveID) { floatWindowManager.switchToFullScreenMode(); return false; } else { + if (state.ownerId.value == TUIRoomEngine.getSelfInfo().userId) { + makeToast(context, LiveKitLocalizations.of(Global.appContext())!.livelist_exit_float_window_tip, + type: ToastType.warning); + return false; + } floatWindowManager.overlayManager.closeOverlay(); } } @@ -80,6 +81,7 @@ class TUILiveKitNavigatorObserver extends RouteObserver { )); return true; } else { + enteringRoomID.value = liveInfo.liveID; Navigator.push( getContext(), MaterialPageRoute( @@ -116,20 +118,21 @@ class TUILiveKitNavigatorObserver extends RouteObserver { GlobalFloatWindowManager floatWindowManager = GlobalFloatWindowManager.instance; GlobalFloatWindowState state = floatWindowManager.state; if (floatWindowManager.isFloating()) { - if (state.ownerId.value == TUIRoomEngine.getSelfInfo().userId) { - isRepeatClick = false; - makeToast(context, LiveKitLocalizations.of(Global.appContext())!.livelist_exit_float_window_tip, - type: ToastType.warning); - return false; - } if (state.roomId.value == liveInfo.liveID) { isRepeatClick = false; floatWindowManager.switchToFullScreenMode(); return false; } else { + if (state.ownerId.value == TUIRoomEngine.getSelfInfo().userId) { + isRepeatClick = false; + makeToast(context, LiveKitLocalizations.of(Global.appContext())!.livelist_exit_float_window_tip, + type: ToastType.warning); + return false; + } floatWindowManager.overlayManager.closeOverlay(); } } + enteringRoomID.value = liveInfo.liveID; Navigator.push( getContext(), MaterialPageRoute( @@ -157,4 +160,12 @@ class TUILiveKitNavigatorObserver extends RouteObserver { return false; }); } + + void _onCurrentLiveChanged() { + final liveInfo = LiveListStore.shared.liveState.currentLive.value; + if (liveInfo.liveID.isNotEmpty) { + LiveKitLogger.info("currentLive is ${liveInfo.liveID}, clear enteringRoomID(${enteringRoomID.value})"); + enteringRoomID.value = ""; + } + } } diff --git a/live/livekit/lib/live_stream/api/index.dart b/live/livekit/lib/live_stream/api/index.dart index e88aa69f..01b999bf 100644 --- a/live/livekit/lib/live_stream/api/index.dart +++ b/live/livekit/lib/live_stream/api/index.dart @@ -1,3 +1,2 @@ -library api; export 'live_stream_service.dart'; \ No newline at end of file diff --git a/live/livekit/lib/live_stream/api/live_stream_service.dart b/live/livekit/lib/live_stream/api/live_stream_service.dart index 77269611..4a39eaac 100644 --- a/live/livekit/lib/live_stream/api/live_stream_service.dart +++ b/live/livekit/lib/live_stream/api/live_stream_service.dart @@ -162,7 +162,7 @@ extension LiveStreamServiceWithMedia on LiveStreamService { try { final jsonString = json.encode(jsonObject); - final result = await roomEngine.invokeExperimentalAPI(jsonString); + await roomEngine.invokeExperimentalAPI(jsonString); } catch (e) { LiveKitLogger.error('Error pauseByAudience'); } @@ -174,7 +174,7 @@ extension LiveStreamServiceWithMedia on LiveStreamService { try { final jsonString = json.encode(jsonObject); - final result = await roomEngine.invokeExperimentalAPI(jsonString); + await roomEngine.invokeExperimentalAPI(jsonString); } catch (e) { LiveKitLogger.error('Error resumeByAudience'); } diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/anchor_broadcast_widget.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/anchor_broadcast_widget.dart index a389a80a..87eb46f6 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/anchor_broadcast_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/anchor_broadcast_widget.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/material.dart'; @@ -35,7 +36,6 @@ class _AnchorBroadcastWidgetState extends State { late final LiveStreamManager liveStreamManager; late final LiveCoreController liveCoreController; late final StreamSubscription _toastSubscription; - late final StreamSubscription _kickedOutSubscription; late final VoidCallback _connectionRequestListener = _handleConnectionRequest; late final VoidCallback _battleRequestListener = _handleBattleRequest; late final VoidCallback _battleWaitingStatusListener = _handleBattleWaitingStatusChanged; @@ -129,13 +129,21 @@ class _AnchorBroadcastWidgetState extends State { ), ); } - return Container( - color: Colors.black, - margin: EdgeInsets.only(top: 120.height), - width: 1.screenWidth, - height: 9 / 16.0 * 1.screenWidth, - child: LiveCoreWidget(controller: widget.liveCoreController), - ); + return LayoutBuilder(builder: (context, constraints) { + final isPortrait = MediaQuery.orientationOf(context) == Orientation.portrait; + final screenWidth = constraints.maxWidth; + final screenHeight = constraints.maxHeight; + final isFloating = widget.liveStreamManager.floatWindowState.isFloatWindowMode.value; + double height = isFloating ? screenHeight : (isPortrait ? 9 / 16.0 * screenWidth : screenHeight); + double top = isFloating ? 0 : (isPortrait ? 120.height : 0); + return Container( + color: isFloating ? Colors.transparent : Colors.black, + margin: EdgeInsets.only(top: top), + width: screenWidth, + height: height, + child: LiveCoreWidget(controller: widget.liveCoreController), + ); + }); } else { return _buildCoreWidget(); } @@ -243,7 +251,7 @@ class _AnchorBroadcastWidgetState extends State { var endInfo = AnchorEndStatisticsWidgetInfo( roomId: liveStreamManager.roomState.roomId, liveDuration: statisticsData.liveDuration, - viewCount: statisticsData.totalViewers, + viewCount: max(statisticsData.totalViewers - 1, 0), messageCount: statisticsData.totalMessageCount, giftIncome: statisticsData.totalGiftCoins, giftSenderCount: statisticsData.totalUniqueGiftSenders, @@ -263,8 +271,10 @@ extension on _AnchorBroadcastWidgetState { liveStreamManager.battleState.isInWaiting.addListener(_battleWaitingStatusListener); liveStreamManager.floatWindowState.isFloatWindowMode.addListener(_isFloatWindowModeListener); - _toastSubscription = liveStreamManager.toastSubject.stream.listen((toast) => makeToast(context, toast)); - _kickedOutSubscription = liveStreamManager.kickedOutSubject.stream.listen((_) => _handleKickedOut()); + _toastSubscription = liveStreamManager.toastSubject.stream.listen((toast) { + if (!mounted) return; + makeToast(context, toast); + }); } void _removeObserver() { @@ -276,7 +286,6 @@ extension on _AnchorBroadcastWidgetState { liveStreamManager.floatWindowState.isFloatWindowMode.removeListener(_isFloatWindowModeListener); _toastSubscription.cancel(); - _kickedOutSubscription.cancel(); } void _handleConnectionRequest() { @@ -417,8 +426,6 @@ extension on _AnchorBroadcastWidgetState { } } - void _handleKickedOut() {} - void _isFloatWindowModeChanged() { bool isFloatWindowMode = liveStreamManager.floatWindowState.isFloatWindowMode.value; _connectRequestAlertHandler?.setContentVisible(!isFloatWindowMode); diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/battle/battle_count_down_widget.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/battle/battle_count_down_widget.dart index 767a34a0..ee860697 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/battle/battle_count_down_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/battle/battle_count_down_widget.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; import 'package:tencent_live_uikit/live_stream/features/anchor_broadcast/battle/battle_count_down_backgroud_widget.dart'; diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_float_widget.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_float_widget.dart index 4cbb6fc8..7380ace5 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_float_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_float_widget.dart @@ -128,7 +128,7 @@ class _AnchorCoGuestFloatWidgetState extends State { child: ClipOval( child: Image.network( _getCoGuestState().applicants.value.length > 1 - ? _getCoGuestState().applicants.value.toList()[1].avatarURL ?? "" + ? _getCoGuestState().applicants.value.toList()[1].avatarURL : "", fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_user_management_panel.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_user_management_panel.dart index ff978aaf..64b1fbcc 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_user_management_panel.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/anchor_co_guest_user_management_panel.dart @@ -154,6 +154,28 @@ class _AnchorCoGuestUserManagementPanelState extends State _featuredHostButtonClicked(), + ); + }, + ), + ); + } + // kickout children.add(CommonMenuWidget( imageName: LiveImages.leaveSeat, @@ -200,22 +222,6 @@ extension on _AnchorCoGuestUserManagementPanelState { widget.closeCallback.call(); } - void _localCameraButtonClicked() { - final isCameraOpened = _isCameraOpened.value; - if (isCameraOpened) { - widget.liveStreamManager.mediaManager.closeLocalCamera(); - } else { - final isFrontCamera = DeviceStore.shared.state.isFrontCamera.value; - widget.liveStreamManager.mediaManager.openLocalCamera(isFrontCamera).then((result) { - if (result.code != TUIError.success) { - widget.liveStreamManager.toastSubject - .add(ErrorHandler.convertToErrorMessage(result.code.rawValue, result.message) ?? ''); - } - }); - } - widget.closeCallback.call(); - } - void _flipButtonClicked() { DeviceStore.shared.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); widget.closeCallback.call(); @@ -280,4 +286,23 @@ extension on _AnchorCoGuestUserManagementPanelState { bool _isVideoLocked() { return widget.liveStreamManager.coGuestState.lockVideoUserList.value.contains(user.userID); } + + bool _isUserFeaturedHost() { + return liveSeatStore.liveSeatState.seatList.value + .any((seat) => seat.userInfo.userID == user.userID && seat.isFeaturedHost); + } + + void _featuredHostButtonClicked() { + final isFeaturedHost = _isUserFeaturedHost(); + final future = isFeaturedHost + ? liveSeatStore.revokeFeaturedHost(user.userID) + : liveSeatStore.setFeaturedHost(user.userID); + future.then((result) { + if (result.errorCode != TUIError.success.rawValue) { + widget.liveStreamManager.toastSubject + .add(ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? ''); + } + }); + widget.closeCallback.call(); + } } diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/co_guest_management_panel_widget.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/co_guest_management_panel_widget.dart index 92616dac..eb3802d7 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/co_guest_management_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/co_guest/co_guest_management_panel_widget.dart @@ -206,7 +206,7 @@ class _CoGuestManagePanelWidgetState extends State { height: 40.radius, child: ClipOval( child: Image.network( - seat.userInfo.avatarURL ?? "", + seat.userInfo.avatarURL, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Image.asset( diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/co_host/co_host_management_panel_widget.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/co_host/co_host_management_panel_widget.dart index 8fe00691..a9cc6b7b 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/co_host/co_host_management_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/co_host/co_host_management_panel_widget.dart @@ -1,5 +1,4 @@ import 'package:atomic_x_core/api/live/co_host_store.dart'; -import 'package:atomic_x_core/api/live/live_list_store.dart'; import 'package:atomic_x_core/api/live/live_seat_store.dart'; import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/error/error_handler.dart'; @@ -403,10 +402,6 @@ extension on _CoHostManagementPanelWidgetState { return TUIRoomEngine.getSelfInfo().userId; } - String _getLiveID() { - return liveStreamManager.roomState.roomId; - } - void _addObserver() { _scrollController.addListener(_loadMore); } @@ -516,4 +511,4 @@ extension on _CoHostManagementPanelWidgetState { } liveStreamManager.onRequestCrossRoomConnectionFailed(user.roomId); } -} +} \ No newline at end of file diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/common/anchor_user_management_panel_base.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/common/anchor_user_management_panel_base.dart index f140de5a..9db3b324 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/common/anchor_user_management_panel_base.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/common/anchor_user_management_panel_base.dart @@ -6,19 +6,20 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.da import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; import 'package:tencent_live_uikit/common/index.dart'; import 'package:tencent_live_uikit/live_stream/manager/live_stream_manager.dart'; -import 'package:tencent_live_uikit/tencent_live_uikit.dart'; import 'package:tencent_live_uikit/component/live_info/state/follow_define.dart'; class AnchorUserManagementPanelBase extends StatefulWidget { final LiveUserInfo user; final LiveStreamManager liveStreamManager; final Widget? child; + final bool? enableFollow; const AnchorUserManagementPanelBase({ super.key, required this.user, required this.liveStreamManager, this.child, + this.enableFollow, }); @override @@ -27,7 +28,6 @@ class AnchorUserManagementPanelBase extends StatefulWidget { class _AnchorUserManagementPanelBaseState extends State { final ValueNotifier _isFollow = ValueNotifier(false); - bool _enableFollowButton = true; @override void initState() { @@ -59,6 +59,7 @@ class _AnchorUserManagementPanelBaseState extends State { late final LiveListListener _liveListListener; late final LiveSummaryStore _liveSummaryStore; late final VoidCallback _onScreenShareStatusListener = _onScreenShareStatusChanged; + late final VoidCallback _loginStatusListener = _onLoginStatusChanged; + late final StreamSubscription _loginEventSubscription; + bool _isLiveEndedByServer = false; @override void initState() { @@ -61,12 +64,12 @@ class _AnchorLivingWidgetState extends State { onLiveEnded: (String liveID, LiveEndedReason reason, String message) { if (liveID != liveStreamManager.roomState.roomId) return; if (reason == LiveEndedReason.endedByServer) { + _isLiveEndedByServer = true; widget.onEndLive.call(_buildStatisticsFromSummary(), LiveEndedReason.endedByServer); liveStreamManager.onStopLive(); } }, onKickedOutOfLive: (String liveID, LiveKickedOutReason reason, String message) { - // TODO: LiveListStore not call onKickedOutOfLive if (liveID != liveStreamManager.roomState.roomId) return; _closePage(); }, @@ -75,10 +78,20 @@ class _AnchorLivingWidgetState extends State { coHostStore = CoHostStore.create(liveStreamManager.roomState.roomId); battleStore = BattleStore.create(liveStreamManager.roomState.roomId); _addObserver(); + _loginEventSubscription = LoginStore.shared.loginEventStream.listen((event) { + switch (event) { + case LoginEvent.kickedOffline: + case LoginEvent.loginExpired: + LiveKitLogger.warning("LoginEvent => $event"); + _closePage(); + break; + } + }); } @override void dispose() { + _loginEventSubscription.cancel(); _giftPlayController?.dispose(); _networkInfoManager.dispose(); _removeObserver(); @@ -103,6 +116,14 @@ class _AnchorLivingWidgetState extends State { _closePanelSheetHandler?.close(); } + void _onLoginStatusChanged() { + final loginStatus = LoginStore.shared.loginState.loginStatus; + if (loginStatus == LoginStatus.unlogin) { + LiveKitLogger.warning("LoginStatus => unlogin"); + _closePage(); + } + } + void _closePage() { if (GlobalFloatWindowManager.instance.isEnableFloatWindowFeature()) { GlobalFloatWindowManager.instance.overlayManager.closeOverlay(); @@ -285,7 +306,10 @@ class _AnchorLivingWidgetState extends State { roomId: liveStreamManager.roomState.roomId, ownerId: liveStreamManager.roomState.liveInfo.liveOwner.userID, selfUserId: TUIRoomEngine.getSelfInfo().userId, - selfName: TUIRoomEngine.getSelfInfo().userName); + selfName: TUIRoomEngine.getSelfInfo().userName, + onError: (code, message) { + makeToast(context, ErrorHandler.convertToErrorMessage(code, message) ?? '', type: ToastType.error); + }); _barrageDisplayController ?.setCustomBarrageBuilder(GiftBarrageItemBuilder(selfUserId: TUIRoomEngine.getSelfInfo().userId)); } @@ -379,6 +403,7 @@ class _AnchorLivingWidgetState extends State { extension on _AnchorLivingWidgetState { void _addObserver() { + LoginStore.shared.addListener(_loginStatusListener); DeviceStore.shared.state.screenStatus.addListener(_onScreenShareStatusListener); liveListStore.addLiveListListener(_liveListListener); liveStreamManager.userState.enterUser.addListener(_userEnterRoomListener); @@ -386,6 +411,7 @@ extension on _AnchorLivingWidgetState { } void _removeObserver() { + LoginStore.shared.removeListener(_loginStatusListener); DeviceStore.shared.state.screenStatus.removeListener(_onScreenShareStatusListener); liveListStore.removeLiveListListener(_liveListListener); liveStreamManager.userState.enterUser.removeListener(_userEnterRoomListener); @@ -499,15 +525,12 @@ extension on _AnchorLivingWidgetState { } void _stopLiveStream() async { + if (_isLiveEndedByServer) return; battleStore.exitBattle(liveStreamManager.battleState.battleId.value); final isObsBroadcast = !liveStreamManager.roomState.liveInfo.keepOwnerOnSeat; if (isObsBroadcast) { liveListStore.leaveLive(); - if (GlobalFloatWindowManager.instance.isEnableFloatWindowFeature()) { - GlobalFloatWindowManager.instance.overlayManager.closeOverlay(); - } else { - Navigator.of(context).pop(); - } + _closePage(); } else { if (liveStreamManager.roomState.videoStreamSource == VideoStreamSource.screenShare) { liveStreamManager.mediaManager.stopScreenShare(); @@ -517,6 +540,7 @@ extension on _AnchorLivingWidgetState { _giftPlayController?.dispose(); final result = await future; + if (_isLiveEndedByServer) return; if (result.errorCode != TUIError.success.rawValue) { liveStreamManager.toastSubject .add(ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? ''); diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/index.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/index.dart index 00a47ed0..7973cc73 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/index.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/index.dart @@ -1,3 +1,2 @@ -library living_widget; export 'anchor_living_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/more_features_panel_widget.dart b/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/more_features_panel_widget.dart index 646dc4e1..08285426 100644 --- a/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/more_features_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_broadcast/living_widget/more_features_panel_widget.dart @@ -8,6 +8,7 @@ import 'package:tencent_live_uikit/component/float_window/pip_config_panel_widge import '../../../../component/audio_effect/index.dart'; import '../../../../component/beauty/index.dart'; +import '../../../../component/bgm/index.dart'; import '../../../../component/float_window/global_float_window_manager.dart'; import '../../../manager/live_stream_manager.dart'; @@ -26,6 +27,7 @@ class _MoreFeaturesPanelWidgetState extends State { BottomSheetHandler? _beautySheetHandler; BottomSheetHandler? _audioEffectSheetHandler; + BottomSheetHandler? _bgmSheetHandler; BottomSheetHandler? _pipConfigPanelHandler; @override @@ -44,6 +46,7 @@ class _MoreFeaturesPanelWidgetState extends State { void _closeAllDialog() { _beautySheetHandler?.close(); _audioEffectSheetHandler?.close(); + _bgmSheetHandler?.close(); _pipConfigPanelHandler?.close(); } @@ -51,13 +54,16 @@ class _MoreFeaturesPanelWidgetState extends State { Widget build(BuildContext context) { return SizedBox( width: 1.screenWidth, - height: 200.height, - child: Column(children: [ - SizedBox(height: 24.height), - _buildTitleWidget(), - SizedBox(height: 24.height), - _buildFeaturesListWidget() - ]), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 24.height), + _buildTitleWidget(), + SizedBox(height: 24.height), + _buildFeaturesListWidget(), + SizedBox(height: 24.height), + ], + ), ); } @@ -79,53 +85,73 @@ class _MoreFeaturesPanelWidgetState extends State { } Widget _buildFeaturesListWidget() { + // Each row shows up to 5 items. Items are placed into fixed grid slots + // (5 slots per row) so that an item's horizontal center is identical + // across rows. When the last row is not full, empty slots are appended + // on the right side, which makes the remaining items align from left + // to right while every item still has equal spacing on both sides. + const int itemsPerRow = 5; + final List> rows = []; + for (int i = 0; i < list.length; i += itemsPerRow) { + final List row = []; + for (int j = 0; j < itemsPerRow; j++) { + final int idx = i + j; + row.add(idx < list.length ? idx : null); + } + rows.add(row); + } return SizedBox( width: 1.screenWidth, - height: 85.width, - child: Center( - child: ListView.separated( - shrinkWrap: true, - physics: const AlwaysScrollableScrollPhysics(), - scrollDirection: Axis.horizontal, - separatorBuilder: (context, index) => SizedBox(width: 12.width), - itemCount: list.length, - itemBuilder: (context, index) { - return GestureDetector( - onTap: () => _onTapIndex(index), - child: SizedBox( - width: 56.width, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 56.width, - height: 56.width, - decoration: BoxDecoration( - color: LiveColors.notStandardBlue30Transparency, - border: Border.all(color: LiveColors.notStandardBlue30Transparency), - borderRadius: BorderRadius.circular(10.radius), - ), - child: Center( - child: SizedBox( - width: 30.width, - height: 30.width, - child: Image.asset( - list[index].icon, - package: Constants.pluginName, - ), - ), - ), - ), - SizedBox(height: 6.height), - Text( - list[index].title, - style: const TextStyle(color: LiveColors.designStandardG7, fontSize: 12), - ), - ], + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int r = 0; r < rows.length; r++) ...[ + if (r > 0) SizedBox(height: 16.height), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: rows[r].map((idx) { + return idx == null ? SizedBox(width: 56.width) : _buildFeatureItem(idx); + }).toList(), + ), + ], + ], + ), + ); + } + + Widget _buildFeatureItem(int index) { + return GestureDetector( + onTap: () => _onTapIndex(index), + child: SizedBox( + width: 56.width, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 56.width, + height: 56.width, + decoration: BoxDecoration( + color: LiveColors.notStandardBlue30Transparency, + border: Border.all(color: LiveColors.notStandardBlue30Transparency), + borderRadius: BorderRadius.circular(10.radius), + ), + child: Center( + child: SizedBox( + width: 30.width, + height: 30.width, + child: Image.asset( + list[index].icon, + package: Constants.pluginName, + ), ), ), - ); - }, + ), + SizedBox(height: 6.height), + Text( + list[index].title, + style: const TextStyle(color: LiveColors.designStandardG7, fontSize: 12), + ), + ], ), ), ); @@ -141,12 +167,21 @@ extension on _MoreFeaturesPanelWidgetState { context: context, barrierColor: LiveColors.designStandardTransparent); break; case FeaturesItemType.audioEffect: - _audioEffectSheetHandler = - popupWidget(context: context, AudioEffectPanelWidget( + _audioEffectSheetHandler = popupWidget( + context: context, + AudioEffectPanelWidget( roomId: liveStreamManager.roomState.roomId, onDone: () => _audioEffectSheetHandler?.close(), )); break; + case FeaturesItemType.music: + _bgmSheetHandler = popupWidget( + context: context, + BGMPanelWidget( + roomId: liveStreamManager.roomState.roomId, + ), + ); + break; case FeaturesItemType.flip: DeviceStore.shared.switchCamera(!DeviceStore.shared.state.isFrontCamera.value); break; @@ -158,8 +193,6 @@ extension on _MoreFeaturesPanelWidgetState { case FeaturesItemType.pip: _showPipConfigPanel(); break; - default: - break; } } @@ -203,8 +236,12 @@ extension on _MoreFeaturesPanelWidgetState { type: FeaturesItemType.beauty), FeaturesItem( title: LiveKitLocalizations.of(Global.appContext())!.common_audio_effect, - icon: LiveImages.settingsItemMusic, + icon: LiveImages.settingsItemAudioEffect, type: FeaturesItemType.audioEffect), + FeaturesItem( + title: LiveKitLocalizations.of(Global.appContext())!.common_music, + icon: LiveImages.settingsItemMusic, + type: FeaturesItemType.music), FeaturesItem( title: LiveKitLocalizations.of(Global.appContext())!.common_video_settings_item_flip, icon: LiveImages.settingsItemFlip, @@ -223,7 +260,7 @@ extension on _MoreFeaturesPanelWidgetState { } } -enum FeaturesItemType { beauty, audioEffect, flip, mirror, pip } +enum FeaturesItemType { beauty, audioEffect, music, flip, mirror, pip } class FeaturesItem { String title; diff --git a/live/livekit/lib/live_stream/features/anchor_prepare/index.dart b/live/livekit/lib/live_stream/features/anchor_prepare/index.dart index 0e6de009..4f9f4acb 100644 --- a/live/livekit/lib/live_stream/features/anchor_prepare/index.dart +++ b/live/livekit/lib/live_stream/features/anchor_prepare/index.dart @@ -1,4 +1,3 @@ -library anchor_prepare; export 'widgets/anchor_preview_function_widget.dart'; export 'widgets/anchor_preview_info_edit_widget.dart'; diff --git a/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_info_edit_widget.dart b/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_info_edit_widget.dart index 7b224377..7da3d4ac 100644 --- a/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_info_edit_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_info_edit_widget.dart @@ -268,8 +268,6 @@ extension on _AnchorPreviewInfoEditWidgetState { return LiveKitLocalizations.of(Global.appContext())!.common_stream_privacy_status_default; case LiveStreamPrivacyStatus.privacy: return LiveKitLocalizations.of(Global.appContext())!.common_stream_privacy_status_privacy; - default: - return LiveKitLocalizations.of(Global.appContext())!.common_stream_privacy_status_default; } } } diff --git a/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_video_setting_panel_widget.dart b/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_video_setting_panel_widget.dart index cf3af99c..5b8b36e5 100644 --- a/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_video_setting_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_prepare/widgets/anchor_preview_video_setting_panel_widget.dart @@ -2,7 +2,6 @@ import 'package:atomic_x_core/api/device/device_store.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; -import 'package:tencent_live_uikit/tencent_live_uikit.dart'; import '../../../../common/widget/base_bottom_sheet.dart'; import '../../../manager/live_stream_manager.dart'; @@ -151,8 +150,6 @@ extension on _AnchorPreviewVideoSettingPanelWidgetState { return '540P'; case VideoQuality.quality360P: return '360P'; - default: - return 'unknown'; } } diff --git a/live/livekit/lib/live_stream/features/anchor_prepare/widgets/video_stream_source_widget.dart b/live/livekit/lib/live_stream/features/anchor_prepare/widgets/video_stream_source_widget.dart index ebf7850f..c39a7b7b 100644 --- a/live/livekit/lib/live_stream/features/anchor_prepare/widgets/video_stream_source_widget.dart +++ b/live/livekit/lib/live_stream/features/anchor_prepare/widgets/video_stream_source_widget.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; import 'package:tencent_live_uikit/live_stream/live_define.dart'; diff --git a/live/livekit/lib/live_stream/features/audience/audience_widget.dart b/live/livekit/lib/live_stream/features/audience/audience_widget.dart index 52baad5a..58b6135e 100644 --- a/live/livekit/lib/live_stream/features/audience/audience_widget.dart +++ b/live/livekit/lib/live_stream/features/audience/audience_widget.dart @@ -1,7 +1,6 @@ -import 'dart:async'; - import 'package:atomic_x_core/api/live/live_audience_store.dart'; import 'package:atomic_x_core/api/live/live_list_store.dart'; +import 'package:atomic_x_core/api/live/live_seat_store.dart'; import 'package:atomic_x_core/api/view/live/live_core_widget.dart'; import 'package:flutter/material.dart'; import 'package:live_uikit_barrage/live_uikit_barrage.dart'; @@ -11,6 +10,7 @@ import 'package:tencent_live_uikit/common/widget/base_bottom_sheet.dart'; import 'package:tencent_live_uikit/live_navigator_observer.dart'; import 'package:tencent_live_uikit/live_stream/features/audience/living_widget/audience_empty_seat_widget.dart'; import 'package:tencent_live_uikit/live_stream/features/audience/living_widget/audience_living_widget.dart'; +import 'package:tencent_live_uikit/live_stream/features/audience/living_widget/host_absent_widget.dart'; import 'package:tencent_live_uikit/live_stream/features/audience/panel/audience_user_info_panel_widget.dart'; import 'package:tencent_live_uikit/live_stream/features/audience/panel/audience_user_management_panel_widget.dart'; import 'package:tencent_live_uikit/live_stream/features/audience/panel/co_guest_type_select_panel_widget.dart'; @@ -51,15 +51,14 @@ class AudienceWidget extends StatefulWidget { class _AudienceWidgetState extends State { late final VoidCallback _liveStatusListener = _onLiveStatusChange; - - late final VoidCallback _audioLockedListener = _onAudioLockedStatusChanged; - late final VoidCallback _videoLockedListener = _onVideoLockedStatusChanged; late final VoidCallback _coGuestStatusListener = _onCoGuestStatusChanged; - late final StreamSubscription _kickedOutSubscription; + late final VoidCallback _isFloatWindowModeListener = _isFloatWindowModeChanged; + BottomSheetHandler? _audienceUserInfoPanelHandler; BottomSheetHandler? _audienceUserManagementPanelHandler; late final LiveListListener _liveListListener; + late final LiveSeatListener _liveSeatListener; @override void initState() { @@ -124,46 +123,85 @@ class _AudienceWidgetState extends State { final screenWidth = constraints.maxWidth; final screenHeight = constraints.maxHeight; final isFloating = widget.liveStreamManager.floatWindowState.isFloatWindowMode.value; - double height = isPortrait ? 9 / 16.0 * screenWidth : screenHeight; - double top = isFloating ? (screenHeight - height) / 2 : (isPortrait ? 120.height : 0); + double height = isFloating ? screenHeight : (isPortrait ? 9 / 16.0 * screenWidth : screenHeight); + double top = isFloating ? 0 : (isPortrait ? 120.height : 0); return Container( - color: Colors.black, + color: isFloating ? Colors.transparent : Colors.black, margin: EdgeInsets.only(top: top), width: screenWidth, height: height, - child: LiveCoreWidget(controller: widget.liveCoreController), + child: Stack( + children: [ + LiveCoreWidget(controller: widget.liveCoreController), + HostAbsentWidget(liveStreamManager: widget.liveStreamManager), + ], + ), ); }); } else { - return LiveCoreWidget( - controller: widget.liveCoreController, - videoWidgetBuilder: VideoWidgetBuilder( - coGuestWidgetBuilder: _createCoGuestWidgetBuilder(), - coHostWidgetBuilder: (context, seatInfo, viewLayer) { - if (viewLayer == ViewLayer.background) { - return CoHostBackgroundWidget( - seatInfo: seatInfo, isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode); - } else { - return CoHostForegroundWidget( - seatInfo: seatInfo, isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode); - } - }, - battleWidgetBuilder: (context, seatInfo) { - return BattleMemberInfoWidget( - liveStreamManager: widget.liveStreamManager, - battleUserId: seatInfo.userInfo.userID, - isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode); - }, - battleContainerWidgetBuilder: (context) { - return BattleInfoWidget( - liveStreamManager: widget.liveStreamManager, - isOwner: false, - isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode); - }), + return Stack( + children: [ + LiveCoreWidget( + controller: widget.liveCoreController, + videoWidgetBuilder: VideoWidgetBuilder( + coGuestWidgetBuilder: _createCoGuestWidgetBuilder(), + coHostWidgetBuilder: (context, seatInfo, viewLayer) { + if (viewLayer == ViewLayer.background) { + return CoHostBackgroundWidget( + seatInfo: seatInfo, + isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode); + } else { + return CoHostForegroundWidget( + seatInfo: seatInfo, + isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode, + onTap: () => _onTapCoHostForegroundWidget(seatInfo), + ); + } + }, + battleWidgetBuilder: (context, seatInfo) { + return BattleMemberInfoWidget( + liveStreamManager: widget.liveStreamManager, + battleUserId: seatInfo.userInfo.userID, + isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode); + }, + battleContainerWidgetBuilder: (context) { + return BattleInfoWidget( + liveStreamManager: widget.liveStreamManager, + isOwner: false, + isFloatWindowMode: widget.liveStreamManager.floatWindowState.isFloatWindowMode); + }), + ), + HostAbsentWidget(liveStreamManager: widget.liveStreamManager), + ], ); } } + void _onTapCoHostForegroundWidget(SeatInfo seatInfo) { + LiveUserInfo userInfo = LiveUserInfo(); + userInfo.userID = seatInfo.userInfo.userID; + userInfo.userName = seatInfo.userInfo.userName; + userInfo.avatarURL = seatInfo.userInfo.avatarURL; + _audienceUserInfoPanelHandler = popupWidget( + AudienceUserInfoPanelWidget( + user: userInfo, + liveID: seatInfo.userInfo.liveID, + liveStreamManager: widget.liveStreamManager, + enableEnterRoom: LiveListStore.shared.liveState.currentLive.value.liveID != seatInfo.userInfo.liveID, + onClose: () { + _audienceUserInfoPanelHandler?.close(); + _audienceUserInfoPanelHandler = null; + }, + onExitRoom: () { + _audienceUserInfoPanelHandler?.close(); + _audienceUserInfoPanelHandler = null; + _closePage(); + }, + ), + context: context, + backgroundColor: LiveColors.designStandardTransparent); + } + Widget _buildSeatListWidget() { if (!widget.liveStreamManager.roomManager.isScreenShareLive()) { return const SizedBox.shrink(); @@ -286,28 +324,53 @@ class _AudienceWidgetState extends State { extension on _AudienceWidgetState { void _init() { - _liveListListener = LiveListListener(onLiveEnded: (String liveID, LiveEndedReason reason, String message) { - _closeAllDialog(); - }); + _liveListListener = LiveListListener( + onLiveEnded: (String liveID, LiveEndedReason reason, String message) { + _closeAllDialog(); + }, + onKickedOutOfLive: (String liveID, LiveKickedOutReason reason, String message) { + _handleKickedOut(); + }, + ); LiveListStore.shared.addLiveListListener(_liveListListener); - _kickedOutSubscription = widget.liveStreamManager.kickedOutSubject.stream.listen((_) => _handleKickedOut()); // Defer _joinLiveStream to avoid setState() during build phase. WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; _joinLiveStream(); }); _addLiveStatusListener(); - _addMediaLockedListener(); _addCoGuestStatusListener(); + widget.liveStreamManager.floatWindowState.isFloatWindowMode.addListener(_isFloatWindowModeListener); + _liveSeatListener = LiveSeatListener(onLocalCameraClosedByAdmin: () { + widget.liveStreamManager.mediaManager.closeLocalCamera(); + if (!mounted) return; + final toastMessage = LiveKitLocalizations.of(context)!.common_mute_video_by_owner; + makeToast(context, toastMessage); + }, onLocalCameraOpenedByAdmin: (DeviceControlPolicy policy) { + if (policy == DeviceControlPolicy.unlockOnly) { + if (!mounted) return; + final toastMessage = LiveKitLocalizations.of(context)!.common_un_mute_video_by_master; + makeToast(context, toastMessage); + } + }, onLocalMicrophoneClosedByAdmin: () { + if (!mounted) return; + final toastMessage = LiveKitLocalizations.of(context)!.common_mute_audio_by_master; + makeToast(context, toastMessage); + }, onLocalMicrophoneOpenedByAdmin: (DeviceControlPolicy policy) { + if (policy == DeviceControlPolicy.unlockOnly) { + if (!mounted) return; + final toastMessage = LiveKitLocalizations.of(context)!.common_un_mute_audio_by_master; + makeToast(context, toastMessage); + } + }); + LiveSeatStore.create(widget.roomId).addLiveSeatEventListener(_liveSeatListener); } void _dispose() { widget.onDispose?.call(); LiveListStore.shared.removeLiveListListener(_liveListListener); _closeAllDialog(); - _kickedOutSubscription.cancel(); _removeCoGuestStatusListener(); - _removeMediaLockedListener(); _removeLiveStatusListener(); // Reset liveStatus to prevent stale state when PageResources are reused. // Without this, a cached page returning to current index would see @@ -318,6 +381,8 @@ extension on _AudienceWidgetState { _leaveLiveStream(); }); _resetControllers(); + widget.liveStreamManager.floatWindowState.isFloatWindowMode.removeListener(_isFloatWindowModeListener); + LiveSeatStore.create(widget.roomId).removeLiveSeatEventListener(_liveSeatListener); } void _handleKickedOut() { @@ -327,7 +392,9 @@ extension on _AudienceWidgetState { void _closeAllDialog() { _audienceUserInfoPanelHandler?.close(); + _audienceUserInfoPanelHandler = null; _audienceUserManagementPanelHandler?.close(); + _audienceUserManagementPanelHandler = null; } void _closePage() { @@ -345,6 +412,7 @@ extension on _AudienceWidgetState { LiveListStore liveListStore = LiveListStore.shared; var result = await liveListStore.joinLive(widget.roomId); widget.onJoinLiveStateChanged?.call(false); + if (!mounted) return; if (result.errorCode != TUIError.success.rawValue) { makeToast(context, ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? '', type: ToastType.error, useRootOverlay: true); @@ -353,6 +421,7 @@ extension on _AudienceWidgetState { } widget.liveStreamManager.onJoinLive(result.liveInfo); } catch (e) { + if (!mounted) return; widget.onJoinLiveStateChanged?.call(false); LiveKitLogger.error('AudienceWidget _joinLiveStream error: $e'); makeToast(context, ErrorHandler.convertToErrorMessage(-1, e.toString()) ?? '', @@ -363,6 +432,10 @@ extension on _AudienceWidgetState { void _leaveLiveStream() { LiveListStore liveListStore = LiveListStore.shared; + if (liveListStore.liveState.currentLive.value.liveID.isEmpty) { + LiveKitLogger.warning('AudienceWidget _leaveLiveStream currentLive is Empty'); + return; + } liveListStore.leaveLive(); } @@ -374,16 +447,6 @@ extension on _AudienceWidgetState { widget.liveStreamManager.roomState.liveStatus.removeListener(_liveStatusListener); } - void _addMediaLockedListener() { - widget.liveStreamManager.mediaState.isAudioLocked.addListener(_audioLockedListener); - widget.liveStreamManager.mediaState.isVideoLocked.addListener(_videoLockedListener); - } - - void _removeMediaLockedListener() { - widget.liveStreamManager.mediaState.isAudioLocked.removeListener(_audioLockedListener); - widget.liveStreamManager.mediaState.isVideoLocked.removeListener(_videoLockedListener); - } - void _resetControllers() { BarrageDisplayController.resetState(); } @@ -417,19 +480,9 @@ extension on _AudienceWidgetState { widget.onCoGuestStateChanged?.call(shouldDisableScroll); } - void _onAudioLockedStatusChanged() { - final isAudioLocked = widget.liveStreamManager.mediaState.isAudioLocked.value; - final toastMessage = isAudioLocked - ? LiveKitLocalizations.of(context)!.common_mute_audio_by_master - : LiveKitLocalizations.of(context)!.common_un_mute_audio_by_master; - makeToast(context, toastMessage); - } - - void _onVideoLockedStatusChanged() { - final isVideoLocked = widget.liveStreamManager.mediaState.isVideoLocked.value; - final toastMessage = isVideoLocked - ? LiveKitLocalizations.of(context)!.common_mute_video_by_owner - : LiveKitLocalizations.of(context)!.common_un_mute_video_by_master; - makeToast(context, toastMessage); + void _isFloatWindowModeChanged() { + if (widget.liveStreamManager.floatWindowState.isFloatWindowMode.value) { + _closeAllDialog(); + } } } diff --git a/live/livekit/lib/live_stream/features/audience/index.dart b/live/livekit/lib/live_stream/features/audience/index.dart index 1dca4f13..eff9d771 100644 --- a/live/livekit/lib/live_stream/features/audience/index.dart +++ b/live/livekit/lib/live_stream/features/audience/index.dart @@ -1,3 +1,2 @@ -library audience; export 'audience_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/live_stream/features/audience/living_widget/audience_bottom_menu_widget.dart b/live/livekit/lib/live_stream/features/audience/living_widget/audience_bottom_menu_widget.dart index 9e620792..a423ee68 100644 --- a/live/livekit/lib/live_stream/features/audience/living_widget/audience_bottom_menu_widget.dart +++ b/live/livekit/lib/live_stream/features/audience/living_widget/audience_bottom_menu_widget.dart @@ -1,4 +1,3 @@ -import 'package:atomic_x_core/api/live/co_host_store.dart'; import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/material.dart'; import 'package:live_uikit_barrage/live_uikit_barrage.dart'; diff --git a/live/livekit/lib/live_stream/features/audience/living_widget/audience_living_widget.dart b/live/livekit/lib/live_stream/features/audience/living_widget/audience_living_widget.dart index 32dcbda5..fc1a0b08 100644 --- a/live/livekit/lib/live_stream/features/audience/living_widget/audience_living_widget.dart +++ b/live/livekit/lib/live_stream/features/audience/living_widget/audience_living_widget.dart @@ -1,8 +1,9 @@ +import 'dart:async'; + import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:tencent_live_uikit/common/index.dart'; -import 'package:tencent_live_uikit/component/float_window/global_float_window_manager.dart'; import 'package:tencent_live_uikit/component/index.dart'; import 'package:tencent_live_uikit/component/network_info/manager/network_info_manager.dart'; import 'package:tencent_live_uikit/live_stream/features/audience/living_widget/audience_bottom_menu_widget.dart'; @@ -14,9 +15,9 @@ import 'package:tuikit_atomic_x/base_component/basic_controls/alert_dialog.dart' import 'package:tuikit_atomic_x/base_component/basic_controls/toast.dart'; import '../../../../common/widget/base_bottom_sheet.dart'; -import '../../../../component/network_info/index.dart'; import '../../../live_define.dart'; import '../../../manager/module/user_manager.dart'; +import '../panel/admin_user_management_for_audience_panel.dart'; import '../panel/audience_user_info_panel_widget.dart'; import './player_menu_widget.dart'; @@ -40,8 +41,13 @@ class _AudienceLivingWidgetState extends State { late final VoidCallback _playbackVideoQualityChangedListener = _onPlaybackVideoQualityChanged; TUIVideoQuality? playbackQuality; AlertHandler? _closePanelSheetHandler; + BottomSheetHandler? _adminUserManagementPanelHandler; + BottomSheetHandler? _audienceUserInfoPanelHandler; + BottomSheetHandler? _playerMenuPanelHandler; late final LiveListListener _liveListListener; + late final VoidCallback _loginStatusListener = _onLoginStatusChanged; + late final StreamSubscription _loginEventSubscription; @override void initState() { @@ -53,10 +59,23 @@ class _AudienceLivingWidgetState extends State { _closeAllDialog(); }); LiveListStore.shared.addLiveListListener(_liveListListener); + LoginStore.shared.addListener(_loginStatusListener); + _loginEventSubscription = LoginStore.shared.loginEventStream.listen((event) { + switch (event) { + case LoginEvent.kickedOffline: + case LoginEvent.loginExpired: + LiveKitLogger.warning("LoginEvent => $event"); + _closePage(); + break; + } + }); } @override void dispose() { + _closeAllDialog(); + _loginEventSubscription.cancel(); + LoginStore.shared.removeListener(_loginStatusListener); LiveListStore.shared.removeLiveListListener(_liveListListener); _giftPlayController?.dispose(); _networkInfoManager.dispose(); @@ -122,8 +141,10 @@ class _AudienceLivingWidgetState extends State { onClickUserItem: (user) { final isSelf = TUIRoomEngine.getSelfInfo().userId == user.userID; if (!isSelf) { - popupWidget(AudienceUserInfoPanelWidget(user: user, liveStreamManager: widget.liveStreamManager), - context: context, backgroundColor: LiveColors.designStandardTransparent); + _audienceUserInfoPanelHandler = popupWidget( + AudienceUserInfoPanelWidget(user: user, liveStreamManager: widget.liveStreamManager), + context: context, + backgroundColor: LiveColors.designStandardTransparent); } }, ), @@ -244,14 +265,33 @@ class _AudienceLivingWidgetState extends State { return BarrageDisplayWidget( controller: _barrageDisplayController!, onClickBarrageItem: (barrage) { - final isSelf = TUIRoomEngine.getSelfInfo().userId == barrage.sender.userID; + final currentLive = LiveListStore.shared.liveState.currentLive.value; + final selfID = LoginStore.shared.loginState.loginUserInfo?.userID; + final isSelf = selfID == barrage.sender.userID; + if (currentLive.liveID.isEmpty || barrage.sender.userID.isEmpty || isSelf) { + return; + } final user = LiveUserInfo( userID: barrage.sender.userID, userName: barrage.sender.userName, avatarURL: barrage.sender.avatarURL); - if (!isSelf) { - popupWidget(AudienceUserInfoPanelWidget(user: user, liveStreamManager: widget.liveStreamManager), - context: context, backgroundColor: LiveColors.designStandardTransparent); + final adminList = LiveAudienceStore.create(currentLive.liveID).liveAudienceState.adminList.value; + final selfIsAdmin = adminList.any((user) => user.userID == selfID); + final senderIsAdmin = adminList.any((user) => user.userID == barrage.sender.userID); + final senderIsOwner = currentLive.liveOwner.userID == barrage.sender.userID; + if (selfIsAdmin && !senderIsAdmin && !senderIsOwner) { + _adminUserManagementPanelHandler = popupWidget( + AdminUserManagementForAudiencePanel( + user: user, + liveStreamManager: widget.liveStreamManager, + closeCallback: () => _adminUserManagementPanelHandler?.close(), + ), + context: context); + } else { + _audienceUserInfoPanelHandler = popupWidget( + AudienceUserInfoPanelWidget(user: user, liveStreamManager: widget.liveStreamManager), + context: context, + backgroundColor: LiveColors.designStandardTransparent); } }, ); @@ -286,7 +326,7 @@ class _AudienceLivingWidgetState extends State { } final orientation = MediaQuery.orientationOf(context); if (widget.liveStreamManager.roomState.roomVideoStreamIsLandscape.value && orientation == Orientation.landscape) { - popupWidget( + _playerMenuPanelHandler = popupWidget( PlayerMenuWidget(liveStreamManager: widget.liveStreamManager), context: context, barrierColor: LiveColors.designStandardTransparent, @@ -443,6 +483,14 @@ extension on _AudienceLivingWidgetState { playbackQuality = playbackVideoQuality; } + void _onLoginStatusChanged() { + final loginStatus = LoginStore.shared.loginState.loginStatus; + if (loginStatus == LoginStatus.unlogin) { + LiveKitLogger.warning("LoginStatus => unlogin"); + _closePage(); + } + } + void _closePage() { if (GlobalFloatWindowManager.instance.isEnableFloatWindowFeature()) { GlobalFloatWindowManager.instance.overlayManager.closeOverlay(); @@ -515,8 +563,6 @@ extension on _AudienceLivingWidgetState { return '540P'; case TUIVideoQuality.videoQuality_360P: return '360P'; - default: - return 'Original'; } } @@ -528,5 +574,8 @@ extension on _AudienceLivingWidgetState { void _closeAllDialog() { _closePanelSheetHandler?.close(); + _adminUserManagementPanelHandler?.close(); + _audienceUserInfoPanelHandler?.close(); + _playerMenuPanelHandler?.close(); } } diff --git a/live/livekit/lib/live_stream/features/audience/living_widget/host_absent_widget.dart b/live/livekit/lib/live_stream/features/audience/living_widget/host_absent_widget.dart new file mode 100644 index 00000000..a01eedad --- /dev/null +++ b/live/livekit/lib/live_stream/features/audience/living_widget/host_absent_widget.dart @@ -0,0 +1,96 @@ +import 'dart:async'; + +import 'package:atomic_x_core/api/live/live_list_store.dart'; +import 'package:atomic_x_core/api/live/live_seat_store.dart'; +import 'package:flutter/material.dart'; +import 'package:tencent_live_uikit/common/index.dart'; +import 'package:tencent_live_uikit/live_stream/manager/live_stream_manager.dart'; + +/// Audience-side placeholder shown when no host is on any seat for >= 1s. +class HostAbsentWidget extends StatefulWidget { + final LiveStreamManager liveStreamManager; + + const HostAbsentWidget({super.key, required this.liveStreamManager}); + + @override + State createState() => _HostAbsentWidgetState(); +} + +class _HostAbsentWidgetState extends State { + static const Duration _showDelay = Duration(milliseconds: 1500); + + final ValueNotifier _isVisible = ValueNotifier(false); + Timer? _showTimer; + + late final LiveSeatStore _seatStore = LiveSeatStore.create(widget.liveStreamManager.roomState.roomId); + late final VoidCallback _seatListListener = _onSeatListChanged; + late final VoidCallback _floatWindowListener = _onFloatWindowChanged; + + @override + void initState() { + super.initState(); + _seatStore.liveSeatState.seatList.addListener(_seatListListener); + widget.liveStreamManager.floatWindowState.isFloatWindowMode.addListener(_floatWindowListener); + _onSeatListChanged(); + } + + @override + void dispose() { + _seatStore.liveSeatState.seatList.removeListener(_seatListListener); + widget.liveStreamManager.floatWindowState.isFloatWindowMode.removeListener(_floatWindowListener); + _showTimer?.cancel(); + _showTimer = null; + _isVisible.dispose(); + super.dispose(); + } + + void _onFloatWindowChanged() { + if (!mounted) return; + setState(() {}); + } + + void _onSeatListChanged() { + final seatList = _seatStore.liveSeatState.seatList.value; + final bool hasHost = seatList.isNotEmpty && seatList.any((SeatInfo s) => s.userInfo.userID.isNotEmpty); + + // Any state change cancels a pending show timer. + _showTimer?.cancel(); + _showTimer = null; + + if (hasHost) { + _isVisible.value = false; + } else { + _showTimer = Timer(_showDelay, () { + if (!mounted) return; + _isVisible.value = true; + }); + } + } + + String _muteImageAsset(BuildContext context, {required bool isLandscape}) { + final bool isEn = DeviceLanguage.getCurrentLanguageCode(context) == 'en'; + if (isLandscape) { + return isEn ? LiveImages.muteImageEnLand : LiveImages.muteImageLand; + } + return isEn ? LiveImages.muteImageEn : LiveImages.muteImage; + } + + @override + Widget build(BuildContext context) { + final seatTemplate = widget.liveStreamManager.roomState.liveInfo.seatTemplate; + final bool isLandscape4Seats = seatTemplate is VideoLandscape4Seats; + return ValueListenableBuilder( + valueListenable: _isVisible, + builder: (BuildContext context, bool visible, Widget? child) { + if (!visible) return const SizedBox.shrink(); + return SizedBox.expand( + child: Image.asset( + _muteImageAsset(context, isLandscape: isLandscape4Seats), + package: Constants.pluginName, + fit: BoxFit.cover, + ), + ); + }, + ); + } +} diff --git a/live/livekit/lib/live_stream/features/audience/pager/live_list_pager_service.dart b/live/livekit/lib/live_stream/features/audience/pager/live_list_pager_service.dart index 474268d9..f6199faf 100644 --- a/live/livekit/lib/live_stream/features/audience/pager/live_list_pager_service.dart +++ b/live/livekit/lib/live_stream/features/audience/pager/live_list_pager_service.dart @@ -11,8 +11,7 @@ class LiveListPagerService { final LiveListPagerState state = LiveListPagerState(); final LiveListStore _liveListStore; - LiveListPagerService({LiveListStore? liveListStore}) - : _liveListStore = liveListStore ?? LiveListStore.shared; + LiveListPagerService({LiveListStore? liveListStore}) : _liveListStore = liveListStore ?? LiveListStore.shared; Future initWithCurrentLive(LiveInfo currentLiveInfo) async { state.liveInfoList.value = [currentLiveInfo]; @@ -24,6 +23,41 @@ class LiveListPagerService { await _fetchNextPage(excludeLiveId: currentLiveInfo.liveID); } + Future initWithRoomId(String roomId) async { + final placeholder = LiveInfo()..liveID = roomId; + state.liveInfoList.value = [placeholder]; + state.currentPageIndex.value = 0; + state.cursor = ''; + state.hasMoreData.value = true; + state.isLoadingMore.value = false; + + await Future.wait([ + _loadLiveInfo(roomId), + _fetchNextPage(excludeLiveId: roomId), + ]); + } + + Future _loadLiveInfo(String roomId) async { + try { + final result = await _liveListStore.fetchLiveInfo(roomId); + if (!result.isSuccess) { + LiveKitLogger.error('$tag _loadLiveInfo failed [code:${result.errorCode}, message:${result.errorMessage}]'); + return; + } + final fetched = result.liveInfo; + if (fetched.liveID != roomId) return; + + final current = state.liveInfoList.value; + if (current.isEmpty) { + state.liveInfoList.value = [fetched]; + } else { + state.liveInfoList.value = [fetched, ...current.sublist(1)]; + } + } catch (e) { + LiveKitLogger.error('$tag _loadLiveInfo error: $e'); + } + } + Future loadMore() async { if (state.isLoadingMore.value || !state.hasMoreData.value) { return; @@ -52,8 +86,7 @@ class LiveListPagerService { ); if (!result.isSuccess) { - LiveKitLogger.error( - '$tag _fetchNextPage failed [code:${result.errorCode}, message:${result.errorMessage}]'); + LiveKitLogger.error('$tag _fetchNextPage failed [code:${result.errorCode}, message:${result.errorMessage}]'); state.hasMoreData.value = false; state.isLoadingMore.value = false; return; @@ -63,14 +96,10 @@ class LiveListPagerService { final newCursor = _liveListStore.liveState.liveListCursor.value; final existingIds = state.liveInfoList.value.map((info) => info.liveID).toSet(); - List deduplicatedList = fetchedList - .where((info) => !existingIds.contains(info.liveID)) - .toList(); + List deduplicatedList = fetchedList.where((info) => !existingIds.contains(info.liveID)).toList(); if (excludeLiveId != null) { - deduplicatedList = deduplicatedList - .where((info) => info.liveID != excludeLiveId) - .toList(); + deduplicatedList = deduplicatedList.where((info) => info.liveID != excludeLiveId).toList(); } state.liveInfoList.value = [ diff --git a/live/livekit/lib/live_stream/features/audience/panel/admin_user_management_for_audience_panel.dart b/live/livekit/lib/live_stream/features/audience/panel/admin_user_management_for_audience_panel.dart new file mode 100644 index 00000000..f15d10c9 --- /dev/null +++ b/live/livekit/lib/live_stream/features/audience/panel/admin_user_management_for_audience_panel.dart @@ -0,0 +1,158 @@ +import 'package:atomic_x_core/api/live/live_audience_store.dart'; +import 'package:atomic_x_core/api/live/live_list_store.dart'; +import 'package:flutter/material.dart'; +import 'package:tencent_live_uikit/common/index.dart'; +import 'package:tencent_live_uikit/live_stream/manager/live_stream_manager.dart'; +import 'package:tencent_live_uikit/tencent_live_uikit.dart'; + +import '../../anchor_broadcast/common/anchor_user_management_panel_base.dart'; + + +class AdminUserManagementForAudiencePanel extends StatefulWidget { + final LiveUserInfo user; + final LiveStreamManager liveStreamManager; + final VoidCallback closeCallback; + + const AdminUserManagementForAudiencePanel({ + super.key, + required this.user, + required this.liveStreamManager, + required this.closeCallback, + }); + + @override + State createState() => _AdminUserManagementForAudiencePanelState(); +} + +class _AdminUserManagementForAudiencePanelState extends State { + final ValueNotifier _isMessageDisabled = ValueNotifier(false); + AlertHandler? _kickOutAlertHandler; + late final LiveAudienceStore? liveAudienceStore; + late final VoidCallback _messageBannedUserListListener = _onMessageBannedUserListChanged; + late final VoidCallback _floatWindowModeListener = _onFloatWindowModeChanged; + + @override + void initState() { + super.initState(); + final liveInfo = LiveListStore.shared.liveState.currentLive.value; + if (liveInfo.liveID.isNotEmpty) { + liveAudienceStore = LiveAudienceStore.create(liveInfo.liveID); + liveAudienceStore!.liveAudienceState.messageBannedUserList.addListener(_messageBannedUserListListener); + _onMessageBannedUserListChanged(); + } + widget.liveStreamManager.floatWindowState.isFloatWindowMode.addListener(_floatWindowModeListener); + } + + @override + void dispose() { + _kickOutAlertHandler?.close(); + widget.liveStreamManager.floatWindowState.isFloatWindowMode.removeListener(_floatWindowModeListener); + liveAudienceStore?.liveAudienceState.messageBannedUserList.removeListener(_messageBannedUserListListener); + _isMessageDisabled.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnchorUserManagementPanelBase( + user: widget.user, + liveStreamManager: widget.liveStreamManager, + enableFollow: false, + child: _buildMenuWidget(), + ); + } + + Widget _buildMenuWidget() { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 24.width), + child: Container( + constraints: BoxConstraints(maxWidth: 327.width), + height: 77.height, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 20.width, + children: _buildMessageAndKickOutChildren(), + ), + ), + ); + } + + List _buildMessageAndKickOutChildren() { + List children = []; + children.add(ValueListenableBuilder( + valueListenable: _isMessageDisabled, + builder: (context, isMessageDisabled, child) { + return CommonMenuWidget( + imageName: isMessageDisabled ? LiveImages.disableChat : LiveImages.enableChat, + title: isMessageDisabled + ? LiveKitLocalizations.of(context)!.common_enable_message + : LiveKitLocalizations.of(context)!.common_disable_message, + onTap: () => _messageButtonClicked(), + ); + }, + )); + + children.add(CommonMenuWidget( + imageName: LiveImages.anchorKickOut, + title: LiveKitLocalizations.of(context)!.common_kick_out_of_room, + onTap: () => _kickOutButtonClicked(), + )); + + return children; + } +} + +extension on _AdminUserManagementForAudiencePanelState { + void _messageButtonClicked() { + widget.closeCallback.call(); + if (liveAudienceStore == null) return; + final userID = widget.user.userID; + final isDisable = !_isMessageDisabled.value; + liveAudienceStore!.disableSendMessage(userID: userID, isDisable: isDisable).then((result) { + if (result.errorCode != TUIError.success.rawValue) { + widget.liveStreamManager.toastSubject + .add(ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? ''); + } + }); + } + + void _kickOutButtonClicked() { + final userName = widget.user.userName.isNotEmpty ? widget.user.userName : widget.user.userID; + final alertInfo = AlertInfo( + description: + LiveKitLocalizations.of(Global.appContext())!.common_kick_user_confirm_message.replaceAll('xxx', userName), + cancelText: LiveKitLocalizations.of(Global.appContext())!.common_cancel, + cancelCallback: () => _closeKickOutAlert(), + defaultText: LiveKitLocalizations.of(Global.appContext())!.common_remove, + defaultCallback: () { + if (liveAudienceStore == null) return; + liveAudienceStore!.kickUserOutOfRoom(widget.user.userID).then((result) { + if (result.errorCode != TUIError.success.rawValue) { + widget.liveStreamManager.toastSubject + .add(ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? ''); + } + }); + _closeKickOutAlert(); + }); + _kickOutAlertHandler = Alert.showAlert(alertInfo, context); + } + + void _closeKickOutAlert() { + _kickOutAlertHandler?.close(); + widget.closeCallback.call(); + } + + void _onMessageBannedUserListChanged() { + if (liveAudienceStore == null) return; + final messageBannedUserList = liveAudienceStore!.liveAudienceState.messageBannedUserList.value; + _isMessageDisabled.value = messageBannedUserList.any((user) => user.userID == widget.user.userID); + } + + void _onFloatWindowModeChanged() { + bool isFloatWindowMode = widget.liveStreamManager.floatWindowState.isFloatWindowMode.value; + if (isFloatWindowMode) { + _closeKickOutAlert(); + } + } +} diff --git a/live/livekit/lib/live_stream/features/audience/panel/audience_settings_panel_widget.dart b/live/livekit/lib/live_stream/features/audience/panel/audience_settings_panel_widget.dart index 9c5947a3..96c8621f 100644 --- a/live/livekit/lib/live_stream/features/audience/panel/audience_settings_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/audience/panel/audience_settings_panel_widget.dart @@ -79,12 +79,14 @@ class _AudienceSettingsPanelWidgetState extends State= 2; return Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, spacing: 20.width, children: [ - if (!isOnSeat) _buildVideoSettingsItemWidget(context), + if (!isOnSeat && isSupportMultiplePlaybackQuality) _buildVideoSettingsItemWidget(context), if (showPip) _buildPipItemWidget(context), ], ); @@ -168,8 +170,6 @@ extension on _AudienceSettingsPanelWidgetState { return '540P'; case TUIVideoQuality.videoQuality_360P: return '360P'; - default: - return 'unknown'; } } diff --git a/live/livekit/lib/live_stream/features/audience/panel/audience_user_info_panel_widget.dart b/live/livekit/lib/live_stream/features/audience/panel/audience_user_info_panel_widget.dart index e32a647c..3cbdd626 100644 --- a/live/livekit/lib/live_stream/features/audience/panel/audience_user_info_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/audience/panel/audience_user_info_panel_widget.dart @@ -1,11 +1,14 @@ +import 'package:atomic_x_core/api/live/co_host_store.dart'; import 'package:atomic_x_core/api/live/live_audience_store.dart'; +import 'package:atomic_x_core/api/live/live_list_store.dart'; import 'package:flutter/material.dart'; -import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_info.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_operation_result.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart'; import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; +import 'package:tencent_live_uikit/common/error/error_handler.dart'; +import 'package:tencent_live_uikit/component/float_window/global_float_window_manager.dart'; import 'package:tencent_live_uikit/live_stream/manager/live_stream_manager.dart'; import '../../../../../common/constants/constants.dart'; @@ -14,32 +17,62 @@ import '../../../../../common/resources/colors.dart'; import '../../../../../common/resources/images.dart'; import '../../../../../common/widget/index.dart'; import '../../../../../common/screen/index.dart'; +import '../../../../common/logger/logger.dart'; import '../../../../component/live_info/state/follow_define.dart'; +import '../../../../live_navigator_observer.dart'; class AudienceUserInfoPanelWidget extends StatefulWidget { final LiveUserInfo user; + final String? liveID; final LiveStreamManager liveStreamManager; + final VoidCallback? onExitRoom; + final VoidCallback? onClose; + final bool enableFollow; + final bool enableEnterRoom; - const AudienceUserInfoPanelWidget({super.key, required this.user, required this.liveStreamManager}); + const AudienceUserInfoPanelWidget({ + super.key, + required this.user, + required this.liveStreamManager, + this.liveID, + this.onExitRoom, + this.onClose, + this.enableFollow = true, + this.enableEnterRoom = false, + }); @override State createState() => _AudienceUserInfoPanelWidgetState(); } class _AudienceUserInfoPanelWidgetState extends State { + late final String liveID; final ValueNotifier _isFollow = ValueNotifier(false); final ValueNotifier _fansNumber = ValueNotifier(0); - bool _enableFollowButton = true; + + late final VoidCallback _coHostConnectedListener = _onCoHostConnectedChanged; + + CoHostStore? _coHostStore; @override void initState() { super.initState(); + liveID = widget.liveID ?? widget.liveStreamManager.roomState.roomId; _getFansCount(); _checkFollowType(); + if (widget.enableEnterRoom) { + final liveInfo = LiveListStore.shared.liveState.currentLive.value; + if (liveInfo.liveID.isEmpty) return; + _coHostStore = CoHostStore.create(liveInfo.liveID); + _coHostStore!.coHostState.connected.addListener(_coHostConnectedListener); + } } @override void dispose() { + _isFollow.dispose(); + _fansNumber.dispose(); + _coHostStore?.coHostState.connected.removeListener(_coHostConnectedListener); super.dispose(); } @@ -47,7 +80,7 @@ class _AudienceUserInfoPanelWidgetState extends State _gotoNewLiveRoom(), + child: Container( + margin: EdgeInsets.only(left: 14.width, right: 4.width), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.radius), + color: LiveColors.designStandardB1, + ), + alignment: Alignment.center, + child: Text( + LiveKitLocalizations.of(Global.appContext())!.common_enter_anchor_live_room, + style: const TextStyle(fontSize: 16, fontStyle: FontStyle.normal, color: LiveColors.designStandardG7), + ), + ), + ), + ), + ); + } } extension on _AudienceUserInfoPanelWidgetState { @@ -203,10 +262,6 @@ extension on _AudienceUserInfoPanelWidgetState { } void _followButtonClicked() async { - if (_enableFollowButton == false) { - return; - } - _enableFollowButton = false; final friendshipManager = TencentImSDKPlugin.v2TIMManager.getFriendshipManager(); final userId = widget.user.userID; if (userId.isEmpty) { @@ -225,7 +280,6 @@ extension on _AudienceUserInfoPanelWidgetState { } _fansNumber.value += 1; _isFollow.value = true; - _enableFollowButton = true; } else { final result = await friendshipManager.unfollowUser(userIDList: [userId]); if (result.code != 0) { @@ -238,7 +292,38 @@ extension on _AudienceUserInfoPanelWidgetState { } _fansNumber.value -= 1; _isFollow.value = false; - _enableFollowButton = true; } } + + void _gotoNewLiveRoom() async { + LiveKitLogger.info("_gotoNewLiveRoom enter"); + widget.onClose?.call(); + if (widget.onExitRoom == null || widget.liveID == null) { + LiveKitLogger.warning("onExitRoom or liveID is null"); + return; + } + try { + final result = await LiveListStore.shared.fetchLiveInfo(widget.liveID!); + if (!result.isSuccess) { + makeToast(Global.appContext(), ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? ''); + return; + } + TUILiveKitNavigatorObserver.instance.enteringRoomID.value = result.liveInfo.liveID; + await LiveListStore.shared.leaveLive(); + if (mounted) widget.onExitRoom!.call(); + if (GlobalFloatWindowManager.instance.isEnableFloatWindowFeature()) { + GlobalFloatWindowManager.instance.overlayManager.closeOverlay(); + } + TUILiveKitNavigatorObserver.instance.enterLiveRoomPage(Global.appContext(), result.liveInfo); + } on Exception catch (e) { + LiveKitLogger.error(e.toString()); + TUILiveKitNavigatorObserver.instance.enteringRoomID.value = ''; + } + } + + void _onCoHostConnectedChanged() { + if (_coHostStore == null) return; + bool isHost = _coHostStore!.coHostState.connected.value.any((user) => user.liveID == widget.liveID); + if (!isHost) widget.onClose?.call(); + } } diff --git a/live/livekit/lib/live_stream/features/audience/panel/co_guest_type_select_panel_widget.dart b/live/livekit/lib/live_stream/features/audience/panel/co_guest_type_select_panel_widget.dart index 2c6de265..e03cf266 100644 --- a/live/livekit/lib/live_stream/features/audience/panel/co_guest_type_select_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/audience/panel/co_guest_type_select_panel_widget.dart @@ -164,6 +164,7 @@ extension on _CoGuestTypeSelectPanelWidgetState { seatIndex: widget.seatIndex, timeout: Constants.defaultRequestTimeout, ); + if (!mounted) return; if (result.errorCode != TUIError.success.rawValue) { widget.liveStreamManager.coGuestManager.onRequestIntraRoomConnectionFailed(); makeToast(context, ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? '', diff --git a/live/livekit/lib/live_stream/features/audience/panel/co_guest_video_setting_panel_widget.dart b/live/livekit/lib/live_stream/features/audience/panel/co_guest_video_setting_panel_widget.dart index a63623f2..16874bba 100644 --- a/live/livekit/lib/live_stream/features/audience/panel/co_guest_video_setting_panel_widget.dart +++ b/live/livekit/lib/live_stream/features/audience/panel/co_guest_video_setting_panel_widget.dart @@ -186,6 +186,7 @@ extension on _CoGuestVideoSettingsPanelWidgetState { CoGuestStore coGuestStore = CoGuestStore.create(widget.liveStreamManager.roomState.roomId); final result = await coGuestStore.applyForSeat(seatIndex: widget.seatIndex, timeout: Constants.defaultRequestTimeout); + if (!mounted) return; if (result.errorCode != TUIError.success.rawValue) { widget.liveStreamManager.coGuestManager.onRequestIntraRoomConnectionFailed(); makeToast(context, ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage) ?? '', diff --git a/live/livekit/lib/live_stream/features/decorations/battle/index.dart b/live/livekit/lib/live_stream/features/decorations/battle/index.dart index 580bafaf..c97b21ae 100644 --- a/live/livekit/lib/live_stream/features/decorations/battle/index.dart +++ b/live/livekit/lib/live_stream/features/decorations/battle/index.dart @@ -1,4 +1,3 @@ -library battle; export 'battle_info_widget.dart'; export 'battle_member_info_widget.dart'; diff --git a/live/livekit/lib/live_stream/features/decorations/co_guest/co_guest_waiting_agree_widget.dart b/live/livekit/lib/live_stream/features/decorations/co_guest/co_guest_waiting_agree_widget.dart index a08236b5..83e9e7b8 100644 --- a/live/livekit/lib/live_stream/features/decorations/co_guest/co_guest_waiting_agree_widget.dart +++ b/live/livekit/lib/live_stream/features/decorations/co_guest/co_guest_waiting_agree_widget.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'package:atomic_x_core/api/live/co_guest_store.dart'; -import 'package:atomic_x_core/api/live/live_list_store.dart'; import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; diff --git a/live/livekit/lib/live_stream/features/decorations/co_guest/index.dart b/live/livekit/lib/live_stream/features/decorations/co_guest/index.dart index bdc2d7a4..b75cc476 100644 --- a/live/livekit/lib/live_stream/features/decorations/co_guest/index.dart +++ b/live/livekit/lib/live_stream/features/decorations/co_guest/index.dart @@ -1,4 +1,3 @@ -library co_guest; export 'co_guest_background_widget.dart'; export 'co_guest_foreground_widget.dart'; diff --git a/live/livekit/lib/live_stream/features/decorations/co_host/index.dart b/live/livekit/lib/live_stream/features/decorations/co_host/index.dart index 4006a07d..b417d5af 100644 --- a/live/livekit/lib/live_stream/features/decorations/co_host/index.dart +++ b/live/livekit/lib/live_stream/features/decorations/co_host/index.dart @@ -1,4 +1,3 @@ -library co_host; export 'co_host_background_widget.dart'; export 'co_host_foreground_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/live_stream/features/decorations/index.dart b/live/livekit/lib/live_stream/features/decorations/index.dart index a0d59369..833c956a 100644 --- a/live/livekit/lib/live_stream/features/decorations/index.dart +++ b/live/livekit/lib/live_stream/features/decorations/index.dart @@ -1,4 +1,3 @@ -library decorations; export 'battle/index.dart'; export 'co_guest/index.dart'; diff --git a/live/livekit/lib/live_stream/features/end_statistics/index.dart b/live/livekit/lib/live_stream/features/end_statistics/index.dart index d5b73c5e..9a0d5044 100644 --- a/live/livekit/lib/live_stream/features/end_statistics/index.dart +++ b/live/livekit/lib/live_stream/features/end_statistics/index.dart @@ -1,4 +1,3 @@ -library end_statistics; export 'end_statistics_widget_define.dart'; export 'audience_end_statistics_widget.dart'; diff --git a/live/livekit/lib/live_stream/features/index.dart b/live/livekit/lib/live_stream/features/index.dart index 394eeb5c..3f530d61 100644 --- a/live/livekit/lib/live_stream/features/index.dart +++ b/live/livekit/lib/live_stream/features/index.dart @@ -1,4 +1,3 @@ -library video_room; export 'live_room_anchor_widget.dart'; export 'live_room_audience_widget.dart'; diff --git a/live/livekit/lib/live_stream/features/live_list/index.dart b/live/livekit/lib/live_stream/features/live_list/index.dart index e88dd512..e139e09a 100644 --- a/live/livekit/lib/live_stream/features/live_list/index.dart +++ b/live/livekit/lib/live_stream/features/live_list/index.dart @@ -1,4 +1,3 @@ -library live_list; export 'live_list_widget.dart'; export 'live_list_define.dart'; diff --git a/live/livekit/lib/live_stream/features/live_list/live_list_preview_manager.dart b/live/livekit/lib/live_stream/features/live_list/live_list_preview_manager.dart index 2275b985..8f535acb 100644 --- a/live/livekit/lib/live_stream/features/live_list/live_list_preview_manager.dart +++ b/live/livekit/lib/live_stream/features/live_list/live_list_preview_manager.dart @@ -25,9 +25,6 @@ class LiveListPreviewManager { /// Returns a copy of the roomIds currently being previewed in double-column mode. Set get doublePlayingRoomIds => Set.unmodifiable(_doublePlayingRoomIds); - /// Timer for delayed double-column preload. - Timer? _preloadTimer; - /// When true, preloadTopRow Timer callbacks will be suppressed. bool _isBlocked = false; @@ -49,6 +46,10 @@ class LiveListPreviewManager { if (_activePreviews.containsKey(roomId)) { return; } + if (LiveListStore.shared.liveState.currentLive.value.liveID == roomId) { + LiveKitLogger.info('$tag startPreview: $roomId ignore, current live room is playing'); + return; + } final controller = _controllerFactory.create(); _activePreviews[roomId] = controller; controller.startPreview(roomId, isMuteAudio); @@ -85,7 +86,6 @@ class LiveListPreviewManager { required List topRowRoomIds, required List allVisibleRoomIds, }) { - _preloadTimer?.cancel(); if (topRowRoomIds.isEmpty) { // No fully visible row, stop all double-playing previews. @@ -114,19 +114,23 @@ class LiveListPreviewManager { if (roomIdsToPreload.isEmpty) return; - _preloadTimer = Timer(const Duration(milliseconds: 500), () { - if (_isBlocked) return; - for (final roomId in roomIdsToPreload) { - if (!_activePreviews.containsKey(roomId)) { - final controller = _controllerFactory.create(); - _activePreviews[roomId] = controller; - controller.startPreview(roomId, true); - _doublePlayingRoomIds.add(roomId); - LiveKitLogger.info('$tag preloadTopRow: $roomId'); + if (_isBlocked) return; + for (int i = 0; i < roomIdsToPreload.length; i++) { + final roomId = roomIdsToPreload[i]; + Future.delayed(Duration(milliseconds: i * 50), () { + if (_isBlocked || _activePreviews.containsKey(roomId)) return; + if (LiveListStore.shared.liveState.currentLive.value.liveID == roomId) { + LiveKitLogger.info('$tag preloadTopRow, startPreview: $roomId ignore, current live room is playing'); + return; } - } - notifyStateChange(); - }); + final controller = _controllerFactory.create(); + _activePreviews[roomId] = controller; + controller.startPreview(roomId, true); + _doublePlayingRoomIds.add(roomId); + notifyStateChange(); + LiveKitLogger.info('$tag preloadTopRow: $roomId'); + }); + } } /// Check if a preview is active for the given roomId. @@ -139,14 +143,7 @@ class LiveListPreviewManager { return _activePreviews[roomId]; } - /// Cancel the pending preload timer without stopping active previews. - void cancelPreloadTimer() { - _preloadTimer?.cancel(); - _preloadTimer = null; - } - void dispose() { - _preloadTimer?.cancel(); stopAllPreviews(); previewStateNotifier.dispose(); LiveKitLogger.info('$tag disposed'); diff --git a/live/livekit/lib/live_stream/features/live_list/live_list_widget.dart b/live/livekit/lib/live_stream/features/live_list/live_list_widget.dart index 1d1a5cf2..a3ed4732 100644 --- a/live/livekit/lib/live_stream/features/live_list/live_list_widget.dart +++ b/live/livekit/lib/live_stream/features/live_list/live_list_widget.dart @@ -1,9 +1,6 @@ import 'dart:async'; import 'dart:ui'; -import 'package:atomic_x_core/api/login/login_store.dart'; -import 'package:atomic_x_core/api/live/live_list_store.dart'; -import 'package:atomic_x_core/api/view/live/live_core_widget.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -13,7 +10,6 @@ import 'package:tuikit_atomic_x/atomicx.dart' hide RoomType; import '../../../common/index.dart'; import '../../../tencent_live_uikit.dart'; import '../audience/pager/live_core_preview_controller.dart'; -import 'live_list_define.dart'; import 'live_list_preview_manager.dart'; import 'service/live_list_service.dart'; import 'widget/single_column_widget.dart'; @@ -63,6 +59,11 @@ class LiveListWidgetState extends State with RouteAware { /// Tracks current page index in single-column mode. int _currentPageInSingleColumn = 0; + /// In-flight refresh future used to dedupe concurrent _onRefresh() calls. + /// While a refresh is running, subsequent calls reuse the same Future + /// instead of triggering another network request / preload schedule. + Future? _refreshingFuture; + /// GlobalKeys for double-column items (index → key) to find top visible row. final Map _itemKeys = {}; @@ -87,12 +88,12 @@ class LiveListWidgetState extends State with RouteAware { super.initState(); _initData(); _addListener(); - _toastSubscription = - _liveListService.toastStream.listen((toast) => makeToast(context, toast, type: ToastType.error)); + _toastSubscription = _liveListService.toastStream.listen((toast) { + if (!mounted) return; + makeToast(context, toast, type: ToastType.error); + }); GlobalFloatWindowManager.instance.setOverlayClosedCallback(() { - _isEnteringRoom = false; _isOverlayMode = false; - _previewManager.setBlocked(false); _onRefresh(); }); _onRefresh(); @@ -123,8 +124,6 @@ class LiveListWidgetState extends State with RouteAware { // still watching the live stream in the Overlay. return; } - _isEnteringRoom = false; - _previewManager.setBlocked(false); _onRefresh(); } @@ -161,47 +160,50 @@ class LiveListWidgetState extends State with RouteAware { if (_currentStyle.value == newStyle) return; LiveKitLogger.info('LiveListWidget switchStyle: ${_currentStyle.value} -> $newStyle'); + newStyle == LiveListViewStyle.singleColumn ? _switchToSingleColumnStyle() : _switchToDoubleColumnStyle(); + } + + void _switchToSingleColumnStyle() { final liveInfoList = _liveListService.roomListState.liveInfoList.value; + // Find the first item currently being previewed in double-column mode. + // Falls back to the most visible item if no preview is active. + final targetIndex = _findFirstDoublePlayingIndex(liveInfoList) ?? _findMostVisibleDoubleColumnIndex(liveInfoList); + _previewManager.stopAllPreviews(); + _currentPageInSingleColumn = targetIndex; + _currentStyle.value = LiveListViewStyle.singleColumn; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_pageController.hasClients) { + _pageController.jumpToPage(targetIndex); + } + // Start preview for current page. + if (targetIndex < liveInfoList.length) { + _previewManager.startPreview(liveInfoList[targetIndex], isMuteAudio: false); + } + }); + } - if (newStyle == LiveListViewStyle.singleColumn) { - // Find the first item currently being previewed in double-column mode. - // Falls back to the most visible item if no preview is active. - final targetIndex = _findFirstDoublePlayingIndex(liveInfoList) ?? _findMostVisibleDoubleColumnIndex(liveInfoList); - _previewManager.stopAllPreviews(); - _currentPageInSingleColumn = targetIndex; - _currentStyle.value = newStyle; - WidgetsBinding.instance.addPostFrameCallback((_) { - if (_pageController.hasClients) { - _pageController.jumpToPage(targetIndex); - } - // Start preview for current page. - if (targetIndex < liveInfoList.length) { - _previewManager.startPreview(liveInfoList[targetIndex], isMuteAudio: false); - } - }); - } else { - // Switching to double-column: stop all single-column previews. - _previewManager.stopAllPreviews(); - final targetIndex = _currentPageInSingleColumn; - _currentStyle.value = newStyle; + void _switchToDoubleColumnStyle() { + // Switching to double-column: stop all single-column previews. + _previewManager.stopAllPreviews(); + final targetIndex = _currentPageInSingleColumn; + _currentStyle.value = LiveListViewStyle.doubleColumn; + WidgetsBinding.instance.addPostFrameCallback((_) { + // Scroll to approximate position of the item that was visible. + if (targetIndex > 0 && _scrollController.hasClients) { + final rowIndex = targetIndex ~/ 2; + final estimatedOffset = rowIndex * (cellHeight + 8.height); + _scrollController.jumpTo(estimatedOffset.clamp( + 0.0, + _scrollController.position.maxScrollExtent, + )); + } + // Wait one more frame so the sliver finishes re-laying out after + // jumpTo; otherwise item local offsets read by _triggerDoubleColumnPreload + // still reflect the pre-jump positions. WidgetsBinding.instance.addPostFrameCallback((_) { - // Scroll to approximate position of the item that was visible. - if (targetIndex > 0 && _scrollController.hasClients) { - final rowIndex = targetIndex ~/ 2; - final estimatedOffset = rowIndex * (cellHeight + 8.height); - _scrollController.jumpTo(estimatedOffset.clamp( - 0.0, - _scrollController.position.maxScrollExtent, - )); - } - // Wait one more frame so the sliver finishes re-laying out after - // jumpTo; otherwise item local offsets read by _triggerDoubleColumnPreload - // still reflect the pre-jump positions. - WidgetsBinding.instance.addPostFrameCallback((_) { - _triggerDoubleColumnPreload(); - }); + _triggerDoubleColumnPreload(); }); - } + }); } /// Find the most visible item index in double-column mode. @@ -347,12 +349,21 @@ class LiveListWidgetState extends State with RouteAware { // Start preview for the new current page. if (index < liveInfoList.length) { final currentLiveInfo = liveInfoList[index]; - if (!_previewManager.isPreviewActive(currentLiveInfo.liveID)) { - _previewManager.startPreview(currentLiveInfo, isMuteAudio: false); + if (TUILiveKitNavigatorObserver.instance.enteringRoomID.value == currentLiveInfo.liveID) { + LiveKitLogger.info( + 'entering room, startPreload ignore, roomId: ${currentLiveInfo.liveID}'); + } else if (!_isCurrentRoomInFloatWindowMode(currentLiveInfo.liveID)) { + if (!_previewManager.isPreviewActive(currentLiveInfo.liveID)) { + _previewManager.startPreview(currentLiveInfo, isMuteAudio: false); + } else { + // Unmute the current page's preview. + final controller = _previewManager.getPreviewController(currentLiveInfo.liveID); + controller?.startPreview(currentLiveInfo.liveID, false); + } } else { - // Unmute the current page's preview. - final controller = _previewManager.getPreviewController(currentLiveInfo.liveID); - controller?.startPreview(currentLiveInfo.liveID, false); + LiveKitLogger.info( + 'float window view is showing, startPreload ignore, roomId: ${currentLiveInfo + .liveID}'); } } // Load more when approaching end. @@ -555,12 +566,29 @@ class LiveListWidgetState extends State with RouteAware { fullyVisibleItems.where((item) => (item.top - minTop).abs() < 1).map((item) => item.roomId).toList(); } + final filterTopRowRoomIDs = _filterPreloadRoomIDsIfNeeded(topRowRoomIds); + _previewManager.preloadTopRow( - topRowRoomIds: topRowRoomIds, + topRowRoomIds: filterTopRowRoomIDs, allVisibleRoomIds: allVisibleRoomIds, ); } + List _filterPreloadRoomIDsIfNeeded(List preloadRoomIDs) { + final enteringID = TUILiveKitNavigatorObserver.instance.enteringRoomID.value; + if (enteringID.isNotEmpty && preloadRoomIDs.contains(enteringID)) { + preloadRoomIDs.remove(enteringID); + LiveKitLogger.info('entering room, startPreload ignore, roomId: $enteringID'); + } + + final currentLiveID = LiveListStore.shared.liveState.currentLive.value.liveID; + if (preloadRoomIDs.contains(currentLiveID)) { + preloadRoomIDs.remove(currentLiveID); + LiveKitLogger.info('has entered same room, startPreload ignore, roomId: $currentLiveID'); + } + return preloadRoomIDs; + } + /// Ensures the current page's preview is active and unmuted in single-column mode. /// /// If no preview is active for the current page (e.g., after _onRefresh stopped @@ -581,6 +609,17 @@ class LiveListWidgetState extends State with RouteAware { if (currentIndex >= liveInfoList.length) return; final roomId = liveInfoList[currentIndex].liveID; + + if (TUILiveKitNavigatorObserver.instance.enteringRoomID.value == roomId) { + LiveKitLogger.info('entering room, startPreload ignore, roomId: $roomId'); + return; + } + + if (_isCurrentRoomInFloatWindowMode(roomId)) { + LiveKitLogger.info('float window view is showing, startPreload ignore, roomId: $roomId'); + return; + } + if (_previewManager.isPreviewActive(roomId)) { // Preview exists, just ensure audio is unmuted. final controller = _previewManager.getPreviewController(roomId); @@ -592,6 +631,13 @@ class LiveListWidgetState extends State with RouteAware { } } + bool _isCurrentRoomInFloatWindowMode(String roomId) { + if (!GlobalFloatWindowManager.instance.isFloating()) { + return false; + } + return GlobalFloatWindowManager.instance.state.roomId.value == roomId; + } + void _initData() { _scrollController.addListener(_scrollListener); _onLoginChange(); @@ -618,15 +664,30 @@ class LiveListWidgetState extends State with RouteAware { } } - Future _onRefresh() async { - if (TUIRoomEngine.getSelfInfo().userId.isEmpty) { - LiveKitLogger.error("engine login not finish"); - return; + Future _onRefresh() { + // Dedupe: if a refresh is already in flight, reuse the same Future. + if (_refreshingFuture != null) { + return _refreshingFuture!; } + final future = _doRefresh(); + _refreshingFuture = future; + future.whenComplete(() { + // Only clear when the cleared future is still the latest one. + if (identical(_refreshingFuture, future)) { + _refreshingFuture = null; + } + }); + return future; + } + + Future _doRefresh() async { + if (TUILiveKitNavigatorObserver.instance.enteringRoomID.value.isNotEmpty) return; _previewManager.stopAllPreviews(); await _liveListService.refreshFetchList(); + if (!mounted) return; // After refresh, trigger preload based on current view style. WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; if (_currentStyle.value == LiveListViewStyle.singleColumn) { _triggerSingleColumnPreload(); } else { @@ -640,17 +701,26 @@ class LiveListWidgetState extends State with RouteAware { final liveInfo = _liveListService.roomListState.liveInfoList.value[index]; final roomType = LiveIdentityGenerator.instance.getIDType(liveInfo.liveID); - _isOverlayMode = GlobalFloatWindowManager.instance.isEnableFloatWindowFeature(); - _previewManager.setBlocked(true); - _previewManager.cancelPreloadTimer(); - _previewManager.stopAllPreviews(); + _preventPreload(); + _isEnteringRoom = true; if (roomType == RoomType.voice) { - _isEnteringRoom = await TUILiveKitNavigatorObserver.instance.enterVoiceRoomPage(context, liveInfo); + await TUILiveKitNavigatorObserver.instance.enterVoiceRoomPage(context, liveInfo); } else { - _isEnteringRoom = await TUILiveKitNavigatorObserver.instance.enterLiveRoomPage(context, liveInfo); + await TUILiveKitNavigatorObserver.instance.enterLiveRoomPage(context, liveInfo); } + _isEnteringRoom = false; + _allowPreload(); + } + + void _preventPreload() { + _previewManager.setBlocked(true); + _previewManager.stopAllPreviews(); + } + + void _allowPreload() { + _previewManager.setBlocked(false); } Widget _buildSingleColumnEmptyWidget() { @@ -895,7 +965,7 @@ class LiveListWidgetState extends State with RouteAware { try { Uri parsedUri = Uri.parse(urlString); return parsedUri.scheme.isNotEmpty && parsedUri.host.isNotEmpty; - } on FormatException catch (e) { + } on FormatException { return false; } } diff --git a/live/livekit/lib/live_stream/features/live_list/service/live_list_service.dart b/live/livekit/lib/live_stream/features/live_list/service/live_list_service.dart index f553eebc..82eed222 100644 --- a/live/livekit/lib/live_stream/features/live_list/service/live_list_service.dart +++ b/live/livekit/lib/live_stream/features/live_list/service/live_list_service.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:atomic_x_core/atomicxcore.dart'; import '../../../../common/index.dart'; @@ -28,6 +27,7 @@ class LiveListService { roomListState.refreshStatus.value = true; roomListState.cursor = ""; await _fetchLiveList(); + roomListState.refreshStatus.value = false; } Future loadMoreData() async { @@ -37,7 +37,8 @@ class LiveListService { return; } roomListState.loadStatus.value = true; - _loadMoreData(); + await _fetchLiveList(); + roomListState.loadStatus.value = false; } } @@ -46,45 +47,13 @@ extension LiveListServiceLogicExtension on LiveListService { final String cursor = roomListState.cursor; final result = await liveListStore.fetchLiveList(cursor: cursor, count: fetchListCount); if (!result.isSuccess) { - if (result.errorCode != TUIError.errFailed.rawValue && - result.errorMessage != null && - result.errorMessage!.contains('exceed frequency limit')) { - LiveKitLogger.error( - "${LiveListService.tag} _initData [code:${result.errorCode},message:${result.errorMessage}]"); - final message = ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage ?? ''); - if (message != null) _toastController.add(message); - } - roomListState.loadStatus.value = false; - roomListState.refreshStatus.value = false; - roomListState.isHaveMoreData.value = false; - } else { - roomListState.liveInfoList.value = liveListStore.liveState.liveList.value; - roomListState.cursor = liveListStore.liveState.liveListCursor.value; - roomListState.loadStatus.value = false; - roomListState.refreshStatus.value = false; - roomListState.isHaveMoreData.value = liveListStore.liveState.liveListCursor.value.isNotEmpty; - } - } - - Future _loadMoreData() async { - final String cursor = roomListState.cursor; - final result = await liveListStore.fetchLiveList(cursor: cursor, count: fetchListCount); - if (!result.isSuccess) { - LiveKitLogger.error( - "${LiveListService.tag} _initData [code:${result.errorCode},message:${result.errorMessage}]"); - final message = ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage); + final message = ErrorHandler.convertToErrorMessage(result.errorCode, result.errorMessage ?? ''); if (message != null) _toastController.add(message); - roomListState.loadStatus.value = false; roomListState.isHaveMoreData.value = false; } else { - List liveInfoList = [ - ...roomListState.liveInfoList.value, - ...liveListStore.liveState.liveList.value - ]; - roomListState.liveInfoList.value = liveInfoList; + roomListState.liveInfoList.value = liveListStore.liveState.liveList.value; roomListState.cursor = liveListStore.liveState.liveListCursor.value; - roomListState.loadStatus.value = false; roomListState.isHaveMoreData.value = liveListStore.liveState.liveListCursor.value.isNotEmpty; } } -} +} \ No newline at end of file diff --git a/live/livekit/lib/live_stream/features/live_list/store/live_list_state.dart b/live/livekit/lib/live_stream/features/live_list/store/live_list_state.dart index 701d3751..cdf1acc3 100644 --- a/live/livekit/lib/live_stream/features/live_list/store/live_list_state.dart +++ b/live/livekit/lib/live_stream/features/live_list/store/live_list_state.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:rtc_room_engine/rtc_room_engine.dart'; import 'package:atomic_x_core/atomicxcore.dart'; class LSLiveListState { @@ -9,4 +8,4 @@ class LSLiveListState { final ValueNotifier loadStatus = ValueNotifier(false); final ValueNotifier isHaveMoreData = ValueNotifier(false); String cursor = ""; -} +} \ No newline at end of file diff --git a/live/livekit/lib/live_stream/features/live_room_anchor_overlay.dart b/live/livekit/lib/live_stream/features/live_room_anchor_overlay.dart index ee1cdc0c..5a714a44 100644 --- a/live/livekit/lib/live_stream/features/live_room_anchor_overlay.dart +++ b/live/livekit/lib/live_stream/features/live_room_anchor_overlay.dart @@ -1,4 +1,3 @@ -import 'package:atomic_x_core/api/live/live_list_store.dart'; import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; import 'package:tencent_live_uikit/common/widget/float_window/float_window_widget.dart'; diff --git a/live/livekit/lib/live_stream/features/live_room_anchor_widget.dart b/live/livekit/lib/live_stream/features/live_room_anchor_widget.dart index f509b73c..62fc438c 100644 --- a/live/livekit/lib/live_stream/features/live_room_anchor_widget.dart +++ b/live/livekit/lib/live_stream/features/live_room_anchor_widget.dart @@ -106,6 +106,8 @@ class _TUILiveRoomAnchorWidgetState extends State { liveStreamManager: _liveStreamManager, liveCoreController: _liveCoreController, onTapEnterFloatWindowInApp: () { + final isLandscape = _liveStreamManager.floatWindowState.isLandscape.value; + widget.floatWindowController?.setScreenOrientation(isLandscape); widget.floatWindowController?.onTapSwitchFloatWindowInApp(true); }, ); diff --git a/live/livekit/lib/live_stream/features/live_room_audience_overlay.dart b/live/livekit/lib/live_stream/features/live_room_audience_overlay.dart index b931bab4..e62f6ef4 100644 --- a/live/livekit/lib/live_stream/features/live_room_audience_overlay.dart +++ b/live/livekit/lib/live_stream/features/live_room_audience_overlay.dart @@ -9,12 +9,12 @@ import 'live_room_audience_widget.dart'; class TUILiveRoomAudienceOverlay extends StatefulWidget { final String roomId; - final LiveInfo liveInfo; + final LiveInfo? liveInfo; const TUILiveRoomAudienceOverlay({ super.key, required this.roomId, - required this.liveInfo, + this.liveInfo, }); @override diff --git a/live/livekit/lib/live_stream/features/live_room_audience_widget.dart b/live/livekit/lib/live_stream/features/live_room_audience_widget.dart index a8171d00..dc542464 100644 --- a/live/livekit/lib/live_stream/features/live_room_audience_widget.dart +++ b/live/livekit/lib/live_stream/features/live_room_audience_widget.dart @@ -25,13 +25,13 @@ import '../features/audience/pager/live_list_pager_service.dart'; class TUILiveRoomAudienceWidget extends StatefulWidget { final String roomId; - final LiveInfo liveInfo; + final LiveInfo? liveInfo; final FloatWindowController? floatWindowController; const TUILiveRoomAudienceWidget({ super.key, required this.roomId, - required this.liveInfo, + this.liveInfo, this.floatWindowController, }); @@ -75,7 +75,15 @@ class _TUILiveRoomAudienceWidgetState extends State w controllerFactory: LiveCorePreviewControllerFactory(), ); _scrollLockManager = LiveListPagerScrollLockManager(); - _pagerService.initWithCurrentLive(widget.liveInfo); + assert( + widget.liveInfo == null || widget.liveInfo!.liveID == widget.roomId, + 'liveInfo.liveID must equal roomId when both are provided', + ); + if (widget.liveInfo != null) { + _pagerService.initWithCurrentLive(widget.liveInfo!); + } else { + _pagerService.initWithRoomId(widget.roomId); + } _startWakeLock(); } @@ -215,6 +223,8 @@ class _TUILiveRoomAudienceWidgetState extends State w onJoinLiveStateChanged: _onJoinLiveStateChanged, onCoGuestStateChanged: _onCoGuestStateChanged, onTapEnterFloatWindowInApp: () { + final isLandscape = resources.liveStreamManager.floatWindowState.isLandscape.value; + widget.floatWindowController?.setScreenOrientation(isLandscape); widget.floatWindowController?.onTapSwitchFloatWindowInApp(true); }, onDispose: () { diff --git a/live/livekit/lib/live_stream/manager/live_stream_manager.dart b/live/livekit/lib/live_stream/manager/live_stream_manager.dart index fa1669d8..ba92e7d3 100644 --- a/live/livekit/lib/live_stream/manager/live_stream_manager.dart +++ b/live/livekit/lib/live_stream/manager/live_stream_manager.dart @@ -98,6 +98,7 @@ extension LiveStreamManagerWithAnchor on LiveStreamManager { } void onStartLive(bool isJoinSelf, LiveInfo liveInfo) { + _floatWindowManager.setScreenOrientation(liveInfo.seatTemplate is VideoLandscape4Seats); return _roomManager.onStartLive(isJoinSelf, liveInfo); } @@ -188,6 +189,7 @@ extension LiveStreamManagerWithAnchor on LiveStreamManager { extension LiveStreamManagerWithAudience on LiveStreamManager { void onJoinLive(LiveInfo liveInfo) { + _floatWindowManager.setScreenOrientation(liveInfo.seatTemplate is VideoLandscape4Seats); _mediaManager.onJoinLive(liveInfo); return _roomManager.onJoinLive(liveInfo); } diff --git a/live/livekit/lib/live_stream/manager/module/battle_manager.dart b/live/livekit/lib/live_stream/manager/module/battle_manager.dart index 00798499..249c76a4 100644 --- a/live/livekit/lib/live_stream/manager/module/battle_manager.dart +++ b/live/livekit/lib/live_stream/manager/module/battle_manager.dart @@ -316,7 +316,4 @@ extension on BattleManager { return context.roomManager.target?.roomState.roomId ?? ''; } - String _getSelfID() { - return TUIRoomEngine.getSelfInfo().userId; - } } diff --git a/live/livekit/lib/live_stream/manager/module/float_window_manager.dart b/live/livekit/lib/live_stream/manager/module/float_window_manager.dart index ff991730..19e54bdb 100644 --- a/live/livekit/lib/live_stream/manager/module/float_window_manager.dart +++ b/live/livekit/lib/live_stream/manager/module/float_window_manager.dart @@ -1,8 +1,10 @@ import 'dart:convert'; +import 'package:atomic_x_core/api/live/live_list_store.dart'; import 'package:flutter/cupertino.dart'; +import 'package:tencent_live_uikit/common/index.dart'; +import 'package:tencent_live_uikit/component/float_window/global_float_window_manager.dart'; -import '../../../common/platform/rtc_live_tuikit_platform_interface.dart'; import '../../../common/widget/float_window/float_window_mode.dart'; import '../../state/float_window_state.dart'; import '../live_stream_manager.dart'; @@ -36,6 +38,12 @@ class FloatWindowManager { } } + void setScreenOrientation(bool isLandscape) { + if (floatWindowState.isLandscape is ValueNotifier) { + (floatWindowState.isLandscape as ValueNotifier).value = isLandscape; + } + } + Future enablePictureInPicture(String roomId, bool enable, {bool isLandscape = false}) async { final jsonString = _buildEnablePipJsonParams(enable, roomId, isLandscape: isLandscape); return TUILiveKitPlatform.instance.enablePictureInPicture(jsonString); @@ -47,25 +55,25 @@ class FloatWindowManager { bool isLandscape = false, Size canvasSize = const Size(720, 1280), }) { - double w = 1.0; - double h = isLandscape ? 9.0 / 16 * canvasSize.width / canvasSize.height : 1.0; - double x = 0.0; - double y = isLandscape ? (1 - h) / 2.0 : 0.0; Map jsonObject = { 'api': 'enablePictureInPicture', 'params': { "room_id": roomId, "enable": enable, "camBackgroundCapture": true, - "canvas": {"width": canvasSize.width, "height": canvasSize.height, "backgroundColor": "#000000"}, + "canvas": { + "width": isLandscape ? canvasSize.height : canvasSize.width, + "height": isLandscape ? canvasSize.width : canvasSize.height, + "backgroundColor": "#000000" + }, "regions": [ { "userId": "", "userName": "", - "width": w, - "height": h, - "x": x, - "y": y, + "width": 1, + "height": 1, + "x": 0, + "y": 0, "streamType": "high", "backgroundColor": "#000000", "backgroundImage": "" // /path/to/user1_placeholder.png @@ -85,9 +93,14 @@ class FloatWindowManager { } void _onFloatWindowModeChanged() { + final liveInfo = LiveListStore.shared.liveState.currentLive.value; + LiveKitLogger.info("Live(liveID=${liveInfo.liveID}) float window mode: ${floatWindowState.floatWindowMode.value}"); if (floatWindowState.isFloatWindowMode is ValueNotifier) { (floatWindowState.isFloatWindowMode as ValueNotifier).value = floatWindowState.floatWindowMode.value != FloatWindowMode.none; } + if (floatWindowState.isFloatWindowMode.value) { + GlobalFloatWindowManager.instance.setRoomId(liveInfo.liveID); + } } } diff --git a/live/livekit/lib/live_stream/manager/module/index.dart b/live/livekit/lib/live_stream/manager/module/index.dart index e60e3922..2d0439b5 100644 --- a/live/livekit/lib/live_stream/manager/module/index.dart +++ b/live/livekit/lib/live_stream/manager/module/index.dart @@ -1,4 +1,3 @@ -library module; export 'battle_manager.dart'; export 'co_guest_manager.dart'; diff --git a/live/livekit/lib/live_stream/manager/module/media_manager.dart b/live/livekit/lib/live_stream/manager/module/media_manager.dart index 626ba283..cdd7c4c4 100644 --- a/live/livekit/lib/live_stream/manager/module/media_manager.dart +++ b/live/livekit/lib/live_stream/manager/module/media_manager.dart @@ -14,7 +14,6 @@ import '../../api/live_stream_service.dart'; import '../../state/co_guest_state.dart'; import '../../state/media_state.dart'; import '../live_stream_manager.dart'; -import '../../../common/platform/index.dart'; const kIOSAppGroup = ''; //'group.com.tencent.fx.livekit' diff --git a/live/livekit/lib/live_stream/manager/observer/index.dart b/live/livekit/lib/live_stream/manager/observer/index.dart index 9d120674..54955c0c 100644 --- a/live/livekit/lib/live_stream/manager/observer/index.dart +++ b/live/livekit/lib/live_stream/manager/observer/index.dart @@ -1,3 +1,2 @@ -library observer; export 'room_engine_observer.dart'; diff --git a/live/livekit/lib/live_stream/state/float_window_state.dart b/live/livekit/lib/live_stream/state/float_window_state.dart index b72ef646..dd6a316e 100644 --- a/live/livekit/lib/live_stream/state/float_window_state.dart +++ b/live/livekit/lib/live_stream/state/float_window_state.dart @@ -7,4 +7,5 @@ class LSFloatWindowState { final ValueListenable floatWindowMode = ValueNotifier(FloatWindowMode.none); final ValueListenable isFloatWindowMode = ValueNotifier(false); final ValueListenable enablePipMode = ValueNotifier(false); + final ValueListenable isLandscape = ValueNotifier(false); } \ No newline at end of file diff --git a/live/livekit/lib/live_stream/state/index.dart b/live/livekit/lib/live_stream/state/index.dart index a1ae5e6b..3eb0b601 100644 --- a/live/livekit/lib/live_stream/state/index.dart +++ b/live/livekit/lib/live_stream/state/index.dart @@ -1,4 +1,3 @@ -library state; export 'battle_state.dart'; export 'co_guest_state.dart'; diff --git a/live/livekit/lib/seat_grid_widget/index.dart b/live/livekit/lib/seat_grid_widget/index.dart index aa9cf5be..0091ddf4 100644 --- a/live/livekit/lib/seat_grid_widget/index.dart +++ b/live/livekit/lib/seat_grid_widget/index.dart @@ -1,4 +1,3 @@ -library seat_grid_widget; export 'seat_grid_controller.dart'; export 'seat_grid_define.dart'; diff --git a/live/livekit/lib/seat_grid_widget/manager/index.dart b/live/livekit/lib/seat_grid_widget/manager/index.dart index 7368d308..550301b7 100644 --- a/live/livekit/lib/seat_grid_widget/manager/index.dart +++ b/live/livekit/lib/seat_grid_widget/manager/index.dart @@ -1,4 +1,3 @@ -library manager; export 'module/index.dart'; export 'seat_grid_widget_manager.dart'; \ No newline at end of file diff --git a/live/livekit/lib/seat_grid_widget/manager/module/index.dart b/live/livekit/lib/seat_grid_widget/manager/module/index.dart index 2c07e1ab..5a465e18 100644 --- a/live/livekit/lib/seat_grid_widget/manager/module/index.dart +++ b/live/livekit/lib/seat_grid_widget/manager/module/index.dart @@ -1,3 +1,2 @@ -library module; export 'widget_manager.dart'; \ No newline at end of file diff --git a/live/livekit/lib/seat_grid_widget/seat_grid_widget.dart b/live/livekit/lib/seat_grid_widget/seat_grid_widget.dart index 95e803be..10e6bdcb 100644 --- a/live/livekit/lib/seat_grid_widget/seat_grid_widget.dart +++ b/live/livekit/lib/seat_grid_widget/seat_grid_widget.dart @@ -1,4 +1,3 @@ -library seat_grid_widget; export 'seat_grid_controller.dart'; export 'seat_grid_define.dart'; @@ -120,8 +119,6 @@ class _SeatGridWidgetState extends State { case SeatWidgetLayoutRowAlignment.center: alignment = MainAxisAlignment.center; break; - default: - break; } return alignment; } diff --git a/live/livekit/lib/seat_grid_widget/state/index.dart b/live/livekit/lib/seat_grid_widget/state/index.dart index 4f388583..76808fc4 100644 --- a/live/livekit/lib/seat_grid_widget/state/index.dart +++ b/live/livekit/lib/seat_grid_widget/state/index.dart @@ -1,3 +1,2 @@ -library state; export 'widget_state.dart'; \ No newline at end of file diff --git a/live/livekit/lib/seat_grid_widget/widget/animated/index.dart b/live/livekit/lib/seat_grid_widget/widget/animated/index.dart index 454a0ebf..48041922 100644 --- a/live/livekit/lib/seat_grid_widget/widget/animated/index.dart +++ b/live/livekit/lib/seat_grid_widget/widget/animated/index.dart @@ -1,3 +1,2 @@ -library animated; export 'sound_wave_animated_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/seat_grid_widget/widget/index.dart b/live/livekit/lib/seat_grid_widget/widget/index.dart index 4316a129..fea5f740 100644 --- a/live/livekit/lib/seat_grid_widget/widget/index.dart +++ b/live/livekit/lib/seat_grid_widget/widget/index.dart @@ -1,4 +1,3 @@ -library widget; export 'default_seat_widget.dart'; export 'animated/index.dart'; \ No newline at end of file diff --git a/live/livekit/lib/tencent_live_uikit.dart b/live/livekit/lib/tencent_live_uikit.dart index b85bfdf9..d4704b78 100644 --- a/live/livekit/lib/tencent_live_uikit.dart +++ b/live/livekit/lib/tencent_live_uikit.dart @@ -1,4 +1,3 @@ -library tencent_live_uilit; export 'live_stream/features/index.dart'; export 'voice_room/voice_room_widget.dart'; diff --git a/live/livekit/lib/voice_room/index.dart b/live/livekit/lib/voice_room/index.dart index 96d4d110..bfa02ef5 100644 --- a/live/livekit/lib/voice_room/index.dart +++ b/live/livekit/lib/voice_room/index.dart @@ -1,4 +1,3 @@ -library voice_room; export 'widget/index.dart'; export 'voice_room_widget.dart'; diff --git a/live/livekit/lib/voice_room/manager/index.dart b/live/livekit/lib/voice_room/manager/index.dart index 8730f483..3bcfd236 100644 --- a/live/livekit/lib/voice_room/manager/index.dart +++ b/live/livekit/lib/voice_room/manager/index.dart @@ -1,4 +1,3 @@ -library manager; export 'toast_service.dart'; export 'voice_room_prepare_store.dart'; diff --git a/live/livekit/lib/voice_room/voice_room_overlay.dart b/live/livekit/lib/voice_room/voice_room_overlay.dart index 7b196363..21323b29 100644 --- a/live/livekit/lib/voice_room/voice_room_overlay.dart +++ b/live/livekit/lib/voice_room/voice_room_overlay.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:atomic_x_core/atomicxcore.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; @@ -52,8 +54,11 @@ class TUIVoiceRoomOverlayState extends State { } Widget buildOverlayContent() { + final Size fullScreenSize = Size(min(1.screenWidth, 1.screenHeight), max(1.screenWidth, 1.screenHeight)); final width = 60.width; return FloatWindowWidget( + x: fullScreenSize.width - width, + y: (fullScreenSize.height - width) / 2, padding: 0, size: Size(width, width), borderRadius: BorderRadius.circular(width / 2), diff --git a/live/livekit/lib/voice_room/widget/end_statistics/index.dart b/live/livekit/lib/voice_room/widget/end_statistics/index.dart index d5b73c5e..9a0d5044 100644 --- a/live/livekit/lib/voice_room/widget/end_statistics/index.dart +++ b/live/livekit/lib/voice_room/widget/end_statistics/index.dart @@ -1,4 +1,3 @@ -library end_statistics; export 'end_statistics_widget_define.dart'; export 'audience_end_statistics_widget.dart'; diff --git a/live/livekit/lib/voice_room/widget/index.dart b/live/livekit/lib/voice_room/widget/index.dart index f774e7c0..b9cca530 100644 --- a/live/livekit/lib/voice_room/widget/index.dart +++ b/live/livekit/lib/voice_room/widget/index.dart @@ -1,4 +1,3 @@ -library widget; export 'panel/index.dart'; export 'prepare/index.dart'; diff --git a/live/livekit/lib/voice_room/widget/panel/index.dart b/live/livekit/lib/voice_room/widget/panel/index.dart index 046a1883..3c0ff99c 100644 --- a/live/livekit/lib/voice_room/widget/panel/index.dart +++ b/live/livekit/lib/voice_room/widget/panel/index.dart @@ -1,4 +1,3 @@ -library panel; export 'live_background_select_widget.dart'; export 'live_cover_select_panel_widget.dart'; diff --git a/live/livekit/lib/voice_room/widget/panel/live_cover_select_panel_widget.dart b/live/livekit/lib/voice_room/widget/panel/live_cover_select_panel_widget.dart index ad50e166..6f0fedaa 100644 --- a/live/livekit/lib/voice_room/widget/panel/live_cover_select_panel_widget.dart +++ b/live/livekit/lib/voice_room/widget/panel/live_cover_select_panel_widget.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:tencent_live_uikit/voice_room/index.dart'; import 'package:tencent_live_uikit/voice_room/manager/voice_room_prepare_store.dart'; import '../../../common/index.dart'; @@ -150,4 +149,4 @@ extension on _LiveCoverSelectPanelWidgetState { widget.prepareStore.onSetRoomCoverUrl(widget.coverUrls[selectedIndex.value]); Navigator.of(context).pop(); } -} +} \ No newline at end of file diff --git a/live/livekit/lib/voice_room/widget/panel/seat_management_panel_widget.dart b/live/livekit/lib/voice_room/widget/panel/seat_management_panel_widget.dart index 4d1c06e6..864968c4 100644 --- a/live/livekit/lib/voice_room/widget/panel/seat_management_panel_widget.dart +++ b/live/livekit/lib/voice_room/widget/panel/seat_management_panel_widget.dart @@ -1,6 +1,5 @@ import 'package:atomic_x_core/atomicxcore.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; import 'package:tencent_live_uikit/voice_room/manager/index.dart'; import 'package:tencent_live_uikit/voice_room/widget/panel/seat_invitation_panel_widget.dart'; diff --git a/live/livekit/lib/voice_room/widget/panel/seat_mode_setting_widget.dart b/live/livekit/lib/voice_room/widget/panel/seat_mode_setting_widget.dart index 696534ba..807745f8 100644 --- a/live/livekit/lib/voice_room/widget/panel/seat_mode_setting_widget.dart +++ b/live/livekit/lib/voice_room/widget/panel/seat_mode_setting_widget.dart @@ -1,6 +1,5 @@ import 'package:atomic_x_core/api/live/live_list_store.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:rtc_room_engine/api/room/tui_room_define.dart'; import 'package:tencent_live_uikit/common/index.dart'; import 'package:tencent_live_uikit/voice_room/manager/index.dart'; diff --git a/live/livekit/lib/voice_room/widget/panel/settings_panel_widget.dart b/live/livekit/lib/voice_room/widget/panel/settings_panel_widget.dart index a50358c5..d64f6f1d 100644 --- a/live/livekit/lib/voice_room/widget/panel/settings_panel_widget.dart +++ b/live/livekit/lib/voice_room/widget/panel/settings_panel_widget.dart @@ -107,12 +107,10 @@ extension on _SettingsPanelWidgetState { final item = list[index]; switch (item.type) { case SettingsItemType.background: - widget.onTapSettingsPanelItem?.call(SettingsItemType.background); + widget.onTapSettingsPanelItem.call(SettingsItemType.background); break; case SettingsItemType.audioEffect: - widget.onTapSettingsPanelItem?.call(SettingsItemType.audioEffect); - break; - default: + widget.onTapSettingsPanelItem.call(SettingsItemType.audioEffect); break; } } @@ -125,7 +123,7 @@ extension on _SettingsPanelWidgetState { type: SettingsItemType.background), SettingsItem( title: LiveKitLocalizations.of(Global.appContext())!.common_audio_effect, - icon: LiveImages.settingsItemMusic, + icon: LiveImages.settingsItemAudioEffect, type: SettingsItemType.audioEffect) ]; } diff --git a/live/livekit/lib/voice_room/widget/prepare/index.dart b/live/livekit/lib/voice_room/widget/prepare/index.dart index 38a51914..e0d359be 100644 --- a/live/livekit/lib/voice_room/widget/prepare/index.dart +++ b/live/livekit/lib/voice_room/widget/prepare/index.dart @@ -1,4 +1,3 @@ -library prepare; export 'live_prepare_function_widget.dart'; export 'live_prepare_info_edit_widget.dart'; diff --git a/live/livekit/lib/voice_room/widget/prepare/live_prepare_info_edit_widget.dart b/live/livekit/lib/voice_room/widget/prepare/live_prepare_info_edit_widget.dart index aefcd61b..dd01fc06 100644 --- a/live/livekit/lib/voice_room/widget/prepare/live_prepare_info_edit_widget.dart +++ b/live/livekit/lib/voice_room/widget/prepare/live_prepare_info_edit_widget.dart @@ -256,8 +256,6 @@ extension on _LivePrepareInfoEditWidgetState { return LiveKitLocalizations.of(Global.appContext())!.common_stream_privacy_status_default; case PrivacyStatus.privacy: return LiveKitLocalizations.of(Global.appContext())!.common_stream_privacy_status_privacy; - default: - return LiveKitLocalizations.of(Global.appContext())!.common_stream_privacy_status_default; } } } diff --git a/live/livekit/lib/voice_room/widget/root/bottom_menu_widget.dart b/live/livekit/lib/voice_room/widget/root/bottom_menu_widget.dart index 23132c8d..8bfa1126 100644 --- a/live/livekit/lib/voice_room/widget/root/bottom_menu_widget.dart +++ b/live/livekit/lib/voice_room/widget/root/bottom_menu_widget.dart @@ -232,8 +232,6 @@ extension on _BottomMenuWidgetState { ), context: context); break; - default: - break; } } diff --git a/live/livekit/lib/voice_room/widget/root/index.dart b/live/livekit/lib/voice_room/widget/root/index.dart index b3455a26..717468e1 100644 --- a/live/livekit/lib/voice_room/widget/root/index.dart +++ b/live/livekit/lib/voice_room/widget/root/index.dart @@ -1,4 +1,3 @@ -library root; export 'top_widget.dart'; export 'bottom_menu_widget.dart'; \ No newline at end of file diff --git a/live/livekit/lib/voice_room/widget/root/top_widget.dart b/live/livekit/lib/voice_room/widget/root/top_widget.dart index 764cb888..da5605e5 100644 --- a/live/livekit/lib/voice_room/widget/root/top_widget.dart +++ b/live/livekit/lib/voice_room/widget/root/top_widget.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:tencent_live_uikit/common/index.dart'; -import 'package:tencent_live_uikit/component/float_window/global_float_window_manager.dart'; import 'package:tencent_live_uikit/component/index.dart'; enum TopWidgetTapEvent { stop, audienceList, liveInfo, floatWindow } diff --git a/live/livekit/lib/voice_room/widget/voice_room_root_widget.dart b/live/livekit/lib/voice_room/widget/voice_room_root_widget.dart index c2eb2088..295c20c1 100644 --- a/live/livekit/lib/voice_room/widget/voice_room_root_widget.dart +++ b/live/livekit/lib/voice_room/widget/voice_room_root_widget.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'dart:math'; + import 'package:atomic_x_core/atomicxcore.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:collection/collection.dart'; @@ -20,6 +23,7 @@ import 'package:tencent_live_uikit/voice_room/widget/end_statistics/end_statisti import 'package:tencent_live_uikit/voice_room/widget/panel/seat_invitation_panel_widget.dart'; import 'package:tencent_live_uikit/voice_room/widget/panel/user_management_panel_widget.dart'; import 'package:rtc_room_engine/rtc_room_engine.dart' hide DeviceStatus; +import 'package:tuikit_atomic_x/base_component/basic_controls/toast.dart'; import '../../component/float_window/global_float_window_manager.dart'; typedef ShowEndViewCallback = void Function(Map endInfo, bool isAnchor); @@ -92,6 +96,8 @@ class _VoiceRoomRootWidgetState extends State { late LiveAudienceListener _liveAudienceListener; late final LiveListListener _liveListListener; late HostListener _hostListener; + late final VoidCallback _loginStatusListener = _onLoginStatusChanged; + late final StreamSubscription _loginEventSubscription; @override void initState() { @@ -108,10 +114,20 @@ class _VoiceRoomRootWidgetState extends State { _join(liveID: widget.liveID); } _addObserver(); + _loginEventSubscription = LoginStore.shared.loginEventStream.listen((event) { + switch (event) { + case LoginEvent.kickedOffline: + case LoginEvent.loginExpired: + LiveKitLogger.warning("LoginEvent => $event"); + _closePage(); + break; + } + }); } @override void dispose() { + _loginEventSubscription.cancel(); _imStore.unInit(); _removeObserver(); _onDispose(); @@ -216,7 +232,11 @@ class _VoiceRoomRootWidgetState extends State { roomId: currentLive.liveID, ownerId: currentLive.liveOwner.userID, selfUserId: selfInfo.userId, - selfName: selfInfo.userName ?? selfInfo.userId); + selfName: selfInfo.userName ?? selfInfo.userId, + onError: (code, message) { + makeToast(context, ErrorHandler.convertToErrorMessage(code, message) ?? '', + type: ToastType.error); + }); _barrageDisplayController?.setCustomBarrageBuilder(GiftBarrageItemBuilder(selfUserId: selfInfo.userId)); return BarrageDisplayWidget(controller: _barrageDisplayController!); }), @@ -363,7 +383,7 @@ class _VoiceRoomRootWidgetState extends State { var endInfo = AnchorEndStatisticsWidgetInfo( roomId: widget.liveID, liveDuration: _liveStatisticsData.liveDuration, - viewCount: _liveStatisticsData.totalViewers, + viewCount: max(_liveStatisticsData.totalViewers - 1, 0), messageCount: _liveStatisticsData.totalMessageCount, giftIncome: _liveStatisticsData.totalGiftCoins, giftSenderCount: _liveStatisticsData.totalUniqueGiftSenders, @@ -500,8 +520,6 @@ extension _TopWidgetTapEventHandler on _VoiceRoomRootWidgetState { case TopWidgetTapEvent.floatWindow: widget.floatWindowController?.onTapSwitchFloatWindowInApp.call(true); break; - default: - break; } } @@ -566,6 +584,14 @@ extension _TopWidgetTapEventHandler on _VoiceRoomRootWidgetState { } } + void _onLoginStatusChanged() { + final loginStatus = LoginStore.shared.loginState.loginStatus; + if (loginStatus == LoginStatus.unlogin) { + LiveKitLogger.warning("LoginStatus => unlogin"); + _closePage(); + } + } + void _closePage() { if (GlobalFloatWindowManager.instance.isEnableFloatWindowFeature()) { GlobalFloatWindowManager.instance.overlayManager.closeOverlay(); @@ -710,6 +736,7 @@ extension _ObserverOperation on _VoiceRoomRootWidgetState { _liveSeatStore.liveSeatState.seatList.addListener(_seatListChangedListener); widget.floatWindowController?.isFullScreen.addListener(_isFloatWindowModeListener); LiveListStore.shared.addLiveListListener(_liveListListener); + LoginStore.shared.addListener(_loginStatusListener); } void _removeObserver() { @@ -722,6 +749,7 @@ extension _ObserverOperation on _VoiceRoomRootWidgetState { _liveListStore.removeLiveListListener(_liveListener); widget.floatWindowController?.isFullScreen.removeListener(_isFloatWindowModeListener); LiveListStore.shared.removeLiveListListener(_liveListListener); + LoginStore.shared.removeListener(_loginStatusListener); } } @@ -737,7 +765,6 @@ extension _SeatGridObserver on _VoiceRoomRootWidgetState { _exitedRoom.value = true; } }, onKickedOutOfLive: (liveID, reason, message) { - // TODO: LiveListStore not call onKickedOutOfLive _closePage(); }); _liveListStore.addLiveListListener(_liveListener); diff --git a/live/livekit/pubspec.yaml b/live/livekit/pubspec.yaml index 808c2d48..e51416a3 100644 --- a/live/livekit/pubspec.yaml +++ b/live/livekit/pubspec.yaml @@ -1,9 +1,15 @@ name: tencent_live_uikit description: "TUILiveKit enables interactive live streaming for scenarios such as social entertainment, shopping, and fitness classes." -version: 4.1.0 +version: 5.0.0 repository: https://github.com/Tencent-RTC/TUIKit_Flutter homepage: https://trtc.io/ +topics: + - livestream + - voiceroom + - pk + - battle + environment: sdk: '>=3.4.0 <4.0.0' @@ -20,15 +26,12 @@ dependencies: cached_network_image: ^3.4.1 tuikit_atomic_x: path: ../../atomic-x - atomic_x_core: ^4.1.0 - rtc_room_engine: ^4.1.0 - live_uikit_barrage: ^3.0.1 - live_uikit_gift: ^4.1.0 -# te_beauty_kit: -# path: ../../te_beauty_kit - - tencent_cloud_uikit_core: ^1.7.0 - tencent_cloud_chat_sdk: ^8.0.5901 + atomic_x_core: ^5.0.0 + rtc_room_engine: ^4.2.0 + live_uikit_barrage: ^3.0.2 + live_uikit_gift: ^4.1.1 + tencent_cloud_uikit_core: ^2.0.0 + tencent_cloud_chat_sdk: ^9.0.7652 tencent_rtc_sdk: '>=13.2.0' replay_kit_launcher: ^1.0.0