From 8e6865ddc01dd9e180819ac0f2e835aa60536eb2 Mon Sep 17 00:00:00 2001 From: Alex Shepeliev Date: Sun, 21 Apr 2024 12:22:20 +0300 Subject: [PATCH] Simplify sample app (#111) --- .gitignore | 10 +- build.gradle.kts | 57 +- buildSrc/build.gradle.kts | 11 +- buildSrc/settings.gradle.kts | 13 + buildSrc/src/main/kotlin/AndroidConfig.kt | 5 - .../src/main/kotlin/KotlinAndroidTarget.kt | 7 + .../kotlin/KotlinMultiplatformExtension.kt | 15 + .../kotlin/multiplatform-setup.gradle.kts | 37 - .../kotlin/webrtc.multiplatform.gradle.kts | 37 + gradle.properties | 15 +- gradle/libs.versions.toml | 58 + gradle/wrapper/gradle-wrapper.properties | 2 +- kotlin-js-store/yarn.lock | 1597 +++++------------ libs.versions.toml | 39 - sample/.gitignore | 16 + sample/README.md | 4 +- sample/app-android/build.gradle.kts | 59 - sample/app-android/google-services.json | 39 - .../com/shepeliev/webrtckmp/sample/App.kt | 31 - .../webrtckmp/sample/JoinRoomButton.kt | 57 - .../webrtckmp/sample/MainActivity.kt | 15 - .../sample/OpenMicrophoneAndCameraScreen.kt | 78 - .../shepeliev/webrtckmp/sample/VideoScreen.kt | 124 -- .../webrtckmp/sample/navigateToAppSettings.kt | 19 - .../src/main/res/drawable/ic_content_copy.xml | 5 - .../src/main/res/drawable/ic_launcher.xml | 15 - .../src/main/res/values/strings.xml | 3 - sample/app-ios/.gitignore | 221 --- sample/app-ios/Podfile | 6 - sample/app-ios/Podfile.lock | 774 -------- .../app-ios/app-ios.xcodeproj/project.pbxproj | 473 ----- .../xcshareddata/xcschemes/app-ios.xcscheme | 78 - .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - sample/app-ios/app-ios/AppDelegate.swift | 51 - .../AppIcon.appiconset/Contents.json | 98 - .../Base.lproj/LaunchScreen.storyboard | 25 - .../app-ios/Base.lproj/Main.storyboard | 137 -- .../app-ios/app-ios/GoogleService-Info.plist | 34 - sample/app-ios/app-ios/Info.plist | 29 - .../app-ios/app-ios/RoomViewController.swift | 164 -- sample/app-ios/app-ios/SceneDelegate.swift | 52 - .../app-ios/app-ios/StartViewController.swift | 21 - sample/app-web/build.gradle.kts | 20 - sample/app-web/src/main/kotlin/App.kt | 109 -- .../src/main/kotlin/LifecycleRegistry.kt | 18 - sample/app-web/src/main/kotlin/Main.kt | 22 - sample/composeApp/build.gradle.kts | 135 ++ .../composeApp.podspec} | 28 +- .../src/androidMain}/AndroidManifest.xml | 13 +- .../androidMain/kotlin/StartButton.android.kt | 61 + .../src/androidMain/kotlin/Video.android.kt} | 18 +- .../webrtckmp/sample/MainActivity.kt | 45 + .../drawable-v24/ic_launcher_foreground.xml | 30 + .../res/drawable/ic_launcher_background.xml | 170 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes .../src/androidMain/res/values/strings.xml | 3 + .../composeApp/src/commonMain/kotlin/App.kt | 122 ++ .../src/commonMain/kotlin/Hangup.kt | 15 + .../src/commonMain/kotlin/MakeCall.kt | 95 + .../src/commonMain/kotlin/StartButton.kt | 5 + .../composeApp/src/commonMain/kotlin/Video.kt | 6 + .../src/iosMain/kotlin/MainViewController.kt | 36 + .../src/iosMain/kotlin/StartButton.ios.kt | 11 + .../src/iosMain/kotlin/Video.ios.kt | 21 + sample/composeApp/src/jsMain/kotlin/App.kt | 139 ++ sample/composeApp/src/jsMain/kotlin/Main.kt | 17 + .../src/jsMain/kotlin/StartButton.js.kt | 7 + .../src/jsMain/kotlin/UseCoroutineScope.kt | 12 + .../composeApp/src/jsMain/kotlin/Video.js.kt | 8 + .../src/jsMain}/resources/index.html | 2 +- sample/iosApp/Configuration/Config.xcconfig | 3 + .../iosApp/iosApp.xcodeproj/project.pbxproj | 421 +++++ .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/swiftpm/Package.resolved | 14 + .../AccentColor.colorset/Contents.json | 2 +- .../AppIcon.appiconset/Contents.json | 14 + .../AppIcon.appiconset/app-icon-1024.png | Bin 0 -> 67285 bytes .../iosApp}/Assets.xcassets/Contents.json | 2 +- sample/iosApp/iosApp/ContentView.swift | 21 + sample/iosApp/iosApp/Info.plist | 54 + .../Preview Assets.xcassets/Contents.json | 6 + sample/iosApp/iosApp/iOSApp.swift | 10 + sample/shared/build.gradle.kts | 68 - .../webrtckmp/sample/shared/RoomDataSource.kt | 98 - .../shepeliev/webrtckmp/sample/shared/Room.kt | 23 - .../webrtckmp/sample/shared/RoomComponent.kt | 249 --- .../webrtckmp/sample/shared/RoomDataSource.kt | 19 - .../webrtckmp/sample/shared/RoomDataSource.kt | 143 -- .../sample/shared/DocumentSnapshotObserver.kt | 10 - .../webrtckmp/sample/shared/FirebaseApp.kt | 10 - .../webrtckmp/sample/shared/Firestore.kt | 52 - .../sample/shared/QuerySnapshotObserver.kt | 10 - .../webrtckmp/sample/shared/RoomDataSource.kt | 120 -- .../nativeInterop/cinterop/FirebaseCore.def | 3 - .../cinterop/FirebaseFirestore.def | 3 - settings.gradle.kts | 11 +- webrtc-kmp/build.gradle.kts | 28 +- .../webrtckmp/ApplicationContextHolder.kt | 2 + .../com/shepeliev/webrtckmp/PeerConnection.kt | 56 +- .../kotlin/com/shepeliev/webrtckmp/WebRtc.kt | 105 +- .../com/shepeliev/webrtckmp/PeerConnection.kt | 57 +- .../kotlin/com/shepeliev/webrtckmp/WebRtc.kt | 65 +- .../webrtckmp/MediaStreamTrackImpl.kt | 2 +- .../shepeliev/webrtckmp/VideoStreamTrack.kt | 4 + 117 files changed, 2309 insertions(+), 5103 deletions(-) delete mode 100644 buildSrc/src/main/kotlin/AndroidConfig.kt create mode 100644 buildSrc/src/main/kotlin/KotlinAndroidTarget.kt create mode 100644 buildSrc/src/main/kotlin/KotlinMultiplatformExtension.kt delete mode 100644 buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts create mode 100644 buildSrc/src/main/kotlin/webrtc.multiplatform.gradle.kts create mode 100644 gradle/libs.versions.toml delete mode 100644 libs.versions.toml create mode 100644 sample/.gitignore delete mode 100644 sample/app-android/build.gradle.kts delete mode 100644 sample/app-android/google-services.json delete mode 100644 sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/App.kt delete mode 100644 sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/JoinRoomButton.kt delete mode 100644 sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/MainActivity.kt delete mode 100644 sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/OpenMicrophoneAndCameraScreen.kt delete mode 100644 sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/VideoScreen.kt delete mode 100644 sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/navigateToAppSettings.kt delete mode 100644 sample/app-android/src/main/res/drawable/ic_content_copy.xml delete mode 100644 sample/app-android/src/main/res/drawable/ic_launcher.xml delete mode 100644 sample/app-android/src/main/res/values/strings.xml delete mode 100644 sample/app-ios/.gitignore delete mode 100644 sample/app-ios/Podfile delete mode 100644 sample/app-ios/Podfile.lock delete mode 100644 sample/app-ios/app-ios.xcodeproj/project.pbxproj delete mode 100644 sample/app-ios/app-ios.xcodeproj/xcshareddata/xcschemes/app-ios.xcscheme delete mode 100644 sample/app-ios/app-ios.xcworkspace/contents.xcworkspacedata delete mode 100644 sample/app-ios/app-ios.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 sample/app-ios/app-ios/AppDelegate.swift delete mode 100644 sample/app-ios/app-ios/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 sample/app-ios/app-ios/Base.lproj/LaunchScreen.storyboard delete mode 100644 sample/app-ios/app-ios/Base.lproj/Main.storyboard delete mode 100644 sample/app-ios/app-ios/GoogleService-Info.plist delete mode 100644 sample/app-ios/app-ios/Info.plist delete mode 100644 sample/app-ios/app-ios/RoomViewController.swift delete mode 100644 sample/app-ios/app-ios/SceneDelegate.swift delete mode 100644 sample/app-ios/app-ios/StartViewController.swift delete mode 100644 sample/app-web/build.gradle.kts delete mode 100644 sample/app-web/src/main/kotlin/App.kt delete mode 100644 sample/app-web/src/main/kotlin/LifecycleRegistry.kt delete mode 100644 sample/app-web/src/main/kotlin/Main.kt create mode 100644 sample/composeApp/build.gradle.kts rename sample/{shared/shared.podspec => composeApp/composeApp.podspec} (61%) rename sample/{app-android/src/main => composeApp/src/androidMain}/AndroidManifest.xml (50%) create mode 100644 sample/composeApp/src/androidMain/kotlin/StartButton.android.kt rename sample/{app-android/src/main/java/com/shepeliev/webrtckmp/sample/Video.kt => composeApp/src/androidMain/kotlin/Video.android.kt} (83%) create mode 100644 sample/composeApp/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/MainActivity.kt create mode 100644 sample/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 sample/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml create mode 100644 sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 sample/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sample/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sample/composeApp/src/androidMain/res/values/strings.xml create mode 100644 sample/composeApp/src/commonMain/kotlin/App.kt create mode 100644 sample/composeApp/src/commonMain/kotlin/Hangup.kt create mode 100644 sample/composeApp/src/commonMain/kotlin/MakeCall.kt create mode 100644 sample/composeApp/src/commonMain/kotlin/StartButton.kt create mode 100644 sample/composeApp/src/commonMain/kotlin/Video.kt create mode 100644 sample/composeApp/src/iosMain/kotlin/MainViewController.kt create mode 100644 sample/composeApp/src/iosMain/kotlin/StartButton.ios.kt create mode 100644 sample/composeApp/src/iosMain/kotlin/Video.ios.kt create mode 100644 sample/composeApp/src/jsMain/kotlin/App.kt create mode 100644 sample/composeApp/src/jsMain/kotlin/Main.kt create mode 100644 sample/composeApp/src/jsMain/kotlin/StartButton.js.kt create mode 100644 sample/composeApp/src/jsMain/kotlin/UseCoroutineScope.kt create mode 100644 sample/composeApp/src/jsMain/kotlin/Video.js.kt rename sample/{app-web/src/main => composeApp/src/jsMain}/resources/index.html (79%) create mode 100644 sample/iosApp/Configuration/Config.xcconfig create mode 100644 sample/iosApp/iosApp.xcodeproj/project.pbxproj rename sample/{app-ios/app-ios.xcodeproj => iosApp/iosApp.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) rename sample/{app-ios/app-ios.xcodeproj => iosApp/iosApp.xcodeproj}/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) create mode 100644 sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename sample/{app-ios/app-ios => iosApp/iosApp}/Assets.xcassets/AccentColor.colorset/Contents.json (98%) create mode 100644 sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png rename sample/{app-ios/app-ios => iosApp/iosApp}/Assets.xcassets/Contents.json (96%) create mode 100644 sample/iosApp/iosApp/ContentView.swift create mode 100644 sample/iosApp/iosApp/Info.plist create mode 100644 sample/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 sample/iosApp/iosApp/iOSApp.swift delete mode 100644 sample/shared/build.gradle.kts delete mode 100644 sample/shared/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt delete mode 100644 sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Room.kt delete mode 100644 sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomComponent.kt delete mode 100644 sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt delete mode 100644 sample/shared/src/iosMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt delete mode 100644 sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/DocumentSnapshotObserver.kt delete mode 100644 sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/FirebaseApp.kt delete mode 100644 sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Firestore.kt delete mode 100644 sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/QuerySnapshotObserver.kt delete mode 100644 sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt delete mode 100644 sample/shared/src/nativeInterop/cinterop/FirebaseCore.def delete mode 100644 sample/shared/src/nativeInterop/cinterop/FirebaseFirestore.def diff --git a/.gitignore b/.gitignore index 10460a19..be15d602 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,10 @@ *.iml .gradle -/local.properties -/.idea +.idea +local.properties .DS_Store build/ -/captures +captures +xcuserdata .externalNativeBuild .cxx -local.properties -Carthage/ -Cartfile.resolved diff --git a/build.gradle.kts b/build.gradle.kts index 559f5ea7..2a90d3cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,43 +1,42 @@ import java.util.Properties plugins { - id("com.google.gms.google-services") version "4.3.15" apply false - id("io.github.gradle-nexus.publish-plugin") version "1.3.0" + alias(libs.plugins.ktlint) + alias(libs.plugins.nexus) + alias(libs.plugins.jetbrains.compose) apply false } -allprojects { - val localProps = Properties() - val localPropertiesFile = rootProject.file("local.properties") - if (localPropertiesFile.exists()) { - localPropertiesFile.inputStream().use { localProps.load(it) } - } +val localProps = Properties() +val localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.inputStream().use { localProps.load(it) } +} - val signingKey by extra( - localProps.getOrDefault("signing.key", System.getenv("SIGNING_KEY") ?: "") - ) - val signingPassword by extra( - localProps.getOrDefault("signing.password", System.getenv("SIGNING_PASSWORD") ?: "") - ) - val ossrhUsername by extra( - localProps.getOrDefault("ossrhUsername", System.getenv("OSSRH_USERNAME") ?: "") +val signingKey by extra( + localProps.getOrDefault("signing.key", System.getenv("SIGNING_KEY") ?: "") +) +val signingPassword by extra( + localProps.getOrDefault("signing.password", System.getenv("SIGNING_PASSWORD") ?: "") +) +val ossrhUsername by extra( + localProps.getOrDefault("ossrhUsername", System.getenv("OSSRH_USERNAME") ?: "") +) +val ossrhPassword by extra( + localProps.getOrDefault("ossrhPassword", System.getenv("OSSRH_PASSWORD") ?: "") +) +val sonatypeStagingProfileId by extra( + localProps.getOrDefault( + "sonatypeStagingProfileId", + System.getenv("SONATYPE_STAGING_PROFILE_ID") ?: "" ) - val ossrhPassword by extra( - localProps.getOrDefault("ossrhPassword", System.getenv("OSSRH_PASSWORD") ?: "") - ) - val sonatypeStagingProfileId by extra( - localProps.getOrDefault( - "sonatypeStagingProfileId", - System.getenv("SONATYPE_STAGING_PROFILE_ID") ?: "" - ) - ) -} +) nexusPublishing { this.repositories { sonatype { - val sonatypeStagingProfileId: String by extra - val ossrhUsername: String by extra - val ossrhPassword: String by extra + val sonatypeStagingProfileId: String by rootProject.extra + val ossrhUsername: String by rootProject.extra + val ossrhPassword: String by rootProject.extra stagingProfileId.set(sonatypeStagingProfileId) username.set(ossrhUsername) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 31e7ee59..618f6814 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -2,14 +2,7 @@ plugins { `kotlin-dsl` } -repositories { - gradlePluginPortal() - mavenCentral() - google() -} - dependencies { - implementation("com.android.tools.build:gradle:8.0.2") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") - implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0") + implementation(libs.kotlin.plugin) + implementation(libs.agp.plugin) } diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index e69de29b..75df2751 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,13 @@ +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } + + repositories { + gradlePluginPortal() + mavenCentral() + google() + } +} diff --git a/buildSrc/src/main/kotlin/AndroidConfig.kt b/buildSrc/src/main/kotlin/AndroidConfig.kt deleted file mode 100644 index c5a3388c..00000000 --- a/buildSrc/src/main/kotlin/AndroidConfig.kt +++ /dev/null @@ -1,5 +0,0 @@ -object AndroidConfig { - const val minSdkVersion = 21 - const val compileSdkVersion = 33 - const val targetSdkVersion = 33 -} diff --git a/buildSrc/src/main/kotlin/KotlinAndroidTarget.kt b/buildSrc/src/main/kotlin/KotlinAndroidTarget.kt new file mode 100644 index 00000000..4acc43b1 --- /dev/null +++ b/buildSrc/src/main/kotlin/KotlinAndroidTarget.kt @@ -0,0 +1,7 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget + +fun KotlinAndroidTarget.configureJvmTarget(jvmVersion: String = "1.8") { + compilations.all { + kotlinOptions.jvmTarget = jvmVersion + } +} diff --git a/buildSrc/src/main/kotlin/KotlinMultiplatformExtension.kt b/buildSrc/src/main/kotlin/KotlinMultiplatformExtension.kt new file mode 100644 index 00000000..00627579 --- /dev/null +++ b/buildSrc/src/main/kotlin/KotlinMultiplatformExtension.kt @@ -0,0 +1,15 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +fun KotlinMultiplatformExtension.configureKotlinCompilerArgs(vararg args: String) { + targets.all { + compilations.all { + kotlinOptions { + freeCompilerArgs += setOf( + "-opt-in=kotlin.RequiresOptIn", + "-Xexpect-actual-classes", + *args + ) + } + } + } +} diff --git a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts deleted file mode 100644 index bd2ce71f..00000000 --- a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - id("com.android.library") - kotlin("multiplatform") - id("org.jlleitschuh.gradle.ktlint") -} - -kotlin { - targets.all { - compilations.all { - kotlinOptions { - freeCompilerArgs += listOf( - "-opt-in=kotlin.RequiresOptIn", - "-Xexpect-actual-classes", - ) - } - } - } -} - -android { - compileSdk = AndroidConfig.compileSdkVersion - - sourceSets.named("main") { - manifest.srcFile("src/androidMain/AndroidManifest.xml") - res.srcDir("src/androidMain/res") - } - - defaultConfig { - minSdk = AndroidConfig.minSdkVersion - targetSdk = AndroidConfig.targetSdkVersion - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } -} diff --git a/buildSrc/src/main/kotlin/webrtc.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/webrtc.multiplatform.gradle.kts new file mode 100644 index 00000000..747520de --- /dev/null +++ b/buildSrc/src/main/kotlin/webrtc.multiplatform.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("com.android.library") + kotlin("multiplatform") +} + +kotlin { + configureKotlinCompilerArgs() + + androidTarget { + configureJvmTarget() + } +} + +android { + compileSdk = androidCompileSdkVersion + + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDir("src/androidMain/res") + + defaultConfig { + minSdk = androidMinSdkVersion + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +private val Project.versionCatalog: VersionCatalog + get() = extensions.getByType().named("libs") + +private val Project.androidCompileSdkVersion: Int + get() = "${versionCatalog.findVersion("compileSdk").get()}".toInt() + +private val Project.androidMinSdkVersion: Int + get() = "${versionCatalog.findVersion("minSdk").get()}".toInt() diff --git a/gradle.properties b/gradle.properties index a69d6ad6..af87efa2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,26 @@ +# Gradle org.gradle.caching=true org.gradle.daemon=true org.gradle.parallel=true org.gradle.jvmargs=-Xmx2g +# Kotlin kotlin.code.style=official -kotlin.mpp.enableCInteropCommonization=true kotlin.js.compiler=ir +# Android +android.nonTransitiveRClass=true android.useAndroidX=true - # Workaround for java.lang.NoSuchMethodError: No static method createEgl14([I)Lorg/webrtc/EglBase14; exception # https://issuetracker.google.com/issues/265195801 -android.enableDexingArtifactTransform=false +android.useFullClasspathForDexingTransform = true + +# KMP +kotlin.mpp.androidSourceSetLayoutVersion=2 +kotlin.mpp.enableCInteropCommonization=true + +#Compose +org.jetbrains.compose.experimental.jscanvas.enabled=true # Versions webRtcKmpVersion=0.114.4 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..ae0623c4 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,58 @@ +[versions] +kotlin = "1.9.23" +kotlin-coroutines = "1.8.0" +androidx-activity-compose = "1.9.0" +androidx-appcompat = "1.6.1" +androidx-core = "1.13.0" +androidx-material = "1.11.0" +androidx-lifecycle = "2.7.0" +androidx-test-core = "1.5.0" +androidx-test-runner = "1.5.2" +androidx-test-rules = "1.5.0" +accompanist-permision = "0.34.0" +compose = "1.6.6" +kermit = "2.0.3" +kotlin-wrappers = "1.0.0-pre.732" +webrtc-sdk = "114.5735.02" + +#Android +minSdk = "21" +compileSdk = "34" +targetSdk = "34" + +# Plugins +agp = "8.2.2" +ktlint = "10.3.0" +nexus = "1.3.0" +compose-plugin = "1.6.2" + +[libraries] +webrtc-sdk = { module = "io.github.webrtc-sdk:android", version.ref = "webrtc-sdk" } +kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } +kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlin-coroutines" } +androidx-coreKtx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } +androidx-material = { module = "com.google.android.material:material", version.ref = "androidx-material" } +accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist-permision" } +compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } +compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } +kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } +androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" } +androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } +androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" } +kotlin-wrappers-bom = { module = "org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom", version.ref = "kotlin-wrappers" } +kotlin-wrappers-emotion = { module = "org.jetbrains.kotlin-wrappers:kotlin-emotion" } +kotlin-wrappers-react = { module = "org.jetbrains.kotlin-wrappers:kotlin-react" } +kotlin-wrappers-reactDom = { module = "org.jetbrains.kotlin-wrappers:kotlin-react-dom" } +kotlin-wrappers-mui = { module = "org.jetbrains.kotlin-wrappers:kotlin-mui-material" } + +# Plugin dependencies +kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +agp-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } + +[plugins] +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } +nexus = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus" } +jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 15de9024..e411586a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index f4e39534..2cedcd06 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -3,64 +3,54 @@ "@babel/code-frame@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" "@babel/helper-module-imports@^7.16.7": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" - integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== - -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== - -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== -"@babel/plugin-syntax-jsx@^7.17.12": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" - integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" - integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== +"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.14.0" -"@babel/types@^7.18.6": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6" - integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== +"@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" "@colors/colors@1.5.0": @@ -68,568 +58,155 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@date-io/core@^2.14.0": - version "2.14.0" - resolved "https://registry.yarnpkg.com/@date-io/core/-/core-2.14.0.tgz#03e9b9b9fc8e4d561c32dd324df0f3ccd967ef14" - integrity sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw== - -"@date-io/date-fns@^2.14.0": - version "2.14.0" - resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-2.14.0.tgz#92ab150f488f294c135c873350d154803cebdbea" - integrity sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA== - dependencies: - "@date-io/core" "^2.14.0" - -"@date-io/dayjs@^2.14.0": - version "2.14.0" - resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-2.14.0.tgz#8d4e93e1d473bb5f25210866204dc33384ca4c20" - integrity sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og== - dependencies: - "@date-io/core" "^2.14.0" - -"@date-io/luxon@^2.14.0": - version "2.14.0" - resolved "https://registry.yarnpkg.com/@date-io/luxon/-/luxon-2.14.0.tgz#cd1641229e00a899625895de3a31e3aaaf66629f" - integrity sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg== - dependencies: - "@date-io/core" "^2.14.0" - -"@date-io/moment@^2.14.0": - version "2.14.0" - resolved "https://registry.yarnpkg.com/@date-io/moment/-/moment-2.14.0.tgz#8300abd6ae8c55d8edee90d118db3cef0b1d4f58" - integrity sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA== - dependencies: - "@date-io/core" "^2.14.0" - "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@emotion/babel-plugin@^11.10.0": - version "11.10.0" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.0.tgz#ae545b8faa6b42d3a50ec86b70b758296f3c4467" - integrity sha512-xVnpDAAbtxL1dsuSelU5A7BnY/lftws0wUexNJZTPsvX/1tM4GZJbclgODhvW4E+NH7E5VFcH0bBn30NvniPJA== +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== dependencies: "@babel/helper-module-imports" "^7.16.7" - "@babel/plugin-syntax-jsx" "^7.17.12" "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.0" - "@emotion/memoize" "^0.8.0" - "@emotion/serialize" "^1.1.0" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" babel-plugin-macros "^3.1.0" convert-source-map "^1.5.0" escape-string-regexp "^4.0.0" find-root "^1.1.0" source-map "^0.5.7" - stylis "4.0.13" - -"@emotion/cache@^11.10.0", "@emotion/cache@^11.9.3": - version "11.10.1" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.1.tgz#75a157c2a6bb9220450f73ebef1df2e1467dc65d" - integrity sha512-uZTj3Yz5D69GE25iFZcIQtibnVCFsc/6+XIozyL3ycgWvEdif2uEw9wlUt6umjLr4Keg9K6xRPHmD8LGi+6p1A== - dependencies: - "@emotion/memoize" "^0.8.0" - "@emotion/sheet" "^1.2.0" - "@emotion/utils" "^1.2.0" - "@emotion/weak-memoize" "^0.3.0" - stylis "4.0.13" - -"@emotion/css@^11.9.0": - version "11.10.0" - resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.10.0.tgz#270b4fdf2419e59cb07081d0e9f7940d88b8b443" - integrity sha512-dH9f+kSCucc8ilMg0MUA1AemabcyzYpe5EKX24F528PJjD7HyIY/VBNJHxfUdc8l400h2ncAjR6yEDu+DBj2cg== - dependencies: - "@emotion/babel-plugin" "^11.10.0" - "@emotion/cache" "^11.10.0" - "@emotion/serialize" "^1.1.0" - "@emotion/sheet" "^1.2.0" - "@emotion/utils" "^1.2.0" - -"@emotion/hash@^0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" - integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== - -"@emotion/is-prop-valid@^1.1.3", "@emotion/is-prop-valid@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" - integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== - dependencies: - "@emotion/memoize" "^0.8.0" - -"@emotion/memoize@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" - integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== - -"@emotion/react@^11.9.0": - version "11.10.0" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.0.tgz#53c577f063f26493f68a05188fb87528d912ff2e" - integrity sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ== + stylis "4.2.0" + +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + +"@emotion/css@^11.11.2": + version "11.11.2" + resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.11.2.tgz#e5fa081d0c6e335352e1bc2b05953b61832dca5a" + integrity sha512-VJxe1ucoMYMS7DkiMdC2T7PWNbrEI0a39YRiyDvK2qq4lXwjRbVP/z4lpG+odCsRzadlR+1ywwrTzhdm5HNdew== + dependencies: + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.2" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + +"@emotion/is-prop-valid@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/react@^11.11.4": + version "11.11.4" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" + integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.10.0" - "@emotion/cache" "^11.10.0" - "@emotion/serialize" "^1.1.0" - "@emotion/utils" "^1.2.0" - "@emotion/weak-memoize" "^0.3.0" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.0.tgz#b1f97b1011b09346a40e9796c37a3397b4ea8ea8" - integrity sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA== +"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3", "@emotion/serialize@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.4.tgz#fc8f6d80c492cfa08801d544a05331d1cc7cd451" + integrity sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ== dependencies: - "@emotion/hash" "^0.9.0" - "@emotion/memoize" "^0.8.0" - "@emotion/unitless" "^0.8.0" - "@emotion/utils" "^1.2.0" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" csstype "^3.0.2" -"@emotion/sheet@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.0.tgz#771b1987855839e214fc1741bde43089397f7be5" - integrity sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w== +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== -"@emotion/styled@^11.8.1": - version "11.10.0" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.0.tgz#c19484dab4206ae46727c07efb4316423dd21312" - integrity sha512-V9oaEH6V4KePeQpgUE83i8ht+4Ri3E8Djp/ZPJ4DQlqWhSKITvgzlR3/YQE2hdfP4Jw3qVRkANJz01LLqK9/TA== +"@emotion/styled@^11.11.0": + version "11.11.5" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.5.tgz#0c5c8febef9d86e8a926e663b2e5488705545dfb" + integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.10.0" - "@emotion/is-prop-valid" "^1.2.0" - "@emotion/serialize" "^1.1.0" - "@emotion/utils" "^1.2.0" - -"@emotion/unitless@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" - integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== - -"@emotion/utils@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" - integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== - -"@emotion/weak-memoize@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" - integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== - -"@firebase/analytics-compat@0.1.13": - version "0.1.13" - resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.1.13.tgz#61e1d6f9e4d033c3ed9943d91530eb3e0f382f92" - integrity sha512-QC1DH/Dwc8fBihn0H+jocBWyE17GF1fOCpCrpAiQ2u16F/NqsVDVG4LjIqdhq963DXaXneNY7oDwa25Up682AA== - dependencies: - "@firebase/analytics" "0.8.0" - "@firebase/analytics-types" "0.7.0" - "@firebase/component" "0.5.17" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/analytics-types@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.7.0.tgz#91960e7c87ce8bf18cf8dd9e55ccbf5dc3989b5d" - integrity sha512-DNE2Waiwy5+zZnCfintkDtBfaW6MjIG883474v6Z0K1XZIvl76cLND4iv0YUb48leyF+PJK1KO2XrgHb/KpmhQ== - -"@firebase/analytics@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.8.0.tgz#b5d595082f57d33842b1fd9025d88f83065e87fe" - integrity sha512-wkcwainNm8Cu2xkJpDSHfhBSdDJn86Q1TZNmLWc67VrhZUHXIKXxIqb65/tNUVE+I8+sFiDDNwA+9R3MqTQTaA== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/installations" "0.5.12" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/app-check-compat@0.2.12": - version "0.2.12" - resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.2.12.tgz#e30b2395e3d30f8cfcf3554fc87875f82c1aa086" - integrity sha512-GFppNLlUyMN9Iq31ME/+GkjRVKlc+MeanzUKQ9UaR73ZsYH3oX3Ja+xjoYgixaVJDDG+ofBYR7ZXTkkQdSR/pw== - dependencies: - "@firebase/app-check" "0.5.12" - "@firebase/app-check-types" "0.4.0" - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/app-check-interop-types@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.0.tgz#83afd9d41f99166c2bdb2d824e5032e9edd8fe53" - integrity sha512-uZfn9s4uuRsaX5Lwx+gFP3B6YsyOKUE+Rqa6z9ojT4VSRAsZFko9FRn6OxQUA1z5t5d08fY4pf+/+Dkd5wbdbA== - -"@firebase/app-check-types@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.4.0.tgz#7007a9d1d720db20bcf466fe6785c96feaa0a82d" - integrity sha512-SsWafqMABIOu7zLgWbmwvHGOeQQVQlwm42kwwubsmfLmL4Sf5uGpBfDhQ0CAkpi7bkJ/NwNFKafNDL9prRNP0Q== - -"@firebase/app-check@0.5.12": - version "0.5.12" - resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.5.12.tgz#82f305cc01bfe4d32c35e425941b2eca2ce9f089" - integrity sha512-l+MmvupSGT/F+I5ei7XjhEfpoL4hLVJr0vUwcG5NEf2hAkQnySli9fnbl9fZu1BJaQ2kthrMmtg1gcbcM9BUCQ== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/app-compat@0.1.30": - version "0.1.30" - resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.1.30.tgz#027542ec59e1a482edb8dc90b611f3c47e0a7a9b" - integrity sha512-t51oJEJzjts4D5C7Nol0Ua7dqhpQSlcWSa7X1VtL+zjcTZ92ibYmwQjXomexBmlKvCUamGClMAEBfEgUtr0Wug== - dependencies: - "@firebase/app" "0.7.29" - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/app-types@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.7.0.tgz#c9e16d1b8bed1a991840b8d2a725fb58d0b5899f" - integrity sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg== - -"@firebase/app@0.7.29": - version "0.7.29" - resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.7.29.tgz#404fcc7f130b1829350d3a2e20d2d350e0c54b91" - integrity sha512-jT47plTi/O0lpXEXPx5t/dH/3BVnP9Tq/D8SZkhMUXPYlYDudvepIiV3VOW8XxbbHU/X+JyY0qG5CoWxIk0teg== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - idb "7.0.1" - tslib "^2.1.0" - -"@firebase/auth-compat@0.2.18": - version "0.2.18" - resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.2.18.tgz#c7bb254fbb23447069f81abb15f96e91de40b285" - integrity sha512-Fw2PJS0G/tGrfyEBcYJQ42sfy5+sANrK5xd7tuzgV7zLFW5rYkHUIZngXjuOBwLOcfO2ixa/FavfeJle3oJ38Q== - dependencies: - "@firebase/auth" "0.20.5" - "@firebase/auth-types" "0.11.0" - "@firebase/component" "0.5.17" - "@firebase/util" "1.6.3" - node-fetch "2.6.7" - selenium-webdriver "4.1.2" - tslib "^2.1.0" - -"@firebase/auth-interop-types@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz#5ce13fc1c527ad36f1bb1322c4492680a6cf4964" - integrity sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g== - -"@firebase/auth-types@0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.11.0.tgz#b9c73c60ca07945b3bbd7a097633e5f78fa9e886" - integrity sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw== - -"@firebase/auth@0.20.5": - version "0.20.5" - resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.20.5.tgz#a2e6c6b593d8f9cf8276a7d1f8ab5b055d65cc50" - integrity sha512-SbKj7PCAuL0lXEToUOoprc1im2Lr/bzOePXyPC7WWqVgdVBt0qovbfejlzKYwJLHUAPg9UW1y3XYe3IlbXr77w== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - node-fetch "2.6.7" - selenium-webdriver "4.1.2" - tslib "^2.1.0" - -"@firebase/component@0.5.17": - version "0.5.17" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.5.17.tgz#89291f378714df05d44430c524708669380d8ea6" - integrity sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q== - dependencies: - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/database-compat@0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-0.2.3.tgz#023ee1444088dd49714f93dcb2dff63d4b9a8589" - integrity sha512-uwSMnbjlSQM5gQRq8OoBLs7uc7obwsl0D6kSDAnMOlPtPl9ert79Rq9faU/COjybsJ8l7tNXMVYYJo3mQ5XNrA== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/database" "0.13.3" - "@firebase/database-types" "0.9.11" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/database-types@0.9.11": - version "0.9.11" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.9.11.tgz#ac8881e20e490d1557c8975aa3e7815dbf03b2e6" - integrity sha512-27V3eFomWCZqLR6qb3Q9eS2lsUtulhSHeDNaL6fImwnhvMYTmf6ZwMfRWupgi8AFwW4s91g9Oc1/fkQtJGHKQw== - dependencies: - "@firebase/app-types" "0.7.0" - "@firebase/util" "1.6.3" - -"@firebase/database@0.13.3": - version "0.13.3" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.13.3.tgz#cf9acc03434c89e0bcb8b8ab3a6d2a70e342b8c5" - integrity sha512-ZE+QJqQUaCTZiIzGq3RJLo64HRMtbdaEwyDhfZyPEzMJV4kyLsw3cHdEHVCtBmdasTvwtpO2YRFmd4AXAoKtNw== - dependencies: - "@firebase/auth-interop-types" "0.1.6" - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - faye-websocket "0.11.4" - tslib "^2.1.0" - -"@firebase/firestore-compat@0.1.22": - version "0.1.22" - resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.1.22.tgz#535e51d9034c08a7f4ebb67e0de86459ab4c5494" - integrity sha512-1HWmJtbxhDzAV7984XSQX7tp0MzjhRFnBygpU6k6H2m0Ey9JVDTPK8lIlZCctjCCA2cBsek7yAD+rDnpWC+KRw== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/firestore" "3.4.13" - "@firebase/firestore-types" "2.5.0" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/firestore-types@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-2.5.0.tgz#16fca40b6980fdb000de86042d7a96635f2bcdd7" - integrity sha512-I6c2m1zUhZ5SH0cWPmINabDyH5w0PPFHk2UHsjBpKdZllzJZ2TwTkXbDtpHUZNmnc/zAa0WNMNMvcvbb/xJLKA== - -"@firebase/firestore@3.4.13": - version "3.4.13" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-3.4.13.tgz#c934f72fed311f9359cff3c836ab43d492c85b55" - integrity sha512-wLsbWflFoWDg9NprulzTAjtapLA3dfaG1Dsa9OUsgPRDGg5jfeo60n43d94fesX4crE+C5vkFhLQKMgsEGpr9w== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - "@firebase/webchannel-wrapper" "0.6.2" - "@grpc/grpc-js" "^1.3.2" - "@grpc/proto-loader" "^0.6.13" - node-fetch "2.6.7" - tslib "^2.1.0" - -"@firebase/functions-compat@0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.2.4.tgz#afa5d8eefe6d51c7b89e44d9262700b68fbcb73f" - integrity sha512-Crfn6il1yXGuXkjSd8nKrqR4XxPvuP19g64bXpM6Ix67qOkQg676kyOuww0FF17xN0NSXHfG8Pyf+CUrx8wJ5g== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/functions" "0.8.4" - "@firebase/functions-types" "0.5.0" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/functions-types@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.5.0.tgz#b50ba95ccce9e96f7cda453228ffe1684645625b" - integrity sha512-qza0M5EwX+Ocrl1cYI14zoipUX4gI/Shwqv0C1nB864INAD42Dgv4v94BCyxGHBg2kzlWy8PNafdP7zPO8aJQA== - -"@firebase/functions@0.8.4": - version "0.8.4" - resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.8.4.tgz#a9b7a10314f286df1ded87d8546fb8d9107a9c06" - integrity sha512-o1bB0xMyQKe+b246zGnjwHj4R6BH4mU2ZrSaa/3QvTpahUQ3hqYfkZPLOXCU7+vEFxHb3Hd4UUjkFhxoAcPqLA== - dependencies: - "@firebase/app-check-interop-types" "0.1.0" - "@firebase/auth-interop-types" "0.1.6" - "@firebase/component" "0.5.17" - "@firebase/messaging-interop-types" "0.1.0" - "@firebase/util" "1.6.3" - node-fetch "2.6.7" - tslib "^2.1.0" - -"@firebase/installations-compat@0.1.12": - version "0.1.12" - resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.1.12.tgz#d0394127f71aff596cb8bb607840095d1617246e" - integrity sha512-BIhFpWIn/GkuOa+jnXkp3SDJT2RLYJF6MWpinHIBKFJs7MfrgYZ3zQ1AlhobDEql+bkD1dK4dB5sNcET2T+EyA== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/installations" "0.5.12" - "@firebase/installations-types" "0.4.0" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/installations-types@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.4.0.tgz#256782ff9adfb390ac658c25bc32f89635ddce7c" - integrity sha512-nXxWKQDvBGctuvsizbUEJKfxXU9WAaDhon+j0jpjIfOJkvkj3YHqlLB/HeYjpUn85Pb22BjplpTnDn4Gm9pc3A== + "@emotion/babel-plugin" "^11.11.0" + "@emotion/is-prop-valid" "^1.2.2" + "@emotion/serialize" "^1.1.4" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== -"@firebase/installations@0.5.12": - version "0.5.12" - resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.5.12.tgz#1d5764aa6f0b73d9d6d1a81a07eab5cd71a5ea27" - integrity sha512-Zq43fCE0PB5tGJ3ojzx5RNQzKdej1188qgAk22rwjuhP7npaG/PlJqDG1/V0ZjTLRePZ1xGrfXSPlA17c/vtNw== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/util" "1.6.3" - idb "7.0.1" - tslib "^2.1.0" +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== -"@firebase/logger@0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.3.3.tgz#0f724b1e0b166d17ac285aac5c8ec14d136beed4" - integrity sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q== - dependencies: - tslib "^2.1.0" +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== -"@firebase/messaging-compat@0.1.16": - version "0.1.16" - resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.1.16.tgz#4fe4e2c1b496e62f63e815cb242a2ab323cd7899" - integrity sha512-uG7rWcXJzU8vvlEBFpwG1ndw/GURrrmKcwsHopEWbsPGjMRaVWa7XrdKbvIR7IZohqPzcC/V9L8EeqF4Q4lz8w== +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== dependencies: - "@firebase/component" "0.5.17" - "@firebase/messaging" "0.9.16" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/messaging-interop-types@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.1.0.tgz#bdac02dd31edd5cb9eec37b1db698ea5e2c1a631" - integrity sha512-DbvUl/rXAZpQeKBnwz0NYY5OCqr2nFA0Bj28Fmr3NXGqR4PAkfTOHuQlVtLO1Nudo3q0HxAYLa68ZDAcuv2uKQ== - -"@firebase/messaging@0.9.16": - version "0.9.16" - resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.9.16.tgz#96b57ebbb054e57f78585f85f59d521c5ba5cd85" - integrity sha512-Yl9gGrAvJF6C1gg3+Cr2HxlL6APsDEkrorkFafmSP1l+rg1epZKoOAcKJbSF02Vtb50wfb9FqGGy8tzodgETxg== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/installations" "0.5.12" - "@firebase/messaging-interop-types" "0.1.0" - "@firebase/util" "1.6.3" - idb "7.0.1" - tslib "^2.1.0" - -"@firebase/performance-compat@0.1.12": - version "0.1.12" - resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.1.12.tgz#ac50b0cd29bf7f5e1e33c640dba25e2f8db95f0b" - integrity sha512-IBORzUeGY1MGdZnsix9Mu5z4+C3WHIwalu0usxvygL0EZKHztGG8bppYPGH/b5vvg8QyHs9U+Pn1Ot2jZhffQQ== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/performance" "0.5.12" - "@firebase/performance-types" "0.1.0" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/performance-types@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.1.0.tgz#5e6efa9dc81860aee2cb7121b39ae8fa137e69fc" - integrity sha512-6p1HxrH0mpx+622Ql6fcxFxfkYSBpE3LSuwM7iTtYU2nw91Hj6THC8Bc8z4nboIq7WvgsT/kOTYVVZzCSlXl8w== + "@floating-ui/utils" "^0.2.1" -"@firebase/performance@0.5.12": - version "0.5.12" - resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.5.12.tgz#4eae3eb91eeffb29b996e7908172052d4a901856" - integrity sha512-MPVTkOkGrm2SMQgI1FPNBm85y2pPqlPb6VDjIMCWkVpAr6G1IZzUT24yEMySRcIlK/Hh7/Qu1Nu5ASRzRuX6+Q== +"@floating-ui/dom@^1.6.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== dependencies: - "@firebase/component" "0.5.17" - "@firebase/installations" "0.5.12" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - tslib "^2.1.0" + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" -"@firebase/polyfill@0.3.36": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@firebase/polyfill/-/polyfill-0.3.36.tgz#c057cce6748170f36966b555749472b25efdb145" - integrity sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg== - dependencies: - core-js "3.6.5" - promise-polyfill "8.1.3" - whatwg-fetch "2.0.4" - -"@firebase/remote-config-compat@0.1.12": - version "0.1.12" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.1.12.tgz#7606752d7bfe2701d58568345ca536beda14ee53" - integrity sha512-Yz7Gtb2rLa7ykXZX9DnSTId8CXd++jFFLW3foUImrYwJEtWgLJc7gwkRfd1M73IlKGNuQAY+DpUNF0n1dLbecA== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/logger" "0.3.3" - "@firebase/remote-config" "0.3.11" - "@firebase/remote-config-types" "0.2.0" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/remote-config-types@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.2.0.tgz#1e2759fc01f20b58c564db42196f075844c3d1fd" - integrity sha512-hqK5sCPeZvcHQ1D6VjJZdW6EexLTXNMJfPdTwbD8NrXUw6UjWC4KWhLK/TSlL0QPsQtcKRkaaoP+9QCgKfMFPw== - -"@firebase/remote-config@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.3.11.tgz#93c82b5944a20c027f4ee82c145813ca96b430bb" - integrity sha512-qA84dstrvVpO7rWT/sb2CLv1kjHVmz59SRFPKohJJYFBcPOGK4Pe4FWWhKAE9yg1Gnl0qYAGkahOwNawq3vE0g== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/installations" "0.5.12" - "@firebase/logger" "0.3.3" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/storage-compat@0.1.17": - version "0.1.17" - resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.1.17.tgz#da721071e006d066fb9b1cff69481bd59a02346b" - integrity sha512-nOYmnpI0gwoz5nROseMi9WbmHGf+xumfsOvdPyMZAjy0VqbDnpKIwmTUZQBdR+bLuB5oIkHQsvw9nbb1SH+PzQ== - dependencies: - "@firebase/component" "0.5.17" - "@firebase/storage" "0.9.9" - "@firebase/storage-types" "0.6.0" - "@firebase/util" "1.6.3" - tslib "^2.1.0" - -"@firebase/storage-types@0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.6.0.tgz#0b1af64a2965af46fca138e5b70700e9b7e6312a" - integrity sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA== - -"@firebase/storage@0.9.9": - version "0.9.9" - resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.9.9.tgz#3d0080dd130bc3315731483384a7ef7c00f76e22" - integrity sha512-Zch7srLT2SIh9y2nCVv/4Kne0HULn7OPkmreY70BJTUJ+g5WLRjggBq6x9fV5ls9V38iqMWfn4prxzX8yIc08A== +"@floating-ui/react-dom@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== dependencies: - "@firebase/component" "0.5.17" - "@firebase/util" "1.6.3" - node-fetch "2.6.7" - tslib "^2.1.0" + "@floating-ui/dom" "^1.6.1" -"@firebase/util@1.6.3": - version "1.6.3" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.6.3.tgz#76128c1b5684c031823e95f6c08a7fb8560655c6" - integrity sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg== - dependencies: - tslib "^2.1.0" - -"@firebase/webchannel-wrapper@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.6.2.tgz#6d05fa126104c9907573364dc04147b89b530e15" - integrity sha512-zThUKcqIU6utWzM93uEvhlh8qj8A5LMPFJPvk/ODb+8GSSif19xM2Lw1M2ijyBy8+6skSkQBbavPzOU5Oh/8tQ== - -"@grpc/grpc-js@^1.3.2": - version "1.6.8" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.8.tgz#77cc8b2d841c34dea8b105d45ff1732caefae4f2" - integrity sha512-Nt5tufF/O5Q310kP0cDzxznWMZW58GCTZhUUiAQ9B0K0ANKNQ4Lj/K9XK0vZg+UBKq5/7z7+8mXHHfrcwoeFJQ== - dependencies: - "@grpc/proto-loader" "^0.7.0" - "@types/node" ">=12.12.47" - -"@grpc/proto-loader@^0.6.13": - version "0.6.13" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc" - integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g== - dependencies: - "@types/long" "^4.0.1" - lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^6.11.3" - yargs "^16.2.0" - -"@grpc/proto-loader@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.0.tgz#743cc8a941cc251620c66ebe0d330e1411a33535" - integrity sha512-SGPZtVmqOvNfPFOA/nNPn+0Weqa5wubBgQ56+JgTbeLY2VezwtMjwPPFzh0kvQccwWT3a2TXT0ZGK/pJoOTk1A== - dependencies: - "@types/long" "^4.0.1" - lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^7.0.0" - yargs "^16.2.0" +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" @@ -655,191 +232,124 @@ "@jridgewell/sourcemap-codec" "^1.4.14" "@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - -"@mui/base@5.0.0-alpha.92": - version "5.0.0-alpha.92" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.92.tgz#5c2ca31801fe21a8fec9bfda2cf5f44b1e3c7284" - integrity sha512-ZgnSLrTXL4iUdLQhjp01dAOTQPQlnwrqjZRwDT3E6LZXEYn6cMv1MY6LZkWcF/zxrUnyasnsyMAgZ5d8AXS7bA== - dependencies: - "@babel/runtime" "^7.17.2" - "@emotion/is-prop-valid" "^1.1.3" - "@mui/types" "^7.1.5" - "@mui/utils" "^5.9.3" - "@popperjs/core" "^2.11.5" - clsx "^1.2.1" + version "2.0.5" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== + +"@mui/base@5.0.0-beta.37": + version "5.0.0-beta.37" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.37.tgz#0e7e0f28402391fcfbb05476d5acc6c4f2d817b1" + integrity sha512-/o3anbb+DeCng8jNsd3704XtmmLDZju1Fo8R2o7ugrVtPQ/QpcqddwKNzKPZwa0J5T8YNW3ZVuHyQgbTnQLisQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.11" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" prop-types "^15.8.1" - react-is "^18.2.0" -"@mui/lab@^5.0.0-alpha.82": - version "5.0.0-alpha.93" - resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.93.tgz#b0a1662ae478e791f083fdf49143766b890671e6" - integrity sha512-PGrI6tGwKGpLEv4sG7Jkhh/gqk5SE3KRCY8SKmC7JIOUpHEdPKIPH7kId/UZUd8+NOkr8YsljXmozpWv9PLsBQ== - dependencies: - "@babel/runtime" "^7.17.2" - "@mui/base" "5.0.0-alpha.92" - "@mui/system" "^5.9.3" - "@mui/utils" "^5.9.3" - clsx "^1.2.1" +"@mui/base@^5.0.0-beta.37": + version "5.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" + integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" prop-types "^15.8.1" - react-is "^18.2.0" -"@mui/material@^5.8.0": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.9.3.tgz#23aa8a6f651f3f139a38e0eea39468ced7f509d0" - integrity sha512-idDJajnfnDr+2pI6h2tzWtWoZJmVHNk6aSjISirMuVOGy0ugWpsCE+KW4++GS7aTCujXm9+cl5bWAyXvGjiPIQ== - dependencies: - "@babel/runtime" "^7.17.2" - "@mui/base" "5.0.0-alpha.92" - "@mui/system" "^5.9.3" - "@mui/types" "^7.1.5" - "@mui/utils" "^5.9.3" - "@types/react-transition-group" "^4.4.5" - clsx "^1.2.1" - csstype "^3.1.0" +"@mui/core-downloads-tracker@^5.15.11": + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz#2bc2bda50db66c12f10aefec907c48c8f669ef59" + integrity sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg== + +"@mui/material@5.15.11": + version "5.15.11" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.11.tgz#4f42ee30443699ffb5836029c6d8464154eca603" + integrity sha512-FA3eEuEZaDaxgN3CgfXezMWbCZ4VCeU/sv0F0/PK5n42qIgsPVD6q+j71qS7/62sp6wRFMHtDMpXRlN+tT/7NA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.37" + "@mui/core-downloads-tracker" "^5.15.11" + "@mui/system" "^5.15.11" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.11" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.0" + csstype "^3.1.3" prop-types "^15.8.1" react-is "^18.2.0" react-transition-group "^4.4.5" -"@mui/private-theming@^5.9.3": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.9.3.tgz#8ea06dbe0522b0cf4ba5ee19b1a4d7f74539ae1c" - integrity sha512-Ys3WO39WqoGciGX9k5AIi/k2zJhlydv4FzlEEwtw9OqdMaV0ydK/TdZekKzjP9sTI/JcdAP3H5DWtUaPLQJjWg== +"@mui/private-theming@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee" + integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw== dependencies: - "@babel/runtime" "^7.17.2" - "@mui/utils" "^5.9.3" + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.15.14" prop-types "^15.8.1" -"@mui/styled-engine@^5.8.7": - version "5.8.7" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.8.7.tgz#63d0779c07677fe76d4705a02c7ae99f89b50780" - integrity sha512-tVqtowjbYmiRq+qcqXK731L9eWoL9H8xTRhuTgaDGKdch1zlt4I2UwInUe1w2N9N/u3/jHsFbLcl1Un3uOwpQg== +"@mui/styled-engine@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" + integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw== dependencies: - "@babel/runtime" "^7.17.2" - "@emotion/cache" "^11.9.3" - csstype "^3.1.0" + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" prop-types "^15.8.1" -"@mui/system@^5.9.3": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.9.3.tgz#5d2d06db2b2454573639de394f81b6be3608a1ce" - integrity sha512-EXQV2POwncstHLYII+G4VSYdEFun1TjBbQSBDK76DbIkug8nPjtjAZ+3Kgk3/NoFIigW+vQ9cDVUZtlbRH6YMQ== - dependencies: - "@babel/runtime" "^7.17.2" - "@mui/private-theming" "^5.9.3" - "@mui/styled-engine" "^5.8.7" - "@mui/types" "^7.1.5" - "@mui/utils" "^5.9.3" - clsx "^1.2.1" - csstype "^3.1.0" +"@mui/system@^5.15.11": + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.15.tgz#658771b200ce3c4a0f28e58169f02e5e718d1c53" + integrity sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.15.14" + "@mui/styled-engine" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + clsx "^2.1.0" + csstype "^3.1.3" prop-types "^15.8.1" -"@mui/types@^7.1.5": - version "7.1.5" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.5.tgz#5e5cc49d719bc86522983359bc1f90eddcff0624" - integrity sha512-HnRXrxgHJYJcT8ZDdDCQIlqk0s0skOKD7eWs9mJgBUu70hyW4iA6Kiv3yspJR474RFH8hysKR65VVSzUSzkuwA== +"@mui/types@^7.2.13", "@mui/types@^7.2.14": + version "7.2.14" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" + integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== -"@mui/utils@^5.4.1", "@mui/utils@^5.9.3": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.9.3.tgz#a11e0824f00b7ea40257b390060ce167fe861d02" - integrity sha512-l0N5bcrenE9hnwZ/jPecpIRqsDFHkPXoFUcmkgysaJwVZzJ3yQkGXB47eqmXX5yyGrSc6HksbbqXEaUya+siew== +"@mui/utils@^5.15.11", "@mui/utils@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a" + integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA== dependencies: - "@babel/runtime" "^7.17.2" - "@types/prop-types" "^15.7.5" - "@types/react-is" "^16.7.1 || ^17.0.0" + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.11" prop-types "^15.8.1" react-is "^18.2.0" -"@mui/x-date-pickers@^5.0.0-alpha.3": - version "5.0.0-beta.4" - resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-5.0.0-beta.4.tgz#85132b8c8ad030823106adc1ce329330275dc7c7" - integrity sha512-MvfZVsTS99Bz4ZxlkgA3PU7beQ1ET5Aejj9T3sPhnnmb2f4YysIZqhReYl5r4IOEuU/enhMKUYwsqgB0GB0MTg== - dependencies: - "@babel/runtime" "^7.18.6" - "@date-io/core" "^2.14.0" - "@date-io/date-fns" "^2.14.0" - "@date-io/dayjs" "^2.14.0" - "@date-io/luxon" "^2.14.0" - "@date-io/moment" "^2.14.0" - "@mui/utils" "^5.4.1" - "@types/react-transition-group" "^4.4.5" - clsx "^1.2.1" - prop-types "^15.7.2" - react-transition-group "^4.4.2" - rifm "^0.12.1" - -"@popperjs/core@^2.11.5": - version "2.11.5" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64" - integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== "@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== dependencies: "@types/connect" "*" "@types/node" "*" "@types/bonjour@^3.5.9": - version "3.5.10" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" - integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== dependencies: "@types/node" "*" @@ -849,17 +359,17 @@ integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== "@types/connect-history-api-fallback@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" - integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== dependencies: "@types/express-serve-static-core" "*" "@types/node" "*" "@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== dependencies: "@types/node" "*" @@ -899,46 +409,52 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": - version "4.17.30" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" - integrity sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ== +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz#3ae8ab3767d98d0b682cda063c3339e1e86ccfaa" + integrity sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" + "@types/send" "*" "@types/express@*", "@types/express@^4.17.13": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" - integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" + "@types/express-serve-static-core" "^4.17.33" "@types/qs" "*" "@types/serve-static" "*" +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + "@types/http-proxy@^1.17.8": - version "1.17.9" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" - integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== + version "1.17.14" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" + integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== dependencies: "@types/node" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/long@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" - integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== +"@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/node-forge@^1.3.0": version "1.3.11" @@ -952,52 +468,39 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b" integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw== -"@types/node@>=12.12.47", "@types/node@>=13.7.0": - version "18.6.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.5.tgz#06caea822caf9e59d5034b695186ee74154d2802" - integrity sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw== - "@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== -"@types/prop-types@*", "@types/prop-types@^15.7.5": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/prop-types@*", "@types/prop-types@^15.7.11": + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== "@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== "@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/react-is@^16.7.1 || ^17.0.0": - version "17.0.3" - resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a" - integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw== - dependencies: - "@types/react" "*" + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-transition-group@^4.4.5": - version "4.4.5" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" - integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== +"@types/react-transition-group@^4.4.10": + version "4.4.10" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" + integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== dependencies: "@types/react" "*" "@types/react@*": - version "18.0.17" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.17.tgz#4583d9c322d67efe4b39a935d223edcc7050ccf4" - integrity sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ== + version "18.2.79" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.79.tgz#c40efb4f255711f554d47b449f796d1c7756d865" + integrity sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w== dependencies: "@types/prop-types" "*" - "@types/scheduler" "*" csstype "^3.0.2" "@types/retry@0.12.0": @@ -1005,37 +508,41 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" "@types/serve-index@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" - integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== dependencies: "@types/express" "*" "@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== dependencies: - "@types/mime" "*" + "@types/http-errors" "*" "@types/node" "*" + "@types/send" "*" "@types/sockjs@^0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" - integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== dependencies: "@types/node" "*" "@types/ws@^8.5.1": - version "8.5.3" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" - integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + version "8.5.10" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== dependencies: "@types/node" "*" @@ -1220,7 +727,7 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv-keywords@^5.0.0: +ajv-keywords@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== @@ -1237,10 +744,10 @@ ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.8.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" - integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== +ajv@^8.0.0, ajv@^8.9.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -1294,11 +801,6 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" @@ -1328,7 +830,25 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -body-parser@1.20.0, body-parser@^1.19.0: +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +body-parser@^1.19.0: version "1.20.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== @@ -1347,12 +867,10 @@ body-parser@1.20.0, body-parser@^1.19.0: unpipe "1.0.0" bonjour-service@^1.0.11: - version "1.0.13" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.13.tgz#4ac003dc1626023252d58adf2946f57e5da450c1" - integrity sha512-LWKRU/7EqDUC9CTAQtuZl5HzBALoCYwtLhffW3et7vZMwv3bWLpJf8bRYlMD5OCcDpTfnPgNCV4yo9ZIaJGMiA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" + integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" fast-deep-equal "^3.1.3" multicast-dns "^7.2.5" @@ -1432,7 +950,7 @@ caniuse-lite@^1.0.30001349: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz#cc6f5da3f983979ad1e2cdbae0505dccaa7c6a12" integrity sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA== -chalk@^2.0.0: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1449,7 +967,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.3, chokidar@^3.5.1, chokidar@^3.5.3: +chokidar@3.5.3, chokidar@^3.5.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -1464,6 +982,21 @@ chokidar@3.5.3, chokidar@^3.5.1, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -1487,10 +1020,10 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clsx@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== color-convert@^1.9.0: version "1.9.3" @@ -1517,9 +1050,9 @@ color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colorette@^2.0.10: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== colorette@^2.0.14: version "2.0.17" @@ -1593,33 +1126,31 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + convert-source-map@^1.5.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== cookie@~0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== -core-js@3.6.5: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" - integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== - core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -1634,9 +1165,9 @@ cors@~2.8.5: vary "^1" cosmiconfig@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" @@ -1653,10 +1184,10 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -csstype@^3.0.2, csstype@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" - integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== +csstype@^3.0.2, csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== custom-event@~1.0.0: version "1.0.1" @@ -1729,15 +1260,10 @@ diff@5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - dns-packet@^5.2.2: - version "5.4.0" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" - integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== dependencies: "@leichtgewicht/ip-codec" "^2.0.1" @@ -1759,14 +1285,6 @@ dom-serialize@^2.2.1: extend "^3.0.0" void-elements "^2.0.0" -dukat@0.5.8-rc.4: - version "0.5.8-rc.4" - resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21" - integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA== - dependencies: - google-protobuf "3.12.2" - typescript "3.9.5" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -1914,16 +1432,16 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.3: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.0" + body-parser "1.20.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.5.0" + cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -1939,7 +1457,7 @@ express@^4.17.3: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.10.3" + qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.18.0" @@ -1970,7 +1488,7 @@ fastest-levenshtein@^1.0.12: resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== -faye-websocket@0.11.4, faye-websocket@^0.11.3: +faye-websocket@^0.11.3: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== @@ -2031,39 +1549,6 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -firebase@9.9.1: - version "9.9.1" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-9.9.1.tgz#3a4539ab64176196b981d296f7a0b819bb97530b" - integrity sha512-1saLd91mmfNWOdP3DpkCAyXcrwB0iJXZoYY8S98ljp1erL+eUPHu+AHw8ImjynVIejSX07NHgla3mQP0bIniZA== - dependencies: - "@firebase/analytics" "0.8.0" - "@firebase/analytics-compat" "0.1.13" - "@firebase/app" "0.7.29" - "@firebase/app-check" "0.5.12" - "@firebase/app-check-compat" "0.2.12" - "@firebase/app-compat" "0.1.30" - "@firebase/app-types" "0.7.0" - "@firebase/auth" "0.20.5" - "@firebase/auth-compat" "0.2.18" - "@firebase/database" "0.13.3" - "@firebase/database-compat" "0.2.3" - "@firebase/firestore" "3.4.13" - "@firebase/firestore-compat" "0.1.22" - "@firebase/functions" "0.8.4" - "@firebase/functions-compat" "0.2.4" - "@firebase/installations" "0.5.12" - "@firebase/installations-compat" "0.1.12" - "@firebase/messaging" "0.9.16" - "@firebase/messaging-compat" "0.1.16" - "@firebase/performance" "0.5.12" - "@firebase/performance-compat" "0.1.12" - "@firebase/polyfill" "0.3.36" - "@firebase/remote-config" "0.3.11" - "@firebase/remote-config-compat" "0.1.12" - "@firebase/storage" "0.9.9" - "@firebase/storage-compat" "0.1.17" - "@firebase/util" "1.6.3" - flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -2103,10 +1588,10 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-monkey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== +fs-monkey@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" + integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== fs.realpath@^1.0.0: version "1.0.0" @@ -2183,11 +1668,6 @@ glob@^7.1.3, glob@^7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" -google-protobuf@3.12.2: - version "3.12.2" - resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.2.tgz#50ce9f9b6281235724eb243d6a83e969a2176e53" - integrity sha512-4CZhpuRr1d6HjlyrxoXoocoGFnRYgKULgMtikMddA9ztRyYR59Aondv2FioyxWVamRo0rF2XpYawkTCBEQOSkA== - graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -2255,9 +1735,9 @@ hpack.js@^2.1.6: wbuf "^1.1.0" html-entities@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" - integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== + version "2.5.2" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" + integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== http-deceiver@^1.2.7: version "1.2.7" @@ -2329,16 +1809,6 @@ iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -idb@7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/idb/-/idb-7.0.1.tgz#d2875b3a2f205d854ee307f6d196f246fea590a7" - integrity sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg== - -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== - import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -2384,9 +1854,9 @@ ipaddr.js@1.9.1: integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== + version "2.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== is-arrayish@^0.2.1: version "0.2.1" @@ -2407,13 +1877,6 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.0" -is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== - dependencies: - has "^1.0.3" - is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -2538,16 +2001,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jszip@^3.6.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" - integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - setimmediate "^1.0.5" - karma-chrome-launcher@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" @@ -2621,13 +2074,6 @@ launch-editor@^2.6.0: picocolors "^1.0.0" shell-quote "^1.8.1" -lie@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -2652,11 +2098,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== - lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -2681,16 +2122,6 @@ log4js@^6.4.1: rfdc "^1.3.0" streamroller "^3.1.2" -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -long@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" - integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== - loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -2704,11 +2135,11 @@ media-typer@0.3.0: integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memfs@^3.4.3: - version "3.4.7" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.7.tgz#e5252ad2242a724f938cb937e3c4f7ceb1f70e5a" - integrity sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw== + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: - fs-monkey "^1.0.3" + fs-monkey "^1.0.4" merge-descriptors@1.0.1: version "1.0.1" @@ -2856,13 +2287,6 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - node-forge@^1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -2934,9 +2358,9 @@ onetime@^5.1.2: mimic-fn "^2.1.0" open@^8.0.9: - version "8.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== dependencies: define-lazy-prop "^2.0.0" is-docker "^2.1.1" @@ -2983,11 +2407,6 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@~1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -3062,12 +2481,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -promise-polyfill@8.1.3: - version "8.1.3" - resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" - integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== - -prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -3076,44 +2490,6 @@ prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -protobufjs@^6.11.3: - version "6.11.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" - integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" ">=13.7.0" - long "^4.0.0" - -protobufjs@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.0.0.tgz#8c678e1351fd926178fce5a4213913e8d990974f" - integrity sha512-ffNIEm+quOcYtQvHdW406v1NQmZSuqVklxsXk076BtuFnlYZfigLU+JOMrTD8TUOyqHYbRI/fSVNvgd25YeN3w== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" ">=13.7.0" - long "^5.0.0" - proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -3139,6 +2515,13 @@ qs@6.10.3: dependencies: side-channel "^1.0.4" +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -3161,7 +2544,17 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -react-dom@^18.1.0: +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -3179,7 +2572,7 @@ react-is@^18.2.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-transition-group@^4.4.2, react-transition-group@^4.4.5: +react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== @@ -3189,17 +2582,17 @@ react-transition-group@^4.4.2, react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^18.1.0: +react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" -readable-stream@^2.0.1, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -3210,9 +2603,9 @@ readable-stream@^2.0.1, readable-stream@~2.3.6: util-deprecate "~1.0.1" readable-stream@^3.0.6: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -3232,10 +2625,10 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== require-directory@^2.1.1: version "2.1.1" @@ -3269,16 +2662,7 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.19.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.20.0: +resolve@^1.19.0, resolve@^1.20.0: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -3297,11 +2681,6 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rifm@^0.12.1: - version "0.12.1" - resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.12.1.tgz#8fa77f45b7f1cda2a0068787ac821f0593967ac4" - integrity sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg== - rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -3350,14 +2729,14 @@ schema-utils@^3.1.2: ajv-keywords "^3.5.2" schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== dependencies: "@types/json-schema" "^7.0.9" - ajv "^8.8.0" + ajv "^8.9.0" ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" + ajv-keywords "^5.1.0" sdp@^3.0.2: version "3.2.0" @@ -3369,15 +2748,6 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -selenium-webdriver@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.1.2.tgz#d463b4335632d2ea41a9e988e435a55dc41f5314" - integrity sha512-e4Ap8vQvhipgBB8Ry9zBiKGkU6kHKyNnWiavGGLKkrdW81Zv7NVMtFOL/j3yX0G8QScM7XIXijKssNd4EUxSOw== - dependencies: - jszip "^3.6.0" - tmp "^0.2.1" - ws ">=7.4.6" - selfsigned@^2.1.1: version "2.4.1" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" @@ -3442,11 +2812,6 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" @@ -3644,10 +3009,10 @@ strip-json-comments@3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -stylis@4.0.13: - version "4.0.13" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" - integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== supports-color@8.1.1, supports-color@^8.0.0: version "8.1.1" @@ -3730,16 +3095,6 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tslib@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -3748,11 +3103,6 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@3.9.5: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== - typescript@5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" @@ -3820,11 +3170,6 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - webpack-cli@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.0.tgz#abc4b1f44b50250f2632d8b8b536cfe2f6257891" @@ -3845,9 +3190,9 @@ webpack-cli@5.1.0: webpack-merge "^5.7.3" webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== dependencies: colorette "^2.0.10" memfs "^3.4.3" @@ -3962,19 +3307,6 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== -whatwg-fetch@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -4013,11 +3345,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@>=7.4.6: - version "8.8.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0" - integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA== - ws@^8.13.0: version "8.16.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" @@ -4058,7 +3385,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0, yargs@^16.1.1, yargs@^16.2.0: +yargs@16.2.0, yargs@^16.1.1: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== diff --git a/libs.versions.toml b/libs.versions.toml deleted file mode 100644 index 805afb49..00000000 --- a/libs.versions.toml +++ /dev/null @@ -1,39 +0,0 @@ -[versions] - -kotlinCoroutines = "1.6.4" -kotlinWrappers = "1.0.0-pre.343" -androidxCompose = "1.4.3" -decompose = "1.0.0-alpha-01" - -[libraries] -webrtcSdk = "io.github.webrtc-sdk:android:114.5735.02" - -kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutines" } -kotlin-coroutinesPlayServices = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinCoroutines" } -kotlin-wrappers-bom = { module = "org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom", version.ref = "kotlinWrappers" } -kotlin-wrappers-emotion = { module = "org.jetbrains.kotlin-wrappers:kotlin-emotion", version.ref = "kotlinWrappers" } -kotlin-wrappers-react = { module = "org.jetbrains.kotlin-wrappers:kotlin-react", version.ref = "kotlinWrappers" } -kotlin-wrappers-reactDom = { module = "org.jetbrains.kotlin-wrappers:kotlin-react-dom", version.ref = "kotlinWrappers" } -kotlin-wrappers-mui = { module = "org.jetbrains.kotlin-wrappers:kotlin-mui", version.ref = "kotlinWrappers" } -kotlin-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3" - -androidx-coreKtx = "androidx.core:core-ktx:1.10.1" -androidx-appcompat = "androidx.appcompat:appcompat:1.6.1" -androidx-activity-activityKtx = "androidx.activity:activity-ktx:1.7.2" -androidx-compose-activity = "androidx.activity:activity-compose:1.7.2" -androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "androidxCompose" } -androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "androidxCompose" } -androidx-lifecycle-runtime = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1" -androidx-test-core = "androidx.test:core:1.5.0" -androidx-test-runner = "androidx.test:runner:1.5.2" -androidx-test-rules = "androidx.test:rules:1.5.0" - -accompanist-permissions = "com.google.accompanist:accompanist-permissions:0.25.0" - -decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" } -decompose-compose = { module = "com.arkivanov.decompose:extensions-compose-jetpack", version.ref = "decompose" } - -firebase-bom = "com.google.firebase:firebase-bom:32.2.0" -firebase-firestore = { module = "com.google.firebase:firebase-firestore-ktx" } - -kermit = "co.touchlab:kermit:1.1.2" diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100644 index 00000000..b0505b38 --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +.idea +local.properties +.DS_Store +build/ +captures +xcuserdata +.externalNativeBuild +.cxx +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings diff --git a/sample/README.md b/sample/README.md index 4517766c..d3cac238 100644 --- a/sample/README.md +++ b/sample/README.md @@ -19,10 +19,10 @@ Run Android emulator or connect real device. ### iOS -Open `sample/app-ios/app-ios.xcworkspace` in XCode build and run +Open `sample/iosApp/iosApp.xcodeproj` in XCode build and run ### Web ```bash -./gradlew browserRun +./gradlew jsBrowserRun ``` diff --git a/sample/app-android/build.gradle.kts b/sample/app-android/build.gradle.kts deleted file mode 100644 index 1b512f67..00000000 --- a/sample/app-android/build.gradle.kts +++ /dev/null @@ -1,59 +0,0 @@ -plugins { - id("com.android.application") - id("kotlin-android") - id("com.google.gms.google-services") - id("org.jlleitschuh.gradle.ktlint") -} - -android { - namespace = "com.shepeliev.webrtckmp.sample" - - compileSdk = AndroidConfig.compileSdkVersion - - defaultConfig { - minSdk = AndroidConfig.minSdkVersion - targetSdk = AndroidConfig.targetSdkVersion - versionCode = 1 - versionName = "1.0.0" - applicationId = "com.shepeliev.webrtckmp.sample" - } - - buildFeatures { - compose = true - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } - - composeOptions { - kotlinCompilerExtensionVersion = "1.4.8" - } -} - -tasks.withType() { - kotlinOptions { - freeCompilerArgs += listOf( - "-P", - "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true" - ) - } -} - -dependencies { - implementation(project(":sample:shared")) - implementation(deps.androidx.coreKtx) - implementation(deps.androidx.appcompat) - implementation(deps.androidx.activity.activityKtx) - implementation(deps.androidx.compose.activity) - implementation(deps.androidx.compose.material) - implementation(deps.androidx.compose.animation) - implementation(deps.androidx.lifecycle.runtime) - implementation(deps.decompose.compose) - implementation(deps.accompanist.permissions) -} diff --git a/sample/app-android/google-services.json b/sample/app-android/google-services.json deleted file mode 100644 index b7563842..00000000 --- a/sample/app-android/google-services.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "project_info": { - "project_number": "216132728347", - "project_id": "app-rtc-kmp", - "storage_bucket": "app-rtc-kmp.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:216132728347:android:811fd8165c6b3b9f872abe", - "android_client_info": { - "package_name": "com.shepeliev.webrtckmp.sample" - } - }, - "oauth_client": [ - { - "client_id": "216132728347-mv1pltdn889q4vqnmuqs3tn02lcmqbbl.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyCWRlpT3OPlT2C8H5rk0S-Xg7rsIRmSxdU" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "216132728347-mv1pltdn889q4vqnmuqs3tn02lcmqbbl.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/App.kt b/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/App.kt deleted file mode 100644 index 0e6cd522..00000000 --- a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/App.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.shepeliev.webrtckmp.sample - -import androidx.compose.animation.Crossfade -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState -import com.shepeliev.webrtckmp.sample.shared.Room - -@Composable -fun App(room: Room) { - Scaffold( - topBar = { - TopAppBar( - title = { Text("WebRTC KMP") } - ) - } - ) { - - val roomModel by room.model.subscribeAsState() - - Crossfade(targetState = roomModel) { model -> - when (model.localStream) { - null -> OpenMicrophoneAndCameraScreen(room) - else -> VideoScreen(room) - } - } - } -} diff --git a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/JoinRoomButton.kt b/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/JoinRoomButton.kt deleted file mode 100644 index 39291502..00000000 --- a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/JoinRoomButton.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.shepeliev.webrtckmp.sample - -import androidx.compose.material.AlertDialog -import androidx.compose.material.Button -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.material.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue - -@Composable -fun JoinRoomButton(onJoin: (String) -> Unit, enabled: Boolean) { - var isJoinDialogVisible by remember { mutableStateOf(false) } - - if (isJoinDialogVisible) { - var roomId by remember { mutableStateOf("") } - - AlertDialog( - onDismissRequest = { isJoinDialogVisible = false }, - - confirmButton = { - TextButton( - onClick = { - onJoin(roomId) - isJoinDialogVisible = false - }, - enabled = roomId.isNotBlank() - ) { - Text("Join") - } - }, - - dismissButton = { - TextButton(onClick = { isJoinDialogVisible = false }) { - Text("Cancel") - } - }, - - title = { Text("Join into room") }, - - text = { - OutlinedTextField( - value = roomId, - onValueChange = { roomId = it }, - placeholder = { Text("Room ID") } - ) - } - ) - } - - Button(onClick = { isJoinDialogVisible = true }, enabled = enabled) { - Text("Join") -} -} diff --git a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/MainActivity.kt b/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/MainActivity.kt deleted file mode 100644 index d3ce5f24..00000000 --- a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/MainActivity.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shepeliev.webrtckmp.sample - -import android.os.Bundle -import androidx.activity.compose.setContent -import androidx.appcompat.app.AppCompatActivity -import com.arkivanov.decompose.defaultComponentContext -import com.shepeliev.webrtckmp.sample.shared.RoomComponent - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val room = RoomComponent(componentContext = defaultComponentContext()) - setContent { App(room) } - } -} diff --git a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/OpenMicrophoneAndCameraScreen.kt b/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/OpenMicrophoneAndCameraScreen.kt deleted file mode 100644 index ca29faab..00000000 --- a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/OpenMicrophoneAndCameraScreen.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.shepeliev.webrtckmp.sample - -import android.Manifest -import android.preference.PreferenceManager -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.AlertDialog -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.material.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.core.content.edit -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import com.google.accompanist.permissions.rememberMultiplePermissionsState -import com.shepeliev.webrtckmp.sample.shared.Room - -@Composable -fun OpenMicrophoneAndCameraScreen(room: Room) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - OpenCameraAndMicrophoneButton(onClick = room::openUserMedia) - } -} - -@OptIn(ExperimentalPermissionsApi::class) -@Composable -private fun OpenCameraAndMicrophoneButton(onClick: () -> Unit) { - val permissions = rememberMultiplePermissionsState( - listOf( - Manifest.permission.CAMERA, - Manifest.permission.RECORD_AUDIO, - ) - ) - - var isRationaleVisible by remember { mutableStateOf(false) } - - val context = LocalContext.current - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - - if (isRationaleVisible) { - AlertDialog( - text = { Text("Please grant camera and microphone permissions") }, - onDismissRequest = { isRationaleVisible = false }, - confirmButton = { - TextButton(onClick = { - if (!permissions.shouldShowRationale && preferences.getBoolean("should_open_app_settings", false)) { - context.navigateToAppSettings() - } else { - permissions.launchMultiplePermissionRequest() - preferences.edit { putBoolean("should_open_app_settings", true) } - } - isRationaleVisible = false - }) { - Text("Grant permissions") - } - } - ) - } - - Button(onClick = { - when { - permissions.allPermissionsGranted -> { - preferences.edit { putBoolean("should_open_app_settings", false) } - onClick() - } - - else -> isRationaleVisible = true - } - }) { - Text("Open camera and microphone") - } -} diff --git a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/VideoScreen.kt b/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/VideoScreen.kt deleted file mode 100644 index cde2256e..00000000 --- a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/VideoScreen.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.shepeliev.webrtckmp.sample - -import android.widget.Toast -import androidx.compose.animation.Crossfade -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.material.Button -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.unit.dp -import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState -import com.shepeliev.webrtckmp.sample.shared.Room -import com.shepeliev.webrtckmp.videoTracks -import org.webrtc.RendererCommon - -@Composable -fun VideoScreen(room: Room) { - val roomModel by room.model.subscribeAsState() - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { - Column(modifier = Modifier.fillMaxSize()) { - val remoteStream = roomModel.remoteStream - - val animatedWeight by animateFloatAsState( - targetValue = remoteStream?.let { 1f } ?: 0.01f - ) - - remoteStream?.let { - Video( - track = it.videoTracks.first(), - modifier = Modifier.weight(animatedWeight), - scalingTypeMatchOrientation = RendererCommon.ScalingType.SCALE_ASPECT_FILL, - scalingTypeMismatchOrientation = RendererCommon.ScalingType.SCALE_ASPECT_FILL, - ) - } - - roomModel.localStream?.let { - Video( - track = it.videoTracks.first(), - modifier = Modifier.weight(1f), - scalingTypeMatchOrientation = RendererCommon.ScalingType.SCALE_ASPECT_FILL, - scalingTypeMismatchOrientation = RendererCommon.ScalingType.SCALE_ASPECT_FILL, - ) - } - } - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - - Crossfade(targetState = roomModel) { - when { - it.isJoining -> CircularProgressIndicator() - - it.roomId != null -> { - val roomId = roomModel.roomId - val context = LocalContext.current - val clipboardManager = LocalClipboardManager.current - - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Text(text = "Room ID: $roomId", color = Color.White) - - IconButton(onClick = { - val text = buildAnnotatedString { append(roomId!!) } - clipboardManager.setText(text) - Toast.makeText(context, "Room ID is copied.", Toast.LENGTH_SHORT) - .show() - }) { - Icon( - painter = painterResource(id = R.drawable.ic_content_copy), - contentDescription = "Copy room ID", - modifier = Modifier.size(24.dp), - tint = Color.White, - ) - } - } - } - } - } - - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Button(onClick = room::switchCamera) { - Text("Switch camera") - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - if (roomModel.roomId == null) { - Button(onClick = room::createRoom, enabled = !roomModel.isJoining) { - Text("Create") - } - - JoinRoomButton(onJoin = room::joinRoom, enabled = !roomModel.isJoining) - } - - Button(onClick = room::hangup) { - Text("Hangup") - } - } - } - } - } -} diff --git a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/navigateToAppSettings.kt b/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/navigateToAppSettings.kt deleted file mode 100644 index d11867f8..00000000 --- a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/navigateToAppSettings.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.shepeliev.webrtckmp.sample - -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.provider.Settings - -fun Context.navigateToAppSettings() { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.fromParts("package", packageName, null) - addCategory(Intent.CATEGORY_DEFAULT) - addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_NO_HISTORY or - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - ) - } - startActivity(intent) -} diff --git a/sample/app-android/src/main/res/drawable/ic_content_copy.xml b/sample/app-android/src/main/res/drawable/ic_content_copy.xml deleted file mode 100644 index cda8345f..00000000 --- a/sample/app-android/src/main/res/drawable/ic_content_copy.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/sample/app-android/src/main/res/drawable/ic_launcher.xml b/sample/app-android/src/main/res/drawable/ic_launcher.xml deleted file mode 100644 index d974bafb..00000000 --- a/sample/app-android/src/main/res/drawable/ic_launcher.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/sample/app-android/src/main/res/values/strings.xml b/sample/app-android/src/main/res/values/strings.xml deleted file mode 100644 index 9598352e..00000000 --- a/sample/app-android/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - WebRTC KMP Sample - diff --git a/sample/app-ios/.gitignore b/sample/app-ios/.gitignore deleted file mode 100644 index 7e6b5400..00000000 --- a/sample/app-ios/.gitignore +++ /dev/null @@ -1,221 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/swift,xcode,C,objective-c,osx -# Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode,C,objective-c,osx - -### C ### -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -### Objective-C ### -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## User settings -xcuserdata/ - -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) -*.xcscmblueprint -*.xccheckout - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) -build/ -DerivedData/ -*.moved-aside -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 - -## Obj-C/Swift specific -*.hmap - -## App packaging -*.ipa -*.dSYM.zip -*.dSYM - -# CocoaPods -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# Pods/ -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build/ - -# fastlane -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots/**/*.png -fastlane/test_output - -# Code Injection -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ - -### Objective-C Patch ### - -### OSX ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Swift ### -# Xcode -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - - - - - - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - -# Swift Package Manager -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -# Package.pins -# Package.resolved -# *.xcodeproj -# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata -# hence it is not needed unless you have added a package configuration file to your project -# .swiftpm - -.build/ - -# CocoaPods -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# Pods/ -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - - -# Accio dependency management -Dependencies/ -.accio/ - -# fastlane -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - - -# Code Injection -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - - -### Xcode ### - -## Xcode 8 and earlier - -### Xcode Patch ### -*.xcodeproj/* -!*.xcodeproj/project.pbxproj -!*.xcodeproj/xcshareddata/ -!*.xcworkspace/contents.xcworkspacedata -/*.gcno -**/xcshareddata/WorkspaceSettings.xcsettings - -# End of https://www.toptal.com/developers/gitignore/api/swift,xcode,C,objective-c,osx - -Pods/ diff --git a/sample/app-ios/Podfile b/sample/app-ios/Podfile deleted file mode 100644 index 223b4909..00000000 --- a/sample/app-ios/Podfile +++ /dev/null @@ -1,6 +0,0 @@ -platform :ios, '11.0' - -target 'app-ios' do - use_frameworks! - pod 'shared', :path => '../shared' -end diff --git a/sample/app-ios/Podfile.lock b/sample/app-ios/Podfile.lock deleted file mode 100644 index 21fbfc8b..00000000 --- a/sample/app-ios/Podfile.lock +++ /dev/null @@ -1,774 +0,0 @@ -PODS: - - abseil/algorithm (1.20220623.0): - - abseil/algorithm/algorithm (= 1.20220623.0) - - abseil/algorithm/container (= 1.20220623.0) - - abseil/algorithm/algorithm (1.20220623.0): - - abseil/base/config - - abseil/algorithm/container (1.20220623.0): - - abseil/algorithm/algorithm - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/base (1.20220623.0): - - abseil/base/atomic_hook (= 1.20220623.0) - - abseil/base/base (= 1.20220623.0) - - abseil/base/base_internal (= 1.20220623.0) - - abseil/base/config (= 1.20220623.0) - - abseil/base/core_headers (= 1.20220623.0) - - abseil/base/dynamic_annotations (= 1.20220623.0) - - abseil/base/endian (= 1.20220623.0) - - abseil/base/errno_saver (= 1.20220623.0) - - abseil/base/fast_type_id (= 1.20220623.0) - - abseil/base/log_severity (= 1.20220623.0) - - abseil/base/malloc_internal (= 1.20220623.0) - - abseil/base/prefetch (= 1.20220623.0) - - abseil/base/pretty_function (= 1.20220623.0) - - abseil/base/raw_logging_internal (= 1.20220623.0) - - abseil/base/spinlock_wait (= 1.20220623.0) - - abseil/base/strerror (= 1.20220623.0) - - abseil/base/throw_delegate (= 1.20220623.0) - - abseil/base/atomic_hook (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/base (1.20220623.0): - - abseil/base/atomic_hook - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/log_severity - - abseil/base/raw_logging_internal - - abseil/base/spinlock_wait - - abseil/meta/type_traits - - abseil/base/base_internal (1.20220623.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/base/config (1.20220623.0) - - abseil/base/core_headers (1.20220623.0): - - abseil/base/config - - abseil/base/dynamic_annotations (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/errno_saver (1.20220623.0): - - abseil/base/config - - abseil/base/fast_type_id (1.20220623.0): - - abseil/base/config - - abseil/base/log_severity (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/malloc_internal (1.20220623.0): - - abseil/base/base - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/raw_logging_internal - - abseil/base/prefetch (1.20220623.0): - - abseil/base/config - - abseil/base/pretty_function (1.20220623.0) - - abseil/base/raw_logging_internal (1.20220623.0): - - abseil/base/atomic_hook - - abseil/base/config - - abseil/base/core_headers - - abseil/base/errno_saver - - abseil/base/log_severity - - abseil/base/spinlock_wait (1.20220623.0): - - abseil/base/base_internal - - abseil/base/core_headers - - abseil/base/errno_saver - - abseil/base/strerror (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/errno_saver - - abseil/base/throw_delegate (1.20220623.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/cleanup/cleanup (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/cleanup/cleanup_internal - - abseil/cleanup/cleanup_internal (1.20220623.0): - - abseil/base/base_internal - - abseil/base/core_headers - - abseil/utility/utility - - abseil/container/common (1.20220623.0): - - abseil/meta/type_traits - - abseil/types/optional - - abseil/container/compressed_tuple (1.20220623.0): - - abseil/utility/utility - - abseil/container/container_memory (1.20220623.0): - - abseil/base/config - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/utility/utility - - abseil/container/fixed_array (1.20220623.0): - - abseil/algorithm/algorithm - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/throw_delegate - - abseil/container/compressed_tuple - - abseil/memory/memory - - abseil/container/flat_hash_map (1.20220623.0): - - abseil/algorithm/container - - abseil/base/core_headers - - abseil/container/container_memory - - abseil/container/hash_function_defaults - - abseil/container/raw_hash_map - - abseil/memory/memory - - abseil/container/flat_hash_set (1.20220623.0): - - abseil/algorithm/container - - abseil/base/core_headers - - abseil/container/container_memory - - abseil/container/hash_function_defaults - - abseil/container/raw_hash_set - - abseil/memory/memory - - abseil/container/hash_function_defaults (1.20220623.0): - - abseil/base/config - - abseil/hash/hash - - abseil/strings/cord - - abseil/strings/strings - - abseil/container/hash_policy_traits (1.20220623.0): - - abseil/meta/type_traits - - abseil/container/hashtable_debug_hooks (1.20220623.0): - - abseil/base/config - - abseil/container/hashtablez_sampler (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/debugging/stacktrace - - abseil/memory/memory - - abseil/profiling/exponential_biased - - abseil/profiling/sample_recorder - - abseil/synchronization/synchronization - - abseil/utility/utility - - abseil/container/inlined_vector (1.20220623.0): - - abseil/algorithm/algorithm - - abseil/base/core_headers - - abseil/base/throw_delegate - - abseil/container/inlined_vector_internal - - abseil/memory/memory - - abseil/container/inlined_vector_internal (1.20220623.0): - - abseil/base/core_headers - - abseil/container/compressed_tuple - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/types/span - - abseil/container/layout (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/strings/strings - - abseil/types/span - - abseil/utility/utility - - abseil/container/raw_hash_map (1.20220623.0): - - abseil/base/throw_delegate - - abseil/container/container_memory - - abseil/container/raw_hash_set - - abseil/container/raw_hash_set (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/prefetch - - abseil/container/common - - abseil/container/compressed_tuple - - abseil/container/container_memory - - abseil/container/hash_policy_traits - - abseil/container/hashtable_debug_hooks - - abseil/container/hashtablez_sampler - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/utility/utility - - abseil/debugging/debugging_internal (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/errno_saver - - abseil/base/raw_logging_internal - - abseil/debugging/demangle_internal (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/debugging/stacktrace (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/debugging/debugging_internal - - abseil/debugging/symbolize (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/malloc_internal - - abseil/base/raw_logging_internal - - abseil/debugging/debugging_internal - - abseil/debugging/demangle_internal - - abseil/strings/strings - - abseil/functional/any_invocable (1.20220623.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/utility/utility - - abseil/functional/bind_front (1.20220623.0): - - abseil/base/base_internal - - abseil/container/compressed_tuple - - abseil/meta/type_traits - - abseil/utility/utility - - abseil/functional/function_ref (1.20220623.0): - - abseil/base/base_internal - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/hash/city (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/hash/hash (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/container/fixed_array - - abseil/functional/function_ref - - abseil/hash/city - - abseil/hash/low_level_hash - - abseil/meta/type_traits - - abseil/numeric/int128 - - abseil/strings/strings - - abseil/types/optional - - abseil/types/variant - - abseil/utility/utility - - abseil/hash/low_level_hash (1.20220623.0): - - abseil/base/config - - abseil/base/endian - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/memory (1.20220623.0): - - abseil/memory/memory (= 1.20220623.0) - - abseil/memory/memory (1.20220623.0): - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/meta (1.20220623.0): - - abseil/meta/type_traits (= 1.20220623.0) - - abseil/meta/type_traits (1.20220623.0): - - abseil/base/config - - abseil/numeric/bits (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/numeric/int128 (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/numeric/bits - - abseil/numeric/representation (1.20220623.0): - - abseil/base/config - - abseil/profiling/exponential_biased (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/profiling/sample_recorder (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/synchronization/synchronization - - abseil/time/time - - abseil/random/distributions (1.20220623.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/random/internal/distribution_caller - - abseil/random/internal/fast_uniform_bits - - abseil/random/internal/fastmath - - abseil/random/internal/generate_real - - abseil/random/internal/iostream_state_saver - - abseil/random/internal/traits - - abseil/random/internal/uniform_helper - - abseil/random/internal/wide_multiply - - abseil/strings/strings - - abseil/random/internal/distribution_caller (1.20220623.0): - - abseil/base/config - - abseil/base/fast_type_id - - abseil/utility/utility - - abseil/random/internal/fast_uniform_bits (1.20220623.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/random/internal/traits - - abseil/random/internal/fastmath (1.20220623.0): - - abseil/numeric/bits - - abseil/random/internal/generate_real (1.20220623.0): - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/random/internal/fastmath - - abseil/random/internal/traits - - abseil/random/internal/iostream_state_saver (1.20220623.0): - - abseil/meta/type_traits - - abseil/numeric/int128 - - abseil/random/internal/nonsecure_base (1.20220623.0): - - abseil/base/core_headers - - abseil/container/inlined_vector - - abseil/meta/type_traits - - abseil/random/internal/pool_urbg - - abseil/random/internal/salted_seed_seq - - abseil/random/internal/seed_material - - abseil/types/span - - abseil/random/internal/pcg_engine (1.20220623.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/random/internal/fastmath - - abseil/random/internal/iostream_state_saver - - abseil/random/internal/platform (1.20220623.0): - - abseil/base/config - - abseil/random/internal/pool_urbg (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/random/internal/randen - - abseil/random/internal/seed_material - - abseil/random/internal/traits - - abseil/random/seed_gen_exception - - abseil/types/span - - abseil/random/internal/randen (1.20220623.0): - - abseil/base/raw_logging_internal - - abseil/random/internal/platform - - abseil/random/internal/randen_hwaes - - abseil/random/internal/randen_slow - - abseil/random/internal/randen_engine (1.20220623.0): - - abseil/base/endian - - abseil/meta/type_traits - - abseil/random/internal/iostream_state_saver - - abseil/random/internal/randen - - abseil/random/internal/randen_hwaes (1.20220623.0): - - abseil/base/config - - abseil/random/internal/platform - - abseil/random/internal/randen_hwaes_impl - - abseil/random/internal/randen_hwaes_impl (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/numeric/int128 - - abseil/random/internal/platform - - abseil/random/internal/randen_slow (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/numeric/int128 - - abseil/random/internal/platform - - abseil/random/internal/salted_seed_seq (1.20220623.0): - - abseil/container/inlined_vector - - abseil/meta/type_traits - - abseil/random/internal/seed_material - - abseil/types/optional - - abseil/types/span - - abseil/random/internal/seed_material (1.20220623.0): - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/raw_logging_internal - - abseil/random/internal/fast_uniform_bits - - abseil/strings/strings - - abseil/types/optional - - abseil/types/span - - abseil/random/internal/traits (1.20220623.0): - - abseil/base/config - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/random/internal/uniform_helper (1.20220623.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/numeric/int128 - - abseil/random/internal/traits - - abseil/random/internal/wide_multiply (1.20220623.0): - - abseil/base/config - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/random/internal/traits - - abseil/random/random (1.20220623.0): - - abseil/random/distributions - - abseil/random/internal/nonsecure_base - - abseil/random/internal/pcg_engine - - abseil/random/internal/pool_urbg - - abseil/random/internal/randen_engine - - abseil/random/seed_sequences - - abseil/random/seed_gen_exception (1.20220623.0): - - abseil/base/config - - abseil/random/seed_sequences (1.20220623.0): - - abseil/base/config - - abseil/random/internal/pool_urbg - - abseil/random/internal/salted_seed_seq - - abseil/random/internal/seed_material - - abseil/random/seed_gen_exception - - abseil/types/span - - abseil/status/status (1.20220623.0): - - abseil/base/atomic_hook - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/base/strerror - - abseil/container/inlined_vector - - abseil/debugging/stacktrace - - abseil/debugging/symbolize - - abseil/functional/function_ref - - abseil/strings/cord - - abseil/strings/str_format - - abseil/strings/strings - - abseil/types/optional - - abseil/status/statusor (1.20220623.0): - - abseil/base/base - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/meta/type_traits - - abseil/status/status - - abseil/strings/strings - - abseil/types/variant - - abseil/utility/utility - - abseil/strings/cord (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/container/fixed_array - - abseil/container/inlined_vector - - abseil/functional/function_ref - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/strings/cord_internal - - abseil/strings/cordz_functions - - abseil/strings/cordz_info - - abseil/strings/cordz_statistics - - abseil/strings/cordz_update_scope - - abseil/strings/cordz_update_tracker - - abseil/strings/internal - - abseil/strings/str_format - - abseil/strings/strings - - abseil/types/optional - - abseil/types/span - - abseil/strings/cord_internal (1.20220623.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/base/throw_delegate - - abseil/container/compressed_tuple - - abseil/container/inlined_vector - - abseil/container/layout - - abseil/functional/function_ref - - abseil/meta/type_traits - - abseil/strings/strings - - abseil/types/span - - abseil/strings/cordz_functions (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/profiling/exponential_biased - - abseil/strings/cordz_handle (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/synchronization/synchronization - - abseil/strings/cordz_info (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/container/inlined_vector - - abseil/debugging/stacktrace - - abseil/strings/cord_internal - - abseil/strings/cordz_functions - - abseil/strings/cordz_handle - - abseil/strings/cordz_statistics - - abseil/strings/cordz_update_tracker - - abseil/synchronization/synchronization - - abseil/types/span - - abseil/strings/cordz_statistics (1.20220623.0): - - abseil/base/config - - abseil/strings/cordz_update_tracker - - abseil/strings/cordz_update_scope (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/strings/cord_internal - - abseil/strings/cordz_info - - abseil/strings/cordz_update_tracker - - abseil/strings/cordz_update_tracker (1.20220623.0): - - abseil/base/config - - abseil/strings/internal (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/meta/type_traits - - abseil/strings/str_format (1.20220623.0): - - abseil/strings/str_format_internal - - abseil/strings/str_format_internal (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/functional/function_ref - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/numeric/representation - - abseil/strings/strings - - abseil/types/optional - - abseil/types/span - - abseil/utility/utility - - abseil/strings/strings (1.20220623.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/base/throw_delegate - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/strings/internal - - abseil/synchronization/graphcycles_internal (1.20220623.0): - - abseil/base/base - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/malloc_internal - - abseil/base/raw_logging_internal - - abseil/synchronization/kernel_timeout_internal (1.20220623.0): - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/time/time - - abseil/synchronization/synchronization (1.20220623.0): - - abseil/base/atomic_hook - - abseil/base/base - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/malloc_internal - - abseil/base/raw_logging_internal - - abseil/debugging/stacktrace - - abseil/debugging/symbolize - - abseil/synchronization/graphcycles_internal - - abseil/synchronization/kernel_timeout_internal - - abseil/time/time - - abseil/time (1.20220623.0): - - abseil/time/internal (= 1.20220623.0) - - abseil/time/time (= 1.20220623.0) - - abseil/time/internal (1.20220623.0): - - abseil/time/internal/cctz (= 1.20220623.0) - - abseil/time/internal/cctz (1.20220623.0): - - abseil/time/internal/cctz/civil_time (= 1.20220623.0) - - abseil/time/internal/cctz/time_zone (= 1.20220623.0) - - abseil/time/internal/cctz/civil_time (1.20220623.0): - - abseil/base/config - - abseil/time/internal/cctz/time_zone (1.20220623.0): - - abseil/base/config - - abseil/time/internal/cctz/civil_time - - abseil/time/time (1.20220623.0): - - abseil/base/base - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/numeric/int128 - - abseil/strings/strings - - abseil/time/internal/cctz/civil_time - - abseil/time/internal/cctz/time_zone - - abseil/types (1.20220623.0): - - abseil/types/any (= 1.20220623.0) - - abseil/types/bad_any_cast (= 1.20220623.0) - - abseil/types/bad_any_cast_impl (= 1.20220623.0) - - abseil/types/bad_optional_access (= 1.20220623.0) - - abseil/types/bad_variant_access (= 1.20220623.0) - - abseil/types/compare (= 1.20220623.0) - - abseil/types/optional (= 1.20220623.0) - - abseil/types/span (= 1.20220623.0) - - abseil/types/variant (= 1.20220623.0) - - abseil/types/any (1.20220623.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/fast_type_id - - abseil/meta/type_traits - - abseil/types/bad_any_cast - - abseil/utility/utility - - abseil/types/bad_any_cast (1.20220623.0): - - abseil/base/config - - abseil/types/bad_any_cast_impl - - abseil/types/bad_any_cast_impl (1.20220623.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/types/bad_optional_access (1.20220623.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/types/bad_variant_access (1.20220623.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/types/compare (1.20220623.0): - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/types/optional (1.20220623.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/types/bad_optional_access - - abseil/utility/utility - - abseil/types/span (1.20220623.0): - - abseil/algorithm/algorithm - - abseil/base/core_headers - - abseil/base/throw_delegate - - abseil/meta/type_traits - - abseil/types/variant (1.20220623.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/types/bad_variant_access - - abseil/utility/utility - - abseil/utility/utility (1.20220623.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/meta/type_traits - - BoringSSL-GRPC (0.0.24): - - BoringSSL-GRPC/Implementation (= 0.0.24) - - BoringSSL-GRPC/Interface (= 0.0.24) - - BoringSSL-GRPC/Implementation (0.0.24): - - BoringSSL-GRPC/Interface (= 0.0.24) - - BoringSSL-GRPC/Interface (0.0.24) - - FirebaseCore (10.11.0): - - FirebaseCoreInternal (~> 10.0) - - GoogleUtilities/Environment (~> 7.8) - - GoogleUtilities/Logger (~> 7.8) - - FirebaseCoreInternal (10.11.0): - - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseFirestore (10.11.0): - - abseil/algorithm (~> 1.20220623.0) - - abseil/base (~> 1.20220623.0) - - abseil/container/flat_hash_map (~> 1.20220623.0) - - abseil/memory (~> 1.20220623.0) - - abseil/meta (~> 1.20220623.0) - - abseil/strings/strings (~> 1.20220623.0) - - abseil/time (~> 1.20220623.0) - - abseil/types (~> 1.20220623.0) - - FirebaseCore (~> 10.0) - - "gRPC-C++ (~> 1.50.1)" - - leveldb-library (~> 1.22) - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleUtilities/Environment (7.11.1): - - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.11.1): - - GoogleUtilities/Environment - - "GoogleUtilities/NSData+zlib (7.11.1)" - - "gRPC-C++ (1.50.1)": - - "gRPC-C++/Implementation (= 1.50.1)" - - "gRPC-C++/Interface (= 1.50.1)" - - "gRPC-C++/Implementation (1.50.1)": - - abseil/base/base (= 1.20220623.0) - - abseil/base/core_headers (= 1.20220623.0) - - abseil/cleanup/cleanup (= 1.20220623.0) - - abseil/container/flat_hash_map (= 1.20220623.0) - - abseil/container/flat_hash_set (= 1.20220623.0) - - abseil/container/inlined_vector (= 1.20220623.0) - - abseil/functional/any_invocable (= 1.20220623.0) - - abseil/functional/bind_front (= 1.20220623.0) - - abseil/functional/function_ref (= 1.20220623.0) - - abseil/hash/hash (= 1.20220623.0) - - abseil/memory/memory (= 1.20220623.0) - - abseil/meta/type_traits (= 1.20220623.0) - - abseil/random/random (= 1.20220623.0) - - abseil/status/status (= 1.20220623.0) - - abseil/status/statusor (= 1.20220623.0) - - abseil/strings/cord (= 1.20220623.0) - - abseil/strings/str_format (= 1.20220623.0) - - abseil/strings/strings (= 1.20220623.0) - - abseil/synchronization/synchronization (= 1.20220623.0) - - abseil/time/time (= 1.20220623.0) - - abseil/types/optional (= 1.20220623.0) - - abseil/types/span (= 1.20220623.0) - - abseil/types/variant (= 1.20220623.0) - - abseil/utility/utility (= 1.20220623.0) - - "gRPC-C++/Interface (= 1.50.1)" - - gRPC-Core (= 1.50.1) - - "gRPC-C++/Interface (1.50.1)" - - gRPC-Core (1.50.1): - - gRPC-Core/Implementation (= 1.50.1) - - gRPC-Core/Interface (= 1.50.1) - - gRPC-Core/Implementation (1.50.1): - - abseil/base/base (= 1.20220623.0) - - abseil/base/core_headers (= 1.20220623.0) - - abseil/container/flat_hash_map (= 1.20220623.0) - - abseil/container/flat_hash_set (= 1.20220623.0) - - abseil/container/inlined_vector (= 1.20220623.0) - - abseil/functional/any_invocable (= 1.20220623.0) - - abseil/functional/bind_front (= 1.20220623.0) - - abseil/functional/function_ref (= 1.20220623.0) - - abseil/hash/hash (= 1.20220623.0) - - abseil/memory/memory (= 1.20220623.0) - - abseil/meta/type_traits (= 1.20220623.0) - - abseil/random/random (= 1.20220623.0) - - abseil/status/status (= 1.20220623.0) - - abseil/status/statusor (= 1.20220623.0) - - abseil/strings/cord (= 1.20220623.0) - - abseil/strings/str_format (= 1.20220623.0) - - abseil/strings/strings (= 1.20220623.0) - - abseil/synchronization/synchronization (= 1.20220623.0) - - abseil/time/time (= 1.20220623.0) - - abseil/types/optional (= 1.20220623.0) - - abseil/types/span (= 1.20220623.0) - - abseil/types/variant (= 1.20220623.0) - - abseil/utility/utility (= 1.20220623.0) - - BoringSSL-GRPC (= 0.0.24) - - gRPC-Core/Interface (= 1.50.1) - - gRPC-Core/Interface (1.50.1) - - leveldb-library (1.22.2) - - nanopb (2.30909.0): - - nanopb/decode (= 2.30909.0) - - nanopb/encode (= 2.30909.0) - - nanopb/decode (2.30909.0) - - nanopb/encode (2.30909.0) - - PromisesObjC (2.2.0) - - shared (1.0.0): - - FirebaseCore - - FirebaseFirestore - - WebRTC-SDK (= 114.5735.02) - - WebRTC-SDK (114.5735.02) - -DEPENDENCIES: - - shared (from `../shared`) - -SPEC REPOS: - trunk: - - abseil - - BoringSSL-GRPC - - FirebaseCore - - FirebaseCoreInternal - - FirebaseFirestore - - GoogleUtilities - - "gRPC-C++" - - gRPC-Core - - leveldb-library - - nanopb - - PromisesObjC - - WebRTC-SDK - -EXTERNAL SOURCES: - shared: - :path: "../shared" - -SPEC CHECKSUMS: - abseil: 926fb7a82dc6d2b8e1f2ed7f3a718bce691d1e46 - BoringSSL-GRPC: 3175b25143e648463a56daeaaa499c6cb86dad33 - FirebaseCore: 62fd4d549f5e3f3bd52b7998721c5fa0557fb355 - FirebaseCoreInternal: 9e46c82a14a3b3a25be4e1e151ce6d21536b89c0 - FirebaseFirestore: 09be82b113fbcb225b9797d2c525ae8886abc7a3 - GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749 - "gRPC-C++": 0968bace703459fd3e5dcb0b2bed4c573dbff046 - gRPC-Core: 17108291d84332196d3c8466b48f016fc17d816d - leveldb-library: f03246171cce0484482ec291f88b6d563699ee06 - nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 - PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - shared: cf27933a6f2b84a6642d82be3a8ab8c890a0be05 - WebRTC-SDK: dd913fd31cfbf1d43b9a22d83f4c6354c960c623 - -PODFILE CHECKSUM: 0e3d5ba6f75052d9af66e13da5a081f724211aa3 - -COCOAPODS: 1.15.2 diff --git a/sample/app-ios/app-ios.xcodeproj/project.pbxproj b/sample/app-ios/app-ios.xcodeproj/project.pbxproj deleted file mode 100644 index 81e757c0..00000000 --- a/sample/app-ios/app-ios.xcodeproj/project.pbxproj +++ /dev/null @@ -1,473 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - 6ACDDCE04906D276376CC975 /* Pods_app_ios.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3341A31D5C24E958B40A16B /* Pods_app_ios.framework */; }; - FA11F3382899C22A00676143 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA11F3372899C22A00676143 /* AppDelegate.swift */; }; - FA11F33A2899C22A00676143 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA11F3392899C22A00676143 /* SceneDelegate.swift */; }; - FA11F33C2899C22A00676143 /* StartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA11F33B2899C22A00676143 /* StartViewController.swift */; }; - FA11F33F2899C22B00676143 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA11F33D2899C22B00676143 /* Main.storyboard */; }; - FA11F3412899C22C00676143 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA11F3402899C22C00676143 /* Assets.xcassets */; }; - FA11F3442899C22C00676143 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA11F3422899C22C00676143 /* LaunchScreen.storyboard */; }; - FAAF8FF7289A988C004FB1B1 /* RoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAF8FF6289A988C004FB1B1 /* RoomViewController.swift */; }; - FAFB7A41289BF42C002B18FD /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = FAFB7A40289BF42C002B18FD /* GoogleService-Info.plist */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - FA9BD2A428E0A17700E7A36C /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 5D7E487A4CEE7EB53148FE53 /* Pods-app-ios.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-ios.debug.xcconfig"; path = "Target Support Files/Pods-app-ios/Pods-app-ios.debug.xcconfig"; sourceTree = ""; }; - 7BFC8A98B5397F3BAC737F4D /* Pods-app-ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-ios.release.xcconfig"; path = "Target Support Files/Pods-app-ios/Pods-app-ios.release.xcconfig"; sourceTree = ""; }; - F3341A31D5C24E958B40A16B /* Pods_app_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_app_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - FA11F3342899C22A00676143 /* app-ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "app-ios.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - FA11F3372899C22A00676143 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - FA11F3392899C22A00676143 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - FA11F33B2899C22A00676143 /* StartViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartViewController.swift; sourceTree = ""; }; - FA11F33E2899C22B00676143 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - FA11F3402899C22C00676143 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - FA11F3432899C22C00676143 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - FA11F3452899C22C00676143 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - FAAF8FF6289A988C004FB1B1 /* RoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomViewController.swift; sourceTree = ""; }; - FAFB7A40289BF42C002B18FD /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - FA11F3312899C22A00676143 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6ACDDCE04906D276376CC975 /* Pods_app_ios.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 043F32BC1BEEF67A45BD5794 /* Pods */ = { - isa = PBXGroup; - children = ( - 5D7E487A4CEE7EB53148FE53 /* Pods-app-ios.debug.xcconfig */, - 7BFC8A98B5397F3BAC737F4D /* Pods-app-ios.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - FA11F32B2899C22A00676143 = { - isa = PBXGroup; - children = ( - FA11F3362899C22A00676143 /* app-ios */, - FA11F3352899C22A00676143 /* Products */, - FA11F34B2899C70E00676143 /* Frameworks */, - 043F32BC1BEEF67A45BD5794 /* Pods */, - ); - sourceTree = ""; - }; - FA11F3352899C22A00676143 /* Products */ = { - isa = PBXGroup; - children = ( - FA11F3342899C22A00676143 /* app-ios.app */, - ); - name = Products; - sourceTree = ""; - }; - FA11F3362899C22A00676143 /* app-ios */ = { - isa = PBXGroup; - children = ( - FA11F3372899C22A00676143 /* AppDelegate.swift */, - FA11F3392899C22A00676143 /* SceneDelegate.swift */, - FA11F33B2899C22A00676143 /* StartViewController.swift */, - FAAF8FF6289A988C004FB1B1 /* RoomViewController.swift */, - FA11F33D2899C22B00676143 /* Main.storyboard */, - FA11F3402899C22C00676143 /* Assets.xcassets */, - FA11F3422899C22C00676143 /* LaunchScreen.storyboard */, - FA11F3452899C22C00676143 /* Info.plist */, - FAFB7A40289BF42C002B18FD /* GoogleService-Info.plist */, - ); - path = "app-ios"; - sourceTree = ""; - }; - FA11F34B2899C70E00676143 /* Frameworks */ = { - isa = PBXGroup; - children = ( - F3341A31D5C24E958B40A16B /* Pods_app_ios.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - FA11F3332899C22A00676143 /* app-ios */ = { - isa = PBXNativeTarget; - buildConfigurationList = FA11F3482899C22C00676143 /* Build configuration list for PBXNativeTarget "app-ios" */; - buildPhases = ( - C3DBAC6BF516EA57841C92C2 /* [CP] Check Pods Manifest.lock */, - FA11F3302899C22A00676143 /* Sources */, - FA11F3312899C22A00676143 /* Frameworks */, - FA11F3322899C22A00676143 /* Resources */, - FA9BD2A428E0A17700E7A36C /* Embed Frameworks */, - 12628261B270E57875EAEDFA /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "app-ios"; - productName = "app-ios"; - productReference = FA11F3342899C22A00676143 /* app-ios.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - FA11F32C2899C22A00676143 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1340; - LastUpgradeCheck = 1340; - TargetAttributes = { - FA11F3332899C22A00676143 = { - CreatedOnToolsVersion = 13.4.1; - }; - }; - }; - buildConfigurationList = FA11F32F2899C22A00676143 /* Build configuration list for PBXProject "app-ios" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = FA11F32B2899C22A00676143; - productRefGroup = FA11F3352899C22A00676143 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - FA11F3332899C22A00676143 /* app-ios */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - FA11F3322899C22A00676143 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - FA11F3442899C22C00676143 /* LaunchScreen.storyboard in Resources */, - FAFB7A41289BF42C002B18FD /* GoogleService-Info.plist in Resources */, - FA11F3412899C22C00676143 /* Assets.xcassets in Resources */, - FA11F33F2899C22B00676143 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 12628261B270E57875EAEDFA /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app-ios/Pods-app-ios-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app-ios/Pods-app-ios-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app-ios/Pods-app-ios-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - C3DBAC6BF516EA57841C92C2 /* [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-app-ios-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 */ - FA11F3302899C22A00676143 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - FA11F33C2899C22A00676143 /* StartViewController.swift in Sources */, - FA11F3382899C22A00676143 /* AppDelegate.swift in Sources */, - FA11F33A2899C22A00676143 /* SceneDelegate.swift in Sources */, - FAAF8FF7289A988C004FB1B1 /* RoomViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - FA11F33D2899C22B00676143 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - FA11F33E2899C22B00676143 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - FA11F3422899C22C00676143 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - FA11F3432899C22C00676143 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - FA11F3462899C22C00676143 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; - 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - 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 = 13.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - FA11F3472899C22C00676143 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; - 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - 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 = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - FA11F3492899C22C00676143 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5D7E487A4CEE7EB53148FE53 /* Pods-app-ios.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = Q86C89UZUH; - ENABLE_BITCODE = NO; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "app-ios/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = "WebRTC KMP Sample"; - INFOPLIST_KEY_NSCameraUsageDescription = "Access to the camera is required."; - INFOPLIST_KEY_NSMicrophoneUsageDescription = "Access to the microphone is required."; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = com.shepeliev.webrtckmp.sampleapp; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - }; - name = Debug; - }; - FA11F34A2899C22C00676143 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7BFC8A98B5397F3BAC737F4D /* Pods-app-ios.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = Q86C89UZUH; - ENABLE_BITCODE = NO; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "app-ios/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = "WebRTC KMP Sample"; - INFOPLIST_KEY_NSCameraUsageDescription = "Access to the camera is required."; - INFOPLIST_KEY_NSMicrophoneUsageDescription = "Access to the microphone is required."; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = com.shepeliev.webrtckmp.sampleapp; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - FA11F32F2899C22A00676143 /* Build configuration list for PBXProject "app-ios" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - FA11F3462899C22C00676143 /* Debug */, - FA11F3472899C22C00676143 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - FA11F3482899C22C00676143 /* Build configuration list for PBXNativeTarget "app-ios" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - FA11F3492899C22C00676143 /* Debug */, - FA11F34A2899C22C00676143 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = FA11F32C2899C22A00676143 /* Project object */; -} diff --git a/sample/app-ios/app-ios.xcodeproj/xcshareddata/xcschemes/app-ios.xcscheme b/sample/app-ios/app-ios.xcodeproj/xcshareddata/xcschemes/app-ios.xcscheme deleted file mode 100644 index 068c29d4..00000000 --- a/sample/app-ios/app-ios.xcodeproj/xcshareddata/xcschemes/app-ios.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample/app-ios/app-ios.xcworkspace/contents.xcworkspacedata b/sample/app-ios/app-ios.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index ad23cfa3..00000000 --- a/sample/app-ios/app-ios.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/sample/app-ios/app-ios.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sample/app-ios/app-ios.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/sample/app-ios/app-ios.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/sample/app-ios/app-ios/AppDelegate.swift b/sample/app-ios/app-ios/AppDelegate.swift deleted file mode 100644 index 3b5220c0..00000000 --- a/sample/app-ios/app-ios/AppDelegate.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// AppDelegate.swift -// app-ios -// -// Created by Aleksandr Shepeliev on 02.08.2022. -// - -import UIKit -import shared - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - var lifecycle: LifecycleRegistry! - var room: Room! - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - lifecycle = LifecycleRegistryKt.LifecycleRegistry() - let context = DefaultComponentContext(lifecycle: self.lifecycle) - room = RoomComponent(componentContext: context) - - lifecycle.onCreate() - lifecycle.onStart() - lifecycle.onResume() - - return true - } - - func applicationWillTerminate(_ application: UIApplication) { - lifecycle.onPause() - lifecycle.onStop() - lifecycle.onDestroy() - } - - // MARK: UISceneSession Lifecycle - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } - - -} - diff --git a/sample/app-ios/app-ios/Assets.xcassets/AppIcon.appiconset/Contents.json b/sample/app-ios/app-ios/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 9221b9bb..00000000 --- a/sample/app-ios/app-ios/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/sample/app-ios/app-ios/Base.lproj/LaunchScreen.storyboard b/sample/app-ios/app-ios/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e9329..00000000 --- a/sample/app-ios/app-ios/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample/app-ios/app-ios/Base.lproj/Main.storyboard b/sample/app-ios/app-ios/Base.lproj/Main.storyboard deleted file mode 100644 index 484b0fb6..00000000 --- a/sample/app-ios/app-ios/Base.lproj/Main.storyboard +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample/app-ios/app-ios/GoogleService-Info.plist b/sample/app-ios/app-ios/GoogleService-Info.plist deleted file mode 100644 index 259baa6e..00000000 --- a/sample/app-ios/app-ios/GoogleService-Info.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - CLIENT_ID - 216132728347-n1k42tu2k9n6iigihgrfesrnu7e5bvtk.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.216132728347-n1k42tu2k9n6iigihgrfesrnu7e5bvtk - API_KEY - AIzaSyD26yvz58nr-cH1LGiccfT4MJXFSagcWmU - GCM_SENDER_ID - 216132728347 - PLIST_VERSION - 1 - BUNDLE_ID - com.shepeliev.webrtckmp.sample - PROJECT_ID - app-rtc-kmp - STORAGE_BUCKET - app-rtc-kmp.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:216132728347:ios:d76057bc501cb5da872abe - - \ No newline at end of file diff --git a/sample/app-ios/app-ios/Info.plist b/sample/app-ios/app-ios/Info.plist deleted file mode 100644 index 9f650035..00000000 --- a/sample/app-ios/app-ios/Info.plist +++ /dev/null @@ -1,29 +0,0 @@ - - - - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - - NSCameraUsageDescription - Access to the camera is required. - NSMicrophoneUsageDescription - Access to the microphone is required. - - diff --git a/sample/app-ios/app-ios/RoomViewController.swift b/sample/app-ios/app-ios/RoomViewController.swift deleted file mode 100644 index fc09f74f..00000000 --- a/sample/app-ios/app-ios/RoomViewController.swift +++ /dev/null @@ -1,164 +0,0 @@ -// -// RoomViewController.swift -// app-ios -// -// Created by Aleksandr Shepeliev on 03.08.2022. -// - -import UIKit -import WebRTC -import shared - -class RoomViewController: UIViewController { - - @IBOutlet weak var createRoomButton: UIButton! - - @IBOutlet weak var joinRoomButton: UIButton! - - @IBOutlet weak var roomIdContainer: UIStackView! - - @IBOutlet weak var roomIdLabel: UILabel! - -#if arch(x86_64) - private var localVideo = RTCEAGLVideoView() - private var remoteVideo = RTCEAGLVideoView() -#else - private var localVideo = RTCMTLVideoView() - private var remoteVideo = RTCMTLVideoView() -#endif - - private var room: Room! = (UIApplication.shared.delegate as! AppDelegate).room - private var isLocalVideoAttached = false - private var isRemoteVideoAttached = false - private var localVideoTopConstraint: NSLayoutConstraint! - - override func viewDidLoad() { - super.viewDidLoad() - - setupLocalVideo() - - room.model.subscribe(observer: roomModelObserver) - room.openUserMedia() - } - - private func roomModelObserver(_ model: RoomModel) { - NSLog("Room model updated: \(model)") - - if let localStream = model.localStream { - localStreamReady(localStream) - } - - if let remoteStream = model.remoteStream { - remoteStreamReady(remoteStream) - } - - if let roomId = model.roomId { - createRoomButton.isHidden = true - joinRoomButton.isHidden = true - roomIdContainer.isHidden = false - roomIdLabel.text = roomId - } - } - - private func localStreamReady(_ localStream: MediaStream) { - if !isLocalVideoAttached { - isLocalVideoAttached = true - localStream.videoTracks.first?.addRenderer(renderer: localVideo) - } - } - - private func remoteStreamReady(_ remoteStream: MediaStream) { - if !isRemoteVideoAttached { - isRemoteVideoAttached = true - setupRemoteVideo() - - if let track = remoteStream.videoTracks.first { - track.addRenderer(renderer: remoteVideo) - } - } - } - - override func viewWillDisappear(_ animated: Bool) { - room.hangup() - room.model.unsubscribe(observer: roomModelObserver) - } - - // MARK: Actions - - @IBAction func createRoomButtonDidClick(_ sender: Any) { - room.createRoom() - } - - @IBAction func joinRoomButtonDidClick(_ sender: Any) { - let joinDialog = UIAlertController(title: "Join room", message: "Enter room ID", preferredStyle: .alert) - let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in - if let roomId = joinDialog.textFields?.first?.text { - self?.room?.joinRoom(roomId: roomId) - } - } - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) - joinDialog.addTextField() - joinDialog.addAction(okAction) - joinDialog.addAction(cancelAction) - present(joinDialog, animated: true) - } - - @IBAction func copyRoomIdDidClick(_ sender: Any) { - if let roomId = roomIdLabel.text { - UIPasteboard.general.string = roomId - NSLog("Room ID \(roomId) copied.") - } - } - - @IBAction func switchCamera(_ sender: Any) { - room.switchCamera() - } - - // MARK: - UI - private func setupLocalVideo() { - localVideo.contentMode = .scaleAspectFill - view.insertSubview(localVideo, at: 0) - localVideo.translatesAutoresizingMaskIntoConstraints = false - setupLocalVideoConstraints() - } - - private func setupLocalVideoConstraints() { - localVideo.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true - localVideo.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true - localVideo.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true - - localVideoTopConstraint = localVideo.topAnchor.constraint(equalTo: view.topAnchor) - localVideoTopConstraint.isActive = true - } - - private func setupRemoteVideo() { - view.insertSubview(remoteVideo, at: 0) - remoteVideo.translatesAutoresizingMaskIntoConstraints = false - setupRemoteVideoConstraints() - } - - private func setupRemoteVideoConstraints() { - localVideoTopConstraint.isActive = false - - remoteVideo.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true - remoteVideo.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true - remoteVideo.topAnchor.constraint(equalTo: view.topAnchor).isActive = true - remoteVideo.heightAnchor.constraint(equalTo: localVideo.heightAnchor).isActive = true - - localVideoTopConstraint = localVideo.topAnchor.constraint(equalTo: remoteVideo.bottomAnchor) - localVideoTopConstraint.isActive = true - - localVideo.layoutIfNeeded() - } - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ - -} diff --git a/sample/app-ios/app-ios/SceneDelegate.swift b/sample/app-ios/app-ios/SceneDelegate.swift deleted file mode 100644 index 85e56a02..00000000 --- a/sample/app-ios/app-ios/SceneDelegate.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// SceneDelegate.swift -// app-ios -// -// Created by Aleksandr Shepeliev on 02.08.2022. -// - -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } - - -} - diff --git a/sample/app-ios/app-ios/StartViewController.swift b/sample/app-ios/app-ios/StartViewController.swift deleted file mode 100644 index 762b71a2..00000000 --- a/sample/app-ios/app-ios/StartViewController.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ViewController.swift -// app-ios -// -// Created by Aleksandr Shepeliev on 02.08.2022. -// - -import UIKit - -class StartViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } - - @IBAction func hangupAction(unwindSegue: UIStoryboardSegue) { - - } -} - diff --git a/sample/app-web/build.gradle.kts b/sample/app-web/build.gradle.kts deleted file mode 100644 index 69ae22f7..00000000 --- a/sample/app-web/build.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - kotlin("js") - id("org.jlleitschuh.gradle.ktlint") -} - -kotlin { - js(IR) { - browser() - binaries.executable() - } -} - -dependencies { - implementation(project(":sample:shared")) - implementation(project.dependencies.enforcedPlatform(deps.kotlin.wrappers.bom)) - implementation(deps.kotlin.wrappers.emotion) - implementation(deps.kotlin.wrappers.react) - implementation(deps.kotlin.wrappers.reactDom) - implementation(deps.kotlin.wrappers.mui) -} diff --git a/sample/app-web/src/main/kotlin/App.kt b/sample/app-web/src/main/kotlin/App.kt deleted file mode 100644 index ece82835..00000000 --- a/sample/app-web/src/main/kotlin/App.kt +++ /dev/null @@ -1,109 +0,0 @@ -import com.arkivanov.decompose.value.Value -import com.shepeliev.webrtckmp.sample.shared.Room -import csstype.px -import emotion.react.css -import kotlinx.browser.window -import mui.material.Button -import mui.material.ButtonVariant -import org.w3c.dom.HTMLVideoElement -import react.FC -import react.Props -import react.dom.html.ReactHTML.div -import react.dom.html.ReactHTML.h1 -import react.dom.html.ReactHTML.p -import react.dom.html.ReactHTML.video -import react.useEffect -import react.useRef -import react.useState - -external interface AppProps : Props { - var room: Room -} - -val App = FC { props -> - h1 { +"WebRTC KMP Sample" } - - val room = props.room - val roomModel: Room.Model = useValue(room.model) - - roomModel.roomId?.let { - p { +"Room ID: ${roomModel.roomId}" } - } - - div { - Button { - variant = ButtonVariant.contained - onClick = { room.openUserMedia() } - disabled = roomModel.localStream != null - +"Open camera and microphone" - } - - Button { - variant = ButtonVariant.contained - onClick = { room.createRoom() } - disabled = roomModel.isJoining || roomModel.localStream == null || roomModel.roomId != null - +"Create room" - } - - Button { - variant = ButtonVariant.contained - onClick = { - // TODO replace by Material UI dialog - window.prompt("Room ID:")?.let { room.joinRoom(it) } - } - disabled = roomModel.isJoining || roomModel.localStream == null || roomModel.roomId != null - +"Join room" - } - - Button { - variant = ButtonVariant.contained - onClick = { room.hangup() } - disabled = roomModel.localStream == null - +"Hangup" - } - } - - val localVideoRef = useRef(null) - useEffect(roomModel, localVideoRef) { - localVideoRef.current?.srcObject = roomModel.localStream?.js - } - - val remoteVideoRef = useRef(null) - useEffect { - remoteVideoRef.current?.srcObject = roomModel.remoteStream?.js - } - - div { - video { - css { - width = 640.px - height = 480.px - } - ref = localVideoRef - autoPlay = true - } - - if (roomModel.remoteStream != null) { - video { - css { - width = 640.px - height = 480.px - } - ref = remoteVideoRef - autoPlay = true - } - } - } -} - -fun useValue(value: Value): T { - var result by useState(value.value) - - useEffect(value, result) { - val valueObserver: (T) -> Unit = { result = it } - value.subscribe(valueObserver) - cleanup { value.unsubscribe(valueObserver) } - } - - return result -} diff --git a/sample/app-web/src/main/kotlin/LifecycleRegistry.kt b/sample/app-web/src/main/kotlin/LifecycleRegistry.kt deleted file mode 100644 index 6f184e66..00000000 --- a/sample/app-web/src/main/kotlin/LifecycleRegistry.kt +++ /dev/null @@ -1,18 +0,0 @@ -import com.arkivanov.essenty.lifecycle.LifecycleRegistry -import com.arkivanov.essenty.lifecycle.resume -import com.arkivanov.essenty.lifecycle.stop -import kotlinx.browser.document -import org.w3c.dom.Document - -fun LifecycleRegistry.attachToDocument() { - fun onVisibilityChanged() { - when (document.visibilityState) { - "visible" -> resume() - else -> stop() - } - } - - document.addEventListener("visibilitychange", callback = { onVisibilityChanged() }) -} - -private val Document.visibilityState: String get() = asDynamic().visibilityState.unsafeCast() diff --git a/sample/app-web/src/main/kotlin/Main.kt b/sample/app-web/src/main/kotlin/Main.kt deleted file mode 100644 index 62112ac3..00000000 --- a/sample/app-web/src/main/kotlin/Main.kt +++ /dev/null @@ -1,22 +0,0 @@ -import com.arkivanov.decompose.DefaultComponentContext -import com.arkivanov.essenty.lifecycle.LifecycleRegistry -import com.shepeliev.webrtckmp.sample.shared.RoomComponent -import kotlinx.browser.document -import react.Fragment -import react.create -import react.dom.client.createRoot - -fun main() { - val container = document.getElementById("root") ?: error("No root element.") - val root = createRoot(container) - - root.render( - Fragment.create { - App { - val lifecycle = LifecycleRegistry().apply { attachToDocument() } - val context = DefaultComponentContext(lifecycle) - room = RoomComponent(context) - } - } - ) -} diff --git a/sample/composeApp/build.gradle.kts b/sample/composeApp/build.gradle.kts new file mode 100644 index 00000000..a73ab43c --- /dev/null +++ b/sample/composeApp/build.gradle.kts @@ -0,0 +1,135 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + + +plugins { + kotlin("multiplatform") + id("com.android.application") + kotlin("native.cocoapods") + alias(libs.plugins.jetbrains.compose) +} + +kotlin { + configureKotlinCompilerArgs() + + cocoapods { + version = "1.0" + summary = "Compose app" + homepage = "not published" + ios.deploymentTarget = "13.0" + + pod("WebRTC-SDK") { + version = "114.5735.02" + moduleName = "WebRTC" + packageName = "WebRTC" + } + } + + androidTarget { + configureJvmTarget() + } + + listOf( + iosX64 { configureWebRtcCinterops() }, + iosArm64 { configureWebRtcCinterops() }, + iosSimulatorArm64 { configureWebRtcCinterops() } + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + js { + browser { + binaries.executable() + } + } + + sourceSets { + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.kotlin.coroutines) + implementation(libs.kermit) + implementation(project(":webrtc-kmp")) + } + + androidMain.dependencies { + implementation(libs.kotlin.coroutines.android) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.androidx.activity.compose) + implementation(libs.accompanist.permissions) + } + + jsMain.dependencies { + implementation(project.dependencies.platform(libs.kotlin.wrappers.bom)) + implementation(libs.kotlin.wrappers.mui) + implementation(libs.kotlin.wrappers.react) + implementation(libs.kotlin.wrappers.reactDom) + implementation(libs.kotlin.wrappers.emotion) + } + } +} + +android { + namespace = "com.shepeliev.webrtckmp.sample" + compileSdk = libs.versions.compileSdk.get().toInt() + + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + sourceSets["main"].resources.srcDirs("src/commonMain/resources") + + defaultConfig { + applicationId = "com.shepeliev.webrtckmp.sample" + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + dependencies { + debugImplementation(libs.compose.ui.tooling) + } +} + +fun KotlinNativeTarget.configureWebRtcCinterops() { + val webRtcFrameworkPath = file("$buildDir/cocoapods/synthetic/IOS/Pods/WebRTC-SDK") + .resolveArchPath(konanTarget, "WebRTC") + compilations.getByName("main") { + cinterops.getByName("WebRTC") { + compilerOpts("-framework", "WebRTC", "-F$webRtcFrameworkPath") + } + } + + binaries { + getTest("DEBUG").apply { + linkerOpts( + "-framework", + "WebRTC", + "-F$webRtcFrameworkPath", + "-rpath", + "$webRtcFrameworkPath", + "-ObjC" + ) + } + } +} diff --git a/sample/shared/shared.podspec b/sample/composeApp/composeApp.podspec similarity index 61% rename from sample/shared/shared.podspec rename to sample/composeApp/composeApp.podspec index 0a8d7056..470d17da 100644 --- a/sample/shared/shared.podspec +++ b/sample/composeApp/composeApp.podspec @@ -1,37 +1,35 @@ Pod::Spec.new do |spec| - spec.name = 'shared' - spec.version = '1.0.0' - spec.homepage = 'https://github.com/shepeliev/webrtc-kmp/tree/main/sample' + spec.name = 'composeApp' + spec.version = '1.0' + spec.homepage = 'not published' spec.source = { :http=> ''} spec.authors = '' spec.license = '' - spec.summary = 'Shared framework for WebRTC KMP sample' - spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework' + spec.summary = 'Compose app' + spec.vendored_frameworks = 'build/cocoapods/framework/composeApp.framework' spec.libraries = 'c++' - spec.ios.deployment_target = '11.0' - spec.dependency 'FirebaseCore' - spec.dependency 'FirebaseFirestore' + spec.ios.deployment_target = '13.0' spec.dependency 'WebRTC-SDK', '114.5735.02' - if !Dir.exist?('build/cocoapods/framework/shared.framework') || Dir.empty?('build/cocoapods/framework/shared.framework') + if !Dir.exist?('build/cocoapods/framework/composeApp.framework') || Dir.empty?('build/cocoapods/framework/composeApp.framework') raise " - Kotlin framework 'shared' doesn't exist yet, so a proper Xcode project can't be generated. + Kotlin framework 'composeApp' doesn't exist yet, so a proper Xcode project can't be generated. 'pod install' should be executed after running ':generateDummyFramework' Gradle task: - ./gradlew :sample:shared:generateDummyFramework + ./gradlew :sample:composeApp:generateDummyFramework Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" end spec.pod_target_xcconfig = { - 'KOTLIN_PROJECT_PATH' => ':sample:shared', - 'PRODUCT_MODULE_NAME' => 'shared', + 'KOTLIN_PROJECT_PATH' => ':sample:composeApp', + 'PRODUCT_MODULE_NAME' => 'composeApp', } spec.script_phases = [ { - :name => 'Build shared', + :name => 'Build composeApp', :execution_position => :before_compile, :shell_path => '/bin/sh', :script => <<-SCRIPT @@ -48,5 +46,5 @@ Pod::Spec.new do |spec| SCRIPT } ] - + spec.resources = ['build/compose/ios/composeApp/compose-resources'] end \ No newline at end of file diff --git a/sample/app-android/src/main/AndroidManifest.xml b/sample/composeApp/src/androidMain/AndroidManifest.xml similarity index 50% rename from sample/app-android/src/main/AndroidManifest.xml rename to sample/composeApp/src/androidMain/AndroidManifest.xml index 94b8ac46..7083d7c6 100644 --- a/sample/app-android/src/main/AndroidManifest.xml +++ b/sample/composeApp/src/androidMain/AndroidManifest.xml @@ -1,16 +1,25 @@ + + + + + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@android:style/Theme.Material.Light.NoActionBar"> diff --git a/sample/composeApp/src/androidMain/kotlin/StartButton.android.kt b/sample/composeApp/src/androidMain/kotlin/StartButton.android.kt new file mode 100644 index 00000000..c22227c5 --- /dev/null +++ b/sample/composeApp/src/androidMain/kotlin/StartButton.android.kt @@ -0,0 +1,61 @@ +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.preference.PreferenceManager +import android.provider.Settings +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.edit +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.rememberMultiplePermissionsState + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +actual fun StartButton(onClick: () -> Unit, modifier: Modifier) { + val context = LocalContext.current + + val permissions = rememberMultiplePermissionsState( + listOf( + android.Manifest.permission.CAMERA, + android.Manifest.permission.RECORD_AUDIO + ) + ) { + if (it.all { (_, granted) -> granted }) { + onClick() + } + } + + Button(onClick = { + if (permissions.allPermissionsGranted) { + onClick() + } else { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val permissionsRequested = prefs.getBoolean("permissionsRequested", false) + if (!permissions.shouldShowRationale && permissionsRequested) { + context.navigateToAppSettings() + return@Button + } + + prefs.edit { putBoolean("permissionsRequested", true) } + permissions.launchMultiplePermissionRequest() + } + }, modifier = modifier) { + Text("Start") + } +} + +private fun Context.navigateToAppSettings() { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", packageName, null) + addCategory(Intent.CATEGORY_DEFAULT) + addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_NO_HISTORY or + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + ) + } + startActivity(intent) +} diff --git a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/Video.kt b/sample/composeApp/src/androidMain/kotlin/Video.android.kt similarity index 83% rename from sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/Video.kt rename to sample/composeApp/src/androidMain/kotlin/Video.android.kt index 9f943794..260fa44d 100644 --- a/sample/app-android/src/main/java/com/shepeliev/webrtckmp/sample/Video.kt +++ b/sample/composeApp/src/androidMain/kotlin/Video.android.kt @@ -1,5 +1,3 @@ -package com.shepeliev.webrtckmp.sample - import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -18,12 +16,7 @@ import org.webrtc.SurfaceViewRenderer import org.webrtc.VideoSink @Composable -fun Video( - track: VideoStreamTrack, - modifier: Modifier = Modifier, - scalingTypeMatchOrientation: RendererCommon.ScalingType = RendererCommon.ScalingType.SCALE_ASPECT_BALANCED, - scalingTypeMismatchOrientation: RendererCommon.ScalingType = RendererCommon.ScalingType.SCALE_ASPECT_FIT, -) { +actual fun Video(track: VideoStreamTrack, modifier: Modifier) { var renderer by remember { mutableStateOf(null) } val lifecycleEventObserver = remember(renderer, track) { @@ -63,7 +56,10 @@ fun Video( modifier = modifier, factory = { context -> SurfaceViewRenderer(context).apply { - setScalingType(scalingTypeMatchOrientation, scalingTypeMismatchOrientation) + setScalingType( + RendererCommon.ScalingType.SCALE_ASPECT_BALANCED, + RendererCommon.ScalingType.SCALE_ASPECT_FIT + ) renderer = this } }, @@ -71,11 +67,11 @@ fun Video( } private fun VideoStreamTrack.addSinkCatching(sink: VideoSink) { - // runCatching as track may be disposed while activity was in pause mode + // runCatching as track may be disposed while activity was in pause runCatching { addSink(sink) } } private fun VideoStreamTrack.removeSinkCatching(sink: VideoSink) { - // runCatching as track may be disposed while activity was in pause mode + // runCatching as track may be disposed while activity was in pause runCatching { removeSink(sink) } } diff --git a/sample/composeApp/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/MainActivity.kt b/sample/composeApp/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/MainActivity.kt new file mode 100644 index 00000000..bf7357a0 --- /dev/null +++ b/sample/composeApp/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/MainActivity.kt @@ -0,0 +1,45 @@ +package com.shepeliev.webrtckmp.sample + +import App +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import com.shepeliev.webrtckmp.WebRtc +import org.webrtc.Loggable +import org.webrtc.Logging + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + WebRtc.factoryInitializationOptionsBuilder + .setInjectableLogger(WebRtcLogger, Logging.Severity.LS_ERROR) + + setContent { + App() + } + } +} + +private object WebRtcLogger : Loggable { + override fun onLogMessage(message: String, severity: Logging.Severity, tag: String) { + when (severity) { + Logging.Severity.LS_ERROR -> Log.e(tag, message) + Logging.Severity.LS_WARNING -> Log.w(tag, message) + Logging.Severity.LS_INFO -> Log.i(tag, message) + Logging.Severity.LS_VERBOSE -> Log.v(tag, message) + Logging.Severity.LS_NONE -> { + + } + } + } +} + +@Preview +@Composable +fun AppAndroidPreview() { + App() +} diff --git a/sample/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/sample/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/sample/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sample/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/sample/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/sample/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/sample/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/sample/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a571e60098c92c2baca8a5df62f2929cbff01b52 GIT binary patch literal 3593 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 P00000NkvXXu0mjfP=x?Y literal 0 HcmV?d00001 diff --git a/sample/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/sample/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..61da551c5594a1f9d26193983d2cd69189014603 GIT binary patch literal 5339 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 literal 0 HcmV?d00001 diff --git a/sample/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b216f2d313cc673d8b8c4da591c174ebed52795c GIT binary patch literal 11873 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 literal 0 HcmV?d00001 diff --git a/sample/composeApp/src/androidMain/res/values/strings.xml b/sample/composeApp/src/androidMain/res/values/strings.xml new file mode 100644 index 00000000..a56cc825 --- /dev/null +++ b/sample/composeApp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + WebRTC KMP Playground + \ No newline at end of file diff --git a/sample/composeApp/src/commonMain/kotlin/App.kt b/sample/composeApp/src/commonMain/kotlin/App.kt new file mode 100644 index 00000000..7cbdb24e --- /dev/null +++ b/sample/composeApp/src/commonMain/kotlin/App.kt @@ -0,0 +1,122 @@ +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import co.touchlab.kermit.Logger +import co.touchlab.kermit.platformLogWriter +import com.shepeliev.webrtckmp.MediaDevices +import com.shepeliev.webrtckmp.MediaStream +import com.shepeliev.webrtckmp.PeerConnection +import com.shepeliev.webrtckmp.VideoStreamTrack +import com.shepeliev.webrtckmp.videoTracks +import kotlinx.coroutines.launch +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +@Preview +fun App() { + LaunchedEffect(Unit) { + Logger.setLogWriters(platformLogWriter()) + } + + MaterialTheme { + val scope = rememberCoroutineScope() + val (localStream, setLocalStream) = remember { mutableStateOf(null) } + val (remoteVideoTrack, setRemoteVideoTrack) = remember { mutableStateOf(null) } + val (peerConnections, setPeerConnections) = remember { + mutableStateOf?>(null) + } + + LaunchedEffect(localStream, peerConnections) { + if (peerConnections == null || localStream == null) return@LaunchedEffect + makeCall(peerConnections, localStream, setRemoteVideoTrack) + } + + Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + val localVideoTrack = localStream?.videoTracks?.firstOrNull() + + localVideoTrack?.let { Video(track = it, modifier = Modifier.weight(1f)) } + ?: Box(modifier = Modifier.weight(1f)) + + remoteVideoTrack?.let { Video(track = it, modifier = Modifier.weight(1f)) } + ?: Box(modifier = Modifier.weight(1f)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + if (localStream == null) { + StartButton(onClick = { + scope.launch { + val stream = MediaDevices.getUserMedia(audio = true, video = true) + setLocalStream(stream) + } + }) + + return@MaterialTheme + } + + StopButton( + onClick = { + hangup(peerConnections, setPeerConnections, setRemoteVideoTrack) + localStream.release() + setLocalStream(null) + } + ) + + SwitchCameraButton( + onClick = { + scope.launch { localStream.videoTracks.firstOrNull()?.switchCamera() } + } + ) + + if (peerConnections == null) { + CallButton( + onClick = { setPeerConnections(Pair(PeerConnection(), PeerConnection())) }, + ) + } else { + HangupButton(onClick = { + hangup(peerConnections, setPeerConnections, setRemoteVideoTrack) + }) + } + } + } + } +} + +@Composable +private fun CallButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + Button(onClick, modifier = modifier) { + Text("Call") + } +} + +@Composable +private fun HangupButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + Button(onClick, modifier = modifier) { + Text("Hangup") + } +} + +@Composable +private fun SwitchCameraButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + Button(onClick = onClick, modifier = modifier) { + Text("Switch Camera") + } +} + +@Composable +private fun StopButton(onClick: () -> Unit, modifier: Modifier = Modifier) { + Button(onClick = onClick, modifier = modifier) { + Text("Stop") + } +} diff --git a/sample/composeApp/src/commonMain/kotlin/Hangup.kt b/sample/composeApp/src/commonMain/kotlin/Hangup.kt new file mode 100644 index 00000000..034d1c3d --- /dev/null +++ b/sample/composeApp/src/commonMain/kotlin/Hangup.kt @@ -0,0 +1,15 @@ +import com.shepeliev.webrtckmp.PeerConnection +import com.shepeliev.webrtckmp.VideoStreamTrack + +fun hangup( + peerConnections: Pair?, + setPeerConnections: (Pair?) -> Unit, + setRemoteVideoTrack: (VideoStreamTrack?) -> Unit +) { + val (pc1, pc2) = peerConnections ?: return + pc1.getTransceivers().forEach { pc1.removeTrack(it.sender) } + pc1.close() + pc2.close() + setPeerConnections(null) + setRemoteVideoTrack(null) +} diff --git a/sample/composeApp/src/commonMain/kotlin/MakeCall.kt b/sample/composeApp/src/commonMain/kotlin/MakeCall.kt new file mode 100644 index 00000000..e2be7bc5 --- /dev/null +++ b/sample/composeApp/src/commonMain/kotlin/MakeCall.kt @@ -0,0 +1,95 @@ +import co.touchlab.kermit.Logger +import com.shepeliev.webrtckmp.IceCandidate +import com.shepeliev.webrtckmp.MediaStream +import com.shepeliev.webrtckmp.MediaStreamTrackKind +import com.shepeliev.webrtckmp.OfferAnswerOptions +import com.shepeliev.webrtckmp.PeerConnection +import com.shepeliev.webrtckmp.SignalingState +import com.shepeliev.webrtckmp.VideoStreamTrack +import com.shepeliev.webrtckmp.onConnectionStateChange +import com.shepeliev.webrtckmp.onIceCandidate +import com.shepeliev.webrtckmp.onIceConnectionStateChange +import com.shepeliev.webrtckmp.onSignalingStateChange +import com.shepeliev.webrtckmp.onTrack +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +suspend fun makeCall( + peerConnections: Pair, + localStream: MediaStream, + setRemoteVideoTrack: (VideoStreamTrack?) -> Unit +): Nothing = coroutineScope { + val (pc1, pc2) = peerConnections + localStream.tracks.forEach { pc1.addTrack(it) } + val pc1IceCandidates = mutableListOf() + val pc2IceCandidates = mutableListOf() + pc1.onIceCandidate + .onEach { Logger.d { "PC1 onIceCandidate: $it" } } + .onEach { + if (pc2.signalingState == SignalingState.HaveRemoteOffer) { + pc2.addIceCandidate(it) + } else { + pc1IceCandidates.add(it) + } + } + .launchIn(this) + pc2.onIceCandidate + .onEach { Logger.d { "PC2 onIceCandidate: $it" } } + .onEach { + if (pc1.signalingState == SignalingState.HaveRemoteOffer) { + pc1.addIceCandidate(it) + } else { + pc2IceCandidates.add(it) + } + } + .launchIn(this) + pc1.onSignalingStateChange + .onEach { signalingState -> + Logger.d { "PC1 onSignalingStateChange: $signalingState" } + if (signalingState == SignalingState.HaveRemoteOffer) { + pc2IceCandidates.forEach { pc1.addIceCandidate(it) } + pc2IceCandidates.clear() + } + } + .launchIn(this) + pc2.onSignalingStateChange + .onEach { signalingState -> + Logger.d { "PC2 onSignalingStateChange: $signalingState" } + if (signalingState == SignalingState.HaveRemoteOffer) { + pc1IceCandidates.forEach { pc2.addIceCandidate(it) } + pc1IceCandidates.clear() + } + } + .launchIn(this) + pc1.onIceConnectionStateChange + .onEach { Logger.d { "PC1 onIceConnectionStateChange: $it" } } + .launchIn(this) + pc2.onIceConnectionStateChange + .onEach { Logger.d { "PC2 onIceConnectionStateChange: $it" } } + .launchIn(this) + pc1.onConnectionStateChange + .onEach { Logger.d { "PC1 onConnectionStateChange: $it" } } + .launchIn(this) + pc2.onConnectionStateChange + .onEach { Logger.d { "PC2 onConnectionStateChange: $it" } } + .launchIn(this) + pc1.onTrack + .onEach { Logger.d { "PC1 onTrack: $it" } } + .launchIn(this) + pc2.onTrack + .onEach { Logger.d { "PC2 onTrack: ${it.track?.kind}" } } + .filter { it.track?.kind == MediaStreamTrackKind.Video } + .onEach { setRemoteVideoTrack(it.track as VideoStreamTrack) } + .launchIn(this) + val offer = pc1.createOffer(OfferAnswerOptions(offerToReceiveVideo = true, offerToReceiveAudio = true)) + pc1.setLocalDescription(offer) + pc2.setRemoteDescription(offer) + val answer = pc2.createAnswer(options = OfferAnswerOptions()) + pc2.setLocalDescription(answer) + pc1.setRemoteDescription(answer) + + awaitCancellation() +} diff --git a/sample/composeApp/src/commonMain/kotlin/StartButton.kt b/sample/composeApp/src/commonMain/kotlin/StartButton.kt new file mode 100644 index 00000000..331c7c98 --- /dev/null +++ b/sample/composeApp/src/commonMain/kotlin/StartButton.kt @@ -0,0 +1,5 @@ +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +expect fun StartButton(onClick: () -> Unit, modifier: Modifier = Modifier) diff --git a/sample/composeApp/src/commonMain/kotlin/Video.kt b/sample/composeApp/src/commonMain/kotlin/Video.kt new file mode 100644 index 00000000..41449a61 --- /dev/null +++ b/sample/composeApp/src/commonMain/kotlin/Video.kt @@ -0,0 +1,6 @@ +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.shepeliev.webrtckmp.VideoStreamTrack + +@Composable +expect fun Video(track: VideoStreamTrack, modifier: Modifier = Modifier) diff --git a/sample/composeApp/src/iosMain/kotlin/MainViewController.kt b/sample/composeApp/src/iosMain/kotlin/MainViewController.kt new file mode 100644 index 00000000..6c54c55d --- /dev/null +++ b/sample/composeApp/src/iosMain/kotlin/MainViewController.kt @@ -0,0 +1,36 @@ +import WebRTC.RTCAudioSession +import WebRTC.RTCAudioSessionConfiguration +import WebRTC.setConfiguration +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.window.ComposeUIViewController +import co.touchlab.kermit.Logger +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.ObjCObjectVar +import kotlinx.cinterop.alloc +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.ptr +import kotlinx.cinterop.value +import platform.Foundation.NSError + +@Suppress("unused", "FunctionName") +@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) +fun MainViewController() = ComposeUIViewController { + LaunchedEffect(Unit) { + RTCAudioSessionConfiguration.initialize() + memScoped { + val error = alloc>() + with(RTCAudioSession.sharedInstance()) { + lockForConfiguration() + useManualAudio = false + setConfiguration(RTCAudioSessionConfiguration.webRTCConfiguration(), error.ptr) + error.value?.let { + Logger.e { "Error setting WebRTC audio session configuration: ${it.localizedDescription}" } + } + unlockForConfiguration() + } + } + } + + App() +} diff --git a/sample/composeApp/src/iosMain/kotlin/StartButton.ios.kt b/sample/composeApp/src/iosMain/kotlin/StartButton.ios.kt new file mode 100644 index 00000000..62dc1b82 --- /dev/null +++ b/sample/composeApp/src/iosMain/kotlin/StartButton.ios.kt @@ -0,0 +1,11 @@ +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +actual fun StartButton(onClick: () -> Unit, modifier: Modifier) { + Button(onClick = onClick, modifier = modifier) { + Text("Start") + } +} diff --git a/sample/composeApp/src/iosMain/kotlin/Video.ios.kt b/sample/composeApp/src/iosMain/kotlin/Video.ios.kt new file mode 100644 index 00000000..2de7bab9 --- /dev/null +++ b/sample/composeApp/src/iosMain/kotlin/Video.ios.kt @@ -0,0 +1,21 @@ +import WebRTC.RTCMTLVideoView +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.interop.UIKitView +import com.shepeliev.webrtckmp.VideoStreamTrack +import kotlinx.cinterop.ExperimentalForeignApi +import platform.UIKit.UIViewContentMode + +@OptIn(ExperimentalForeignApi::class) +@Composable +actual fun Video(track: VideoStreamTrack, modifier: Modifier) { + UIKitView( + factory = { + RTCMTLVideoView().apply { + contentMode = UIViewContentMode.UIViewContentModeScaleAspectFit + track.addRenderer(this) + } + }, + modifier = modifier, + ) +} diff --git a/sample/composeApp/src/jsMain/kotlin/App.kt b/sample/composeApp/src/jsMain/kotlin/App.kt new file mode 100644 index 00000000..5829a316 --- /dev/null +++ b/sample/composeApp/src/jsMain/kotlin/App.kt @@ -0,0 +1,139 @@ +import com.shepeliev.webrtckmp.MediaDevices +import com.shepeliev.webrtckmp.MediaStream +import com.shepeliev.webrtckmp.PeerConnection +import com.shepeliev.webrtckmp.VideoStreamTrack +import emotion.react.css +import kotlinx.coroutines.launch +import mui.material.Button +import mui.material.ButtonVariant +import react.FC +import react.Props +import react.dom.html.ReactHTML.div +import react.dom.html.ReactHTML.h1 +import react.dom.html.ReactHTML.video +import react.useEffect +import react.useRef +import react.useState +import web.cssom.Display +import web.cssom.JustifyContent +import web.cssom.px +import web.html.HTMLVideoElement + +val ReactApp = FC { _ -> + h1 { +"WebRTC KMP Sample" } + + val scope = useCoroutineScope() + val localVideoRef = useRef(null) + val remoteVideoRef = useRef(null) + val (localStream, setLocalStream) = useState(null) + val (remoteVideoTrack, setRemoteVideoTrack) = useState(null) + val (peerConnections, setPeerConnections) = useState?>(null) + + useEffect(localStream) { + localVideoRef.current?.srcObject = localStream?.js + } + + useEffect(remoteVideoTrack) { + val stream = org.w3c.dom.mediacapture.MediaStream().apply { + remoteVideoTrack?.js?.let { addTrack(it) } + } + remoteVideoRef.current?.srcObject = stream + } + + useEffect(localStream, peerConnections) { + if (peerConnections == null || localStream == null) return@useEffect + val job = scope.launch { + makeCall( + peerConnections = peerConnections, + localStream = localStream, + setRemoteVideoTrack = { setRemoteVideoTrack(it) } + ) + } + + cleanup { job.cancel() } + } + + div { + video { + css { + width = 640.px + height = 480.px + paddingRight = 32.px + } + ref = localVideoRef + autoPlay = true + } + + video { + css { + width = 640.px + height = 480.px + } + ref = remoteVideoRef + autoPlay = true + } + } + + div { + css { + width = 640.px + display = Display.flex + justifyContent = JustifyContent.spaceBetween + } + + if (localStream == null) { + Button { + css { + width = 200.px + } + variant = ButtonVariant.contained + onClick = { + scope.launch { + val stream = MediaDevices.getUserMedia(audio = true, video = true) + setLocalStream(stream) + } + } + +"Start" + } + + return@div + } + + Button { + css { + width = 200.px + } + variant = ButtonVariant.contained + onClick = { + hangup(peerConnections, { setPeerConnections(it) }, { setRemoteVideoTrack(it) }) + localStream.release() + setLocalStream(null) + } + +"Stop" + } + + if (peerConnections == null) { + Button { + css { + width = 200.px + } + variant = ButtonVariant.contained + onClick = { + setPeerConnections(Pair(PeerConnection(), PeerConnection())) + } + +"Call" + } + } else { + Button { + css { + width = 200.px + } + variant = ButtonVariant.contained + onClick = { + hangup(peerConnections, { setPeerConnections(it) }, { setRemoteVideoTrack(it) }) + } + +"Hangup" + } + } + } +} diff --git a/sample/composeApp/src/jsMain/kotlin/Main.kt b/sample/composeApp/src/jsMain/kotlin/Main.kt new file mode 100644 index 00000000..77801aa2 --- /dev/null +++ b/sample/composeApp/src/jsMain/kotlin/Main.kt @@ -0,0 +1,17 @@ +import kotlinx.browser.document +import react.Fragment +import react.create +import react.dom.client.createRoot +import web.dom.Element + +fun main() { + val container = document.getElementById("root") ?: error("No root element.") + val root = createRoot(container as Element) + + root.render( + Fragment.create { + ReactApp { + } + } + ) +} diff --git a/sample/composeApp/src/jsMain/kotlin/StartButton.js.kt b/sample/composeApp/src/jsMain/kotlin/StartButton.js.kt new file mode 100644 index 00000000..56a0aa63 --- /dev/null +++ b/sample/composeApp/src/jsMain/kotlin/StartButton.js.kt @@ -0,0 +1,7 @@ +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +actual fun StartButton(onClick: () -> Unit, modifier: Modifier) { + // Dummy actual for JS +} diff --git a/sample/composeApp/src/jsMain/kotlin/UseCoroutineScope.kt b/sample/composeApp/src/jsMain/kotlin/UseCoroutineScope.kt new file mode 100644 index 00000000..5ae43781 --- /dev/null +++ b/sample/composeApp/src/jsMain/kotlin/UseCoroutineScope.kt @@ -0,0 +1,12 @@ +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import react.useEffect + +fun useCoroutineScope(): CoroutineScope { + val scope = CoroutineScope(Dispatchers.Main) + useEffect(Unit) { + cleanup { scope.cancel() } + } + return scope +} diff --git a/sample/composeApp/src/jsMain/kotlin/Video.js.kt b/sample/composeApp/src/jsMain/kotlin/Video.js.kt new file mode 100644 index 00000000..b93ab349 --- /dev/null +++ b/sample/composeApp/src/jsMain/kotlin/Video.js.kt @@ -0,0 +1,8 @@ +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.shepeliev.webrtckmp.VideoStreamTrack + +@Composable +actual fun Video(track: VideoStreamTrack, modifier: Modifier) { + // Dummy actual for JS +} diff --git a/sample/app-web/src/main/resources/index.html b/sample/composeApp/src/jsMain/resources/index.html similarity index 79% rename from sample/app-web/src/main/resources/index.html rename to sample/composeApp/src/jsMain/resources/index.html index f4b8adfa..878af099 100644 --- a/sample/app-web/src/main/resources/index.html +++ b/sample/composeApp/src/jsMain/resources/index.html @@ -6,6 +6,6 @@
- + diff --git a/sample/iosApp/Configuration/Config.xcconfig b/sample/iosApp/Configuration/Config.xcconfig new file mode 100644 index 00000000..96b2549d --- /dev/null +++ b/sample/iosApp/Configuration/Config.xcconfig @@ -0,0 +1,3 @@ +TEAM_ID=Q86C89UZUH +BUNDLE_ID=com.shepeliev.webrtckmp.sample.WebRTCKMPPlayground +APP_NAME=WebRTC KMP Playground \ No newline at end of file diff --git a/sample/iosApp/iosApp.xcodeproj/project.pbxproj b/sample/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..1fb7b0f7 --- /dev/null +++ b/sample/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,421 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; + 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; + FA64F57B2BD3B81C008C0A82 /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = FA64F57A2BD3B81C008C0A82 /* WebRTC */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = iosApp.app; path = "WebRTC KMP Playground.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B92378962B6B1156000C7307 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FA64F57B2BD3B81C008C0A82 /* WebRTC in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 42799AB246E5F90AF97AA0EF /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + AB1DB47929225F7C00F7AF9C /* Configuration */, + 7555FF7D242A565900829871 /* iosApp */, + 7555FF7C242A565900829871 /* Products */, + 42799AB246E5F90AF97AA0EF /* Frameworks */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* iosApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 058557BA273AAA24004C7B11 /* Assets.xcassets */, + 7555FF82242A565900829871 /* ContentView.swift */, + 7555FF8C242A565B00829871 /* Info.plist */, + 2152FB032600AC8F00CF470E /* iOSApp.swift */, + 058557D7273AAEEB004C7B11 /* Preview Content */, + ); + path = iosApp; + sourceTree = ""; + }; + AB1DB47929225F7C00F7AF9C /* Configuration */ = { + isa = PBXGroup; + children = ( + AB3632DC29227652001CCB65 /* Config.xcconfig */, + ); + path = Configuration; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */, + 7555FF77242A565900829871 /* Sources */, + B92378962B6B1156000C7307 /* Frameworks */, + 7555FF79242A565900829871 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + packageProductDependencies = ( + FA64F57A2BD3B81C008C0A82 /* WebRTC */, + ); + productName = iosApp; + productReference = 7555FF7B242A565900829871 /* iosApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7555FF73242A565900829871 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = orgName; + TargetAttributes = { + 7555FF7A242A565900829871 = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7555FF72242A565900829871; + packageReferences = ( + FA64F5792BD3B81C008C0A82 /* XCRemoteSwiftPackageReference "Specs" */, + ); + productRefGroup = 7555FF7C242A565900829871 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7555FF7A242A565900829871 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7555FF79242A565900829871 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/../..\"\npwd\n./gradlew :sample:composeApp:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, + 7555FF83242A565900829871 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 15.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7555FFA4242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = 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_DOCUMENTATION_COMMENTS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 15.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7555FFA6242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + composeApp, + ); + PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; + PRODUCT_NAME = "${APP_NAME}"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + composeApp, + ); + PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; + PRODUCT_NAME = "${APP_NAME}"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA3242A565B00829871 /* Debug */, + 7555FFA4242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA6242A565B00829871 /* Debug */, + 7555FFA7242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + FA64F5792BD3B81C008C0A82 /* XCRemoteSwiftPackageReference "Specs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/webrtc-sdk/Specs"; + requirement = { + kind = exactVersion; + version = 114.5735.9; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + FA64F57A2BD3B81C008C0A82 /* WebRTC */ = { + isa = XCSwiftPackageProductDependency; + package = FA64F5792BD3B81C008C0A82 /* XCRemoteSwiftPackageReference "Specs" */; + productName = WebRTC; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; +} diff --git a/sample/app-ios/app-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/sample/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from sample/app-ios/app-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to sample/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/sample/app-ios/app-ios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from sample/app-ios/app-ios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..fdff2912 --- /dev/null +++ b/sample/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "specs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/webrtc-sdk/Specs", + "state" : { + "revision" : "fa02befe57f72c52d48d913b46ae183f6b63e042", + "version" : "114.5735.9" + } + } + ], + "version" : 2 +} diff --git a/sample/app-ios/app-ios/Assets.xcassets/AccentColor.colorset/Contents.json b/sample/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 98% rename from sample/app-ios/app-ios/Assets.xcassets/AccentColor.colorset/Contents.json rename to sample/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json index eb878970..ee7e3ca0 100644 --- a/sample/app-ios/app-ios/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/sample/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -8,4 +8,4 @@ "author" : "xcode", "version" : 1 } -} +} \ No newline at end of file diff --git a/sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..8edf56e7 --- /dev/null +++ b/sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "app-icon-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..53fc536fb9ac5c1dbb27c7e1da13db3760070a11 GIT binary patch literal 67285 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 BXNCX( literal 0 HcmV?d00001 diff --git a/sample/app-ios/app-ios/Assets.xcassets/Contents.json b/sample/iosApp/iosApp/Assets.xcassets/Contents.json similarity index 96% rename from sample/app-ios/app-ios/Assets.xcassets/Contents.json rename to sample/iosApp/iosApp/Assets.xcassets/Contents.json index 73c00596..4aa7c535 100644 --- a/sample/app-ios/app-ios/Assets.xcassets/Contents.json +++ b/sample/iosApp/iosApp/Assets.xcassets/Contents.json @@ -3,4 +3,4 @@ "author" : "xcode", "version" : 1 } -} +} \ No newline at end of file diff --git a/sample/iosApp/iosApp/ContentView.swift b/sample/iosApp/iosApp/ContentView.swift new file mode 100644 index 00000000..3cd5c325 --- /dev/null +++ b/sample/iosApp/iosApp/ContentView.swift @@ -0,0 +1,21 @@ +import UIKit +import SwiftUI +import ComposeApp + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea(.keyboard) // Compose has own keyboard handler + } +} + + + diff --git a/sample/iosApp/iosApp/Info.plist b/sample/iosApp/iosApp/Info.plist new file mode 100644 index 00000000..273e44a5 --- /dev/null +++ b/sample/iosApp/iosApp/Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSCameraUsageDescription + Access to the camera is required. + NSMicrophoneUsageDescription + Access to the microphone is required. + + diff --git a/sample/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/sample/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/sample/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sample/iosApp/iosApp/iOSApp.swift b/sample/iosApp/iosApp/iOSApp.swift new file mode 100644 index 00000000..0648e860 --- /dev/null +++ b/sample/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} \ No newline at end of file diff --git a/sample/shared/build.gradle.kts b/sample/shared/build.gradle.kts deleted file mode 100644 index b6386e33..00000000 --- a/sample/shared/build.gradle.kts +++ /dev/null @@ -1,68 +0,0 @@ -import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType - -plugins { - id("multiplatform-setup") - kotlin("native.cocoapods") -} - -kotlin { - cocoapods { - version = "1.0.0" - summary = "Shared framework for WebRTC KMP sample" - homepage = "https://github.com/shepeliev/webrtc-kmp/tree/main/sample" - ios.deploymentTarget = "11.0" - - pod("FirebaseCore") - pod("FirebaseFirestore") - pod("WebRTC-SDK") { - version = "114.5735.02" - linkOnly = true - } - - podfile = project.file("../app-ios/Podfile") - - framework { - baseName = "shared" - export(project(":webrtc-kmp")) - export(deps.decompose) - transitiveExport = true - } - - xcodeConfigurationToNativeBuildType["CUSTOM_DEBUG"] = NativeBuildType.DEBUG - xcodeConfigurationToNativeBuildType["CUSTOM_RELEASE"] = NativeBuildType.RELEASE - } - - androidTarget() - - iosX64() - iosArm64() - iosSimulatorArm64() - - js { - useCommonJs() - browser() - } - - sourceSets { - commonMain.dependencies { - api(project(":webrtc-kmp")) - api(deps.decompose) - implementation(deps.kotlin.coroutines) - implementation(deps.kermit) - } - - androidMain.dependencies { - implementation(project.dependencies.platform(deps.firebase.bom)) - implementation(deps.firebase.firestore) - implementation(deps.kotlin.coroutinesPlayServices) - } - - jsMain.dependencies { - implementation(npm("firebase", version = "9.9.1")) - } - } -} - -android { - namespace = "com.shepeliev.webrtckmp.sample.shared" -} diff --git a/sample/shared/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt b/sample/shared/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt deleted file mode 100644 index 57a2b3d8..00000000 --- a/sample/shared/src/androidMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt +++ /dev/null @@ -1,98 +0,0 @@ -@file:JvmName("AndroidRoomDataSource") - -package com.shepeliev.webrtckmp.sample.shared - -import com.google.firebase.firestore.DocumentChange -import com.google.firebase.firestore.ktx.firestore -import com.google.firebase.ktx.Firebase -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.channels.trySendBlocking -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.tasks.await -import java.util.Date -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -actual class RoomDataSource actual constructor() { - - private val firestore by lazy { Firebase.firestore } - private val roomsRef by lazy { firestore.collection("rooms") } - - actual fun createRoom(): String { - return roomsRef.document().id - } - - actual suspend fun insertOffer(roomId: String, description: SessionDescription) { - roomsRef.document(roomId).set( - mapOf( - "offer" to description.sdp, - "expireAt" to getExpireAtTime() - ) - ).await() - } - - actual suspend fun insertAnswer(roomId: String, description: SessionDescription) { - roomsRef.document(roomId).update(mapOf("answer" to description.sdp)).await() - } - - actual suspend fun insertIceCandidate(roomId: String, peerName: String, candidate: IceCandidate) { - roomsRef.document(roomId) - .collection(peerName) - .add( - mapOf( - "candidate" to candidate.candidate, - "sdpMLineIndex" to candidate.sdpMLineIndex, - "sdpMid" to candidate.sdpMid, - "expireAt" to getExpireAtTime(), - ) - ) - .await() - } - - private fun getExpireAtTime(): Date { - val expireAt = System.currentTimeMillis() + FIRESTORE_DOCUMENT_TTL_SECONDS * 1000 - return Date(expireAt) - } - - actual suspend fun getOffer(roomId: String): SessionDescription? { - val snapshot = roomsRef.document(roomId).get().await() - val offerSdp = snapshot.takeIf { it.exists() }?.getString("offer") - return offerSdp?.let { SessionDescription(SessionDescriptionType.Offer, it) } - } - - actual suspend fun getAnswer(roomId: String): SessionDescription = suspendCancellableCoroutine { cont -> - val registration = roomsRef.document(roomId).addSnapshotListener { value, error -> - val answer = value?.data?.get("answer") as? String - when { - answer != null -> cont.resume(SessionDescription(SessionDescriptionType.Answer, answer)) - error != null -> cont.resumeWithException(error) - } - } - - cont.invokeOnCancellation { registration.remove() } - } - - actual fun observeIceCandidates(roomId: String, peerName: String): Flow = callbackFlow { - val registration = roomsRef.document(roomId).collection(peerName).addSnapshotListener { value, error -> - if (error != null) { - channel.close(error) - } - - val dc = value?.documentChanges ?: return@addSnapshotListener - - dc.filter { it.type == DocumentChange.Type.ADDED } - .map { - IceCandidate( - sdpMid = it.document.data["sdpMid"] as String, - sdpMLineIndex = (it.document.data["sdpMLineIndex"] as Long).toInt(), - candidate = it.document.data["candidate"] as String, - ) - } - .forEach { trySendBlocking(it) } - } - - awaitClose { registration.remove() } - } -} diff --git a/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Room.kt b/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Room.kt deleted file mode 100644 index 6e997ad8..00000000 --- a/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Room.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.shepeliev.webrtckmp.sample.shared - -import com.arkivanov.decompose.value.Value -import com.shepeliev.webrtckmp.MediaStream - -interface Room { - - val model: Value - - fun openUserMedia() - fun switchCamera() - fun createRoom() - fun joinRoom(roomId: String) - fun hangup() - - data class Model( - val localStream: MediaStream? = null, - val remoteStream: MediaStream? = null, - val roomId: String? = null, - val isCaller: Boolean? = null, - val isJoining: Boolean = false, - ) -} diff --git a/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomComponent.kt b/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomComponent.kt deleted file mode 100644 index f4df3f56..00000000 --- a/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomComponent.kt +++ /dev/null @@ -1,249 +0,0 @@ -package com.shepeliev.webrtckmp.sample.shared - -import co.touchlab.kermit.Logger -import co.touchlab.kermit.platformLogWriter -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.value.MutableValue -import com.arkivanov.decompose.value.Value -import com.arkivanov.decompose.value.reduce -import com.arkivanov.essenty.instancekeeper.InstanceKeeper -import com.arkivanov.essenty.instancekeeper.getOrCreate -import com.arkivanov.essenty.lifecycle.doOnCreate -import com.arkivanov.essenty.lifecycle.doOnDestroy -import com.arkivanov.essenty.lifecycle.doOnPause -import com.arkivanov.essenty.lifecycle.doOnResume -import com.arkivanov.essenty.lifecycle.doOnStart -import com.arkivanov.essenty.lifecycle.doOnStop -import com.shepeliev.webrtckmp.IceServer -import com.shepeliev.webrtckmp.MediaDevices -import com.shepeliev.webrtckmp.MediaStreamTrack -import com.shepeliev.webrtckmp.MediaStreamTrackKind -import com.shepeliev.webrtckmp.MediaStreamTrackState -import com.shepeliev.webrtckmp.OfferAnswerOptions -import com.shepeliev.webrtckmp.PeerConnection -import com.shepeliev.webrtckmp.RtcConfiguration -import com.shepeliev.webrtckmp.audioTracks -import com.shepeliev.webrtckmp.onConnectionStateChange -import com.shepeliev.webrtckmp.onIceCandidate -import com.shepeliev.webrtckmp.onIceConnectionStateChange -import com.shepeliev.webrtckmp.onIceGatheringState -import com.shepeliev.webrtckmp.onSignalingStateChange -import com.shepeliev.webrtckmp.onTrack -import com.shepeliev.webrtckmp.videoTracks -import kotlinx.coroutines.Job -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlinx.coroutines.plus - -class RoomComponent( - componentContext: ComponentContext, - viewModel: Room, -) : Room by viewModel, ComponentContext by componentContext { - - constructor(componentContext: ComponentContext) : this(componentContext, componentContext.instanceKeeper.getOrCreate { ViewModel() }) - - private val logger = Logger.withTag("RoomComponent") - - init { - Logger.setLogWriters(platformLogWriter()) - - lifecycle.doOnCreate { logger.d { "onCreate" } } - lifecycle.doOnStart { logger.d { "onStart" } } - lifecycle.doOnResume { logger.d { "onResume" } } - lifecycle.doOnPause { logger.d { "onPause" } } - lifecycle.doOnStop { logger.d { "onStop" } } - lifecycle.doOnDestroy { logger.d { "onDestroy" } } - } - - private class ViewModel : InstanceKeeper.Instance, Room { - - private val logger = Logger.withTag("RoomComponent => ViewModel") - private val _model = MutableValue(Room.Model()) - override val model: Value get() = _model - - private val scope = MainScope() - - private val roomDataSource = RoomDataSource() - private var peerConnection: PeerConnection? = null - private var roomSessionJob: Job? = null - - override fun openUserMedia() { - logger.i { "Open user media" } - roomSessionJob = SupervisorJob() - - scope.launch { - try { - val stream = MediaDevices.getUserMedia(audio = true, video = true) - _model.reduce { it.copy(localStream = stream) } - listenTrackState(stream.videoTracks.first(), "Local video") - } catch (e: Throwable) { - logger.e(e) { "Getting user media failed" } - } - } - } - - override fun switchCamera() { - logger.i { "Switch camera" } - scope.launch { - model.value.localStream?.videoTracks?.first()?.switchCamera() - logger.i { "Camera switched" } - } - } - - override fun createRoom() { - logger.i { "Create room" } - - _model.reduce { it.copy(isJoining = true, isCaller = true) } - val peerConnection = createPeerConnection() - this@ViewModel.peerConnection = peerConnection - - scope.launch { - val roomId = roomDataSource.createRoom() - logger.d { "Room ID: $roomId" } - - collectIceCandidates(peerConnection, roomId, "caller", "callee") - - val offer = peerConnection.createOffer(DefaultOfferAnswerOptions).also { - peerConnection.setLocalDescription(it) - } - - roomDataSource.insertOffer(roomId, offer) - _model.reduce { it.copy(roomId = roomId, isJoining = false) } - - logger.d { "Waiting answer" } - val answer = roomDataSource.getAnswer(roomId) - logger.d { "Answer received." } - peerConnection.setRemoteDescription(answer) - } - } - - override fun joinRoom(roomId: String) { - logger.i { "Join room: $roomId" } - - _model.reduce { it.copy(isJoining = true, roomId = roomId, isCaller = false) } - roomSessionJob = SupervisorJob() - - val peerConnection = createPeerConnection() - this@ViewModel.peerConnection = peerConnection - - scope.launch { - val offer = roomDataSource.getOffer(roomId) - if (offer == null) { - logger.e { "No offer SDP in the room [id = $roomId]" } - _model.reduce { it.copy(isJoining = false, isCaller = null) } - return@launch - } - - collectIceCandidates(peerConnection, roomId, "callee", "caller") - - peerConnection.setRemoteDescription(offer) - peerConnection.createAnswer(DefaultOfferAnswerOptions).also { - peerConnection.setLocalDescription(it) - roomDataSource.insertAnswer(roomId, it) - } - - _model.reduce { it.copy(isJoining = false) } - } - } - - private fun createPeerConnection(): PeerConnection { - logger.i { "Create PeerConnection." } - val peerConnection = PeerConnection(DefaultRtcConfig) - - model.value.localStream?.let { - peerConnection.addTrack(it.audioTracks.first(), it) - peerConnection.addTrack(it.videoTracks.first(), it) - } - - listenRemoteTracks(peerConnection) - registerListeners(peerConnection) - return peerConnection - } - - private fun registerListeners(peerConnection: PeerConnection) { - peerConnection.onIceGatheringState - .onEach { logger.i { "ICE gathering state changed: $it" } } - .launchIn(scope + roomSessionJob!!) - - peerConnection.onConnectionStateChange - .onEach { logger.i { "Connection state changed: $it" } } - .launchIn(scope + roomSessionJob!!) - - peerConnection.onSignalingStateChange - .onEach { logger.i { "Signaling state changed: $it" } } - .launchIn(scope + roomSessionJob!!) - - peerConnection.onIceConnectionStateChange - .onEach { logger.i { "ICE connection state changed: $it" } } - .launchIn(scope + roomSessionJob!!) - } - - private fun listenRemoteTracks(peerConnection: PeerConnection) { - peerConnection.onTrack - .onEach { logger.i { "Remote track received: [id = ${it.track?.id}, kind: ${it.track?.kind} ]" } } - .filter { it.track?.kind == MediaStreamTrackKind.Video } - .onEach { event -> _model.reduce { it.copy(remoteStream = event.streams.first()) } } - .onEach { listenTrackState(it.track!!, "Remote video") } - .launchIn(scope + roomSessionJob!!) - } - - private fun listenTrackState(track: MediaStreamTrack, logPrefix: String) { - track.state - .filter { it is MediaStreamTrackState.Live } - .onEach { logger.w { "$logPrefix track [id = ${track.id}] state changed: $it" } } - .launchIn(scope + roomSessionJob!!) - } - - private fun collectIceCandidates( - peerConnection: PeerConnection, - roomId: String, - localName: String, - remoteName: String - ) { - peerConnection.onIceCandidate - .onEach { logger.i { "New local ICE candidate: $it" } } - .onEach { roomDataSource.insertIceCandidate(roomId, localName, it) } - .launchIn(scope + roomSessionJob!!) - - roomDataSource.observeIceCandidates(roomId, remoteName) - .catch { logger.e(it) { "Observing ice candidate failed [roomId = $roomId, peerName = $remoteName]" } } - .onEach { logger.d { "New remote ICE candidate: $it" } } - .onEach(peerConnection::addIceCandidate) - .launchIn(scope + roomSessionJob!!) - } - - override fun hangup() { - logger.i { "Hangup" } - - roomSessionJob?.cancel() - roomSessionJob = null - _model.value.localStream?.release() - _model.reduce { Room.Model() } - - peerConnection?.close() - peerConnection = null - } - - override fun onDestroy() { - logger.i { "Destroy" } - scope.cancel() - } - } -} - -private val DefaultRtcConfig = RtcConfiguration( - iceServers = listOf( - IceServer(listOf("stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302")), - ) -) - -private val DefaultOfferAnswerOptions = OfferAnswerOptions( - offerToReceiveVideo = true, - offerToReceiveAudio = true, -) diff --git a/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt b/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt deleted file mode 100644 index ea151162..00000000 --- a/sample/shared/src/commonMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.shepeliev.webrtckmp.sample.shared - -import kotlinx.coroutines.flow.Flow - -typealias SessionDescription = com.shepeliev.webrtckmp.SessionDescription -typealias IceCandidate = com.shepeliev.webrtckmp.IceCandidate -typealias SessionDescriptionType = com.shepeliev.webrtckmp.SessionDescriptionType - -expect class RoomDataSource() { - fun createRoom(): String - suspend fun insertOffer(roomId: String, description: SessionDescription) - suspend fun insertAnswer(roomId: String, description: SessionDescription) - suspend fun insertIceCandidate(roomId: String, peerName: String, candidate: IceCandidate) - suspend fun getOffer(roomId: String): SessionDescription? - suspend fun getAnswer(roomId: String): SessionDescription - fun observeIceCandidates(roomId: String, peerName: String): Flow -} - -internal const val FIRESTORE_DOCUMENT_TTL_SECONDS = 60 * 60 * 5 // 5 hours diff --git a/sample/shared/src/iosMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt b/sample/shared/src/iosMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt deleted file mode 100644 index 22601538..00000000 --- a/sample/shared/src/iosMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.shepeliev.webrtckmp.sample.shared - -import cocoapods.FirebaseCore.FIRApp -import cocoapods.FirebaseFirestore.FIRDocumentChange -import cocoapods.FirebaseFirestore.FIRDocumentChangeType -import cocoapods.FirebaseFirestore.FIRDocumentSnapshot -import cocoapods.FirebaseFirestore.FIRFirestore -import cocoapods.FirebaseFirestore.FIRQuerySnapshot -import com.shepeliev.webrtckmp.SessionDescription -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.suspendCancellableCoroutine -import platform.Foundation.NSDate -import platform.Foundation.NSError -import platform.Foundation.dateByAddingTimeInterval -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -actual class RoomDataSource actual constructor() { - - private val roomsRef by lazy { - FIRApp.configure() - FIRFirestore.firestore().collectionWithPath("rooms") - } - - actual fun createRoom(): String { - return roomsRef.documentWithAutoID().documentID - } - - actual suspend fun insertOffer(roomId: String, description: SessionDescription) = - suspendCancellableCoroutine { cont -> - roomsRef.documentWithPath(roomId) - .setData( - documentData = mapOf( - "offer" to description.sdp, - "expireAt" to getExpireAtTime() - ), - merge = true, - completion = { error -> - error - ?.let { cont.resumeWithException(Exception(it.description)) } - ?: cont.resume(Unit) - } - ) - } - - actual suspend fun insertAnswer(roomId: String, description: SessionDescription) = - suspendCancellableCoroutine { cont -> - roomsRef.documentWithPath(roomId) - .updateData( - fields = mapOf( - "answer" to description.sdp, - "expireAt" to getExpireAtTime() - ), - completion = { error -> - error - ?.let { cont.resumeWithException(Exception(it.description)) } - ?: cont.resume(Unit) - } - ) - } - - actual suspend fun insertIceCandidate(roomId: String, peerName: String, candidate: IceCandidate) = - suspendCancellableCoroutine { cont -> - roomsRef - .documentWithPath(roomId) - .collectionWithPath(peerName) - .documentWithAutoID() - .setData( - documentData = mapOf( - "candidate" to candidate.candidate, - "sdpMLineIndex" to candidate.sdpMLineIndex, - "sdpMid" to candidate.sdpMid, - "expireAt" to getExpireAtTime(), - ), - completion = { error -> - error - ?.let { cont.resumeWithException(Exception(it.description)) } - ?: cont.resume(Unit) - } - ) - } - - actual suspend fun getOffer(roomId: String): SessionDescription? = suspendCancellableCoroutine { cont -> - roomsRef.documentWithPath(roomId).getDocumentWithCompletion { snapshot, error -> - error?.let { cont.resumeWithException(Exception(it.description)) } - val offerSdp = snapshot - ?.takeIf { it.exists } - ?.data() - ?.get("offer") as? String - cont.resume(offerSdp?.let { SessionDescription(SessionDescriptionType.Offer, it) }) - } - } - - actual suspend fun getAnswer(roomId: String): SessionDescription = suspendCancellableCoroutine { cont -> - val snapshotListener = { snapshot: FIRDocumentSnapshot?, error: NSError? -> - if (cont.isActive) { - error?.let { cont.resumeWithException(Exception(it.description)) } - val answer = snapshot?.data()?.get("answer") as? String - answer?.let { cont.resume(SessionDescription(SessionDescriptionType.Answer, it)) } - } - Unit - } - - val registration = roomsRef - .documentWithPath(roomId) - .addSnapshotListener(snapshotListener) - - cont.invokeOnCancellation { registration.remove() } - } - - actual fun observeIceCandidates(roomId: String, peerName: String): Flow = callbackFlow { - val listener = { snapshot: FIRQuerySnapshot?, error: NSError? -> - error?.let { channel.close(Exception(it.description)) } - - snapshot?.documentChanges - ?.map { it as FIRDocumentChange } - ?.filter { it.type == FIRDocumentChangeType.FIRDocumentChangeTypeAdded } - ?.map { - IceCandidate( - sdpMid = it.document.data()["sdpMid"] as String, - sdpMLineIndex = (it.document.data()["sdpMLineIndex"] as Long).toInt(), - candidate = it.document.data()["candidate"] as String, - ) - } - ?.forEach { trySend(it) } - - Unit - } - - val registration = roomsRef - .documentWithPath(roomId) - .collectionWithPath(peerName) - .addSnapshotListener(listener) - - awaitClose { registration.remove() } - } - - private fun getExpireAtTime(): NSDate { - return NSDate().dateByAddingTimeInterval(FIRESTORE_DOCUMENT_TTL_SECONDS.toDouble()) - } -} diff --git a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/DocumentSnapshotObserver.kt b/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/DocumentSnapshotObserver.kt deleted file mode 100644 index cc914ebb..00000000 --- a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/DocumentSnapshotObserver.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shepeliev.webrtckmp.sample.shared - -data class DocumentSnapshotObserver( - @JsName("next") - val next: (DocumentSnapshot) -> Unit, - @JsName("error") - val error: (Throwable) -> Unit, - @JsName("complete") - val complete: () -> Unit, -) diff --git a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/FirebaseApp.kt b/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/FirebaseApp.kt deleted file mode 100644 index 6dbfed66..00000000 --- a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/FirebaseApp.kt +++ /dev/null @@ -1,10 +0,0 @@ -@file:JsModule("firebase/app") -@file:JsNonModule - -package com.shepeliev.webrtckmp.sample.shared - -import kotlin.js.Json - -external class FirebaseApp - -external fun initializeApp(config: Json): FirebaseApp diff --git a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Firestore.kt b/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Firestore.kt deleted file mode 100644 index 57c3ee11..00000000 --- a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/Firestore.kt +++ /dev/null @@ -1,52 +0,0 @@ -@file:JsModule("firebase/firestore") -@file:JsNonModule - -package com.shepeliev.webrtckmp.sample.shared - -import kotlin.js.Json -import kotlin.js.Promise - -external class Firestore - -external interface Query - -external interface CollectionReference : Query - -external interface DocumentReference { - val id: String -} - -external interface DocumentSnapshot { - val id: String - fun data(): Json - fun exists(): Boolean -} - -external interface DocumentChange { - val doc: DocumentSnapshot - val type: String -} - -external interface QuerySnapshot { - fun docChanges(): Array -} - -external fun getFirestore(app: FirebaseApp): Firestore - -external fun collection(firestore: Firestore, path: String): CollectionReference - -external fun collection(reference: CollectionReference, path: String): CollectionReference - -external fun doc(reference: CollectionReference, path: String? = definedExternally): DocumentReference - -external fun addDoc(reference: CollectionReference, data: Json): Promise - -external fun setDoc(reference: DocumentReference, data: Json): Promise - -external fun updateDoc(reference: DocumentReference, data: Json): Promise - -external fun getDoc(reference: DocumentReference): Promise - -external fun onSnapshot(query: Query, observer: QuerySnapshotObserver): () -> Unit - -external fun onSnapshot(reference: DocumentReference, observer: DocumentSnapshotObserver): () -> Unit diff --git a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/QuerySnapshotObserver.kt b/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/QuerySnapshotObserver.kt deleted file mode 100644 index d9c6e447..00000000 --- a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/QuerySnapshotObserver.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.shepeliev.webrtckmp.sample.shared - -data class QuerySnapshotObserver( - @JsName("next") - val next: (QuerySnapshot) -> Unit, - @JsName("error") - val error: (Throwable) -> Unit, - @JsName("complete") - val complete: () -> Unit, -) diff --git a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt b/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt deleted file mode 100644 index 760ca30a..00000000 --- a/sample/shared/src/jsMain/kotlin/com/shepeliev/webrtckmp/sample/shared/RoomDataSource.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.shepeliev.webrtckmp.sample.shared - -import kotlinx.coroutines.await -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.js.Date -import kotlin.js.json - -actual class RoomDataSource actual constructor() { - - private val firestore by lazy { - initializeApp( - json( - "applicationId" to "1:216132728347:web:f10a385863ec2d43872abe", - "apiKey" to "AIzaSyDa0FDyeGNZZcKKBXnALeJSqfUxSNKut4w", - "authDomain" to "app-rtc-kmp.firebaseapp.com", - "projectId" to "app-rtc-kmp", - "storageBucket" to "app-rtc-kmp.appspot.com", - "gcmSenderId" to "216132728347", - ) - ).let { getFirestore(it) } - } - - private val roomsRef by lazy { collection(firestore, "rooms") } - - actual fun createRoom(): String { - return doc(roomsRef).id - } - - actual suspend fun insertOffer(roomId: String, description: SessionDescription) { - val docRef = doc(roomsRef, roomId) - setDoc( - docRef, - json( - "offer" to description.sdp, - "expireAt" to getExpireAtTime() - ) - ).await() - } - - actual suspend fun insertAnswer(roomId: String, description: SessionDescription) { - val docRef = doc(roomsRef, roomId) - updateDoc( - docRef, - json( - "answer" to description.sdp, - "expireAt" to getExpireAtTime() - ) - ).await() - } - - actual suspend fun insertIceCandidate(roomId: String, peerName: String, candidate: IceCandidate) { - val colRef = collection(roomsRef, "$roomId/$peerName") - addDoc( - colRef, - json( - "candidate" to candidate.candidate, - "sdpMLineIndex" to candidate.sdpMLineIndex, - "sdpMid" to candidate.sdpMid, - "expireAt" to getExpireAtTime(), - ) - ).await() - } - - private fun getExpireAtTime(): Date { - val expireAt = Date().getTime() + FIRESTORE_DOCUMENT_TTL_SECONDS * 1000 - return Date(expireAt) - } - - actual suspend fun getOffer(roomId: String): SessionDescription? { - val snapshot = getDoc(doc(roomsRef, roomId)).await() - val offerSdp = snapshot.takeIf { it.exists() }?.data()?.get("offer") as? String - return offerSdp?.let { SessionDescription(SessionDescriptionType.Offer, it) } - } - - actual suspend fun getAnswer(roomId: String): SessionDescription = suspendCancellableCoroutine { cont -> - val observer = DocumentSnapshotObserver( - next = { snapshot -> - val answer = snapshot.data()["answer"] as? String - answer?.let { cont.resume(SessionDescription(SessionDescriptionType.Answer, it)) } - }, - - error = cont::resumeWithException, - - complete = cont::cancel - ) - val unsubscribe = onSnapshot(doc(roomsRef, roomId), observer) - cont.invokeOnCancellation { unsubscribe() } - } - - actual fun observeIceCandidates(roomId: String, peerName: String): Flow = callbackFlow { - val observer = QuerySnapshotObserver( - next = { querySnapshot -> - - querySnapshot.docChanges() - .filter { it.type == "added" } - .map { - IceCandidate( - sdpMid = it.doc.data()["sdpMid"] as String, - sdpMLineIndex = it.doc.data()["sdpMLineIndex"] as Int, - candidate = it.doc.data()["candidate"] as String, - ) - } - .forEach { trySend(it) } - }, - - error = channel::close, - - complete = channel::close - ) - - val unsubscribe = onSnapshot(collection(roomsRef, "$roomId/$peerName"), observer) - - awaitClose { unsubscribe() } - } -} diff --git a/sample/shared/src/nativeInterop/cinterop/FirebaseCore.def b/sample/shared/src/nativeInterop/cinterop/FirebaseCore.def deleted file mode 100644 index 1559a5f6..00000000 --- a/sample/shared/src/nativeInterop/cinterop/FirebaseCore.def +++ /dev/null @@ -1,3 +0,0 @@ -language = Objective-C -modules = FirebaseCore -package = FirebaseCore diff --git a/sample/shared/src/nativeInterop/cinterop/FirebaseFirestore.def b/sample/shared/src/nativeInterop/cinterop/FirebaseFirestore.def deleted file mode 100644 index 2a978f45..00000000 --- a/sample/shared/src/nativeInterop/cinterop/FirebaseFirestore.def +++ /dev/null @@ -1,3 +0,0 @@ -language = Objective-C -modules = FirebaseFirestore -package = FirebaseFirestore diff --git a/settings.gradle.kts b/settings.gradle.kts index 8bf0b79b..d8fa1376 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,11 +1,5 @@ @Suppress("UnstableApiUsage") dependencyResolutionManagement { - versionCatalogs { - create("deps") { - from(files("libs.versions.toml")) - } - } - repositories { mavenLocal() mavenCentral() @@ -21,7 +15,6 @@ pluginManagement { } } +rootProject.name = "webrtc-kmp" include(":webrtc-kmp") -include(":sample:shared") -include(":sample:app-android") -include(":sample:app-web") +include(":sample:composeApp") diff --git a/webrtc-kmp/build.gradle.kts b/webrtc-kmp/build.gradle.kts index cfbd4add..ec8da3ea 100644 --- a/webrtc-kmp/build.gradle.kts +++ b/webrtc-kmp/build.gradle.kts @@ -1,5 +1,7 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + plugins { - id("multiplatform-setup") + id("webrtc.multiplatform") kotlin("native.cocoapods") id("maven-publish") id("signing") @@ -28,9 +30,9 @@ kotlin { publishAllLibraryVariants() } - iosX64 { configureIos() } - iosArm64 { configureIos() } - iosSimulatorArm64 { configureIos() } + iosX64 { configureWebRtcCinterops() } + iosArm64 { configureWebRtcCinterops() } + iosSimulatorArm64 { configureWebRtcCinterops() } js { useCommonJs() @@ -39,13 +41,13 @@ kotlin { sourceSets { commonMain.dependencies { - implementation(deps.kotlin.coroutines) + implementation(libs.kotlin.coroutines) } androidMain.dependencies { - implementation(deps.kotlin.coroutines) - implementation(deps.androidx.coreKtx) - api(deps.webrtcSdk) + implementation(libs.kotlin.coroutines.android) + implementation(libs.androidx.coreKtx) + api(libs.webrtc.sdk) } jsMain.dependencies { @@ -67,8 +69,8 @@ android { } dependencies { - androidTestImplementation(deps.androidx.test.core) - androidTestImplementation(deps.androidx.test.runner) + androidTestImplementation(libs.androidx.test.core) + androidTestImplementation(libs.androidx.test.runner) } } @@ -113,14 +115,14 @@ publishing { } signing { - val signingKey: String by extra - val signingPassword: String by extra + val signingKey: String by rootProject.extra + val signingPassword: String by rootProject.extra useInMemoryPgpKeys(signingKey, signingPassword) sign(publishing.publications) } -fun org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget.configureIos() { +fun KotlinNativeTarget.configureWebRtcCinterops() { val webRtcFrameworkPath = file("$buildDir/cocoapods/synthetic/IOS/Pods/WebRTC-SDK") .resolveArchPath(konanTarget, "WebRTC") compilations.getByName("main") { diff --git a/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/ApplicationContextHolder.kt b/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/ApplicationContextHolder.kt index f5deaaeb..d0540431 100644 --- a/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/ApplicationContextHolder.kt +++ b/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/ApplicationContextHolder.kt @@ -6,11 +6,13 @@ import android.content.ContentValues import android.content.Context import android.database.Cursor import android.net.Uri +import org.webrtc.PeerConnectionFactory internal class ApplicationContextHolder : ContentProvider() { override fun onCreate(): Boolean { ApplicationContextHolder.context = checkNotNull(context) + WebRtc.factoryInitializationOptionsBuilder = PeerConnectionFactory.InitializationOptions.builder(context) return true } diff --git a/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt b/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt index 6e79af5c..8e30b5c7 100644 --- a/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt +++ b/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt @@ -11,12 +11,20 @@ import com.shepeliev.webrtckmp.PeerConnectionEvent.RemovedIceCandidates import com.shepeliev.webrtckmp.PeerConnectionEvent.SignalingStateChange import com.shepeliev.webrtckmp.PeerConnectionEvent.StandardizedIceConnectionChange import com.shepeliev.webrtckmp.PeerConnectionEvent.Track +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import org.webrtc.AudioTrack import org.webrtc.CandidatePairChangeEvent import org.webrtc.MediaConstraints +import org.webrtc.MediaStreamTrack.AUDIO_TRACK_KIND +import org.webrtc.MediaStreamTrack.VIDEO_TRACK_KIND import org.webrtc.SdpObserver +import org.webrtc.VideoTrack import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -57,6 +65,7 @@ actual class PeerConnection actual constructor(rtcConfiguration: RtcConfiguratio MutableSharedFlow(extraBufferCapacity = FLOW_BUFFER_CAPACITY) internal actual val peerConnectionEvent: Flow = _peerConnectionEvent.asSharedFlow() + private val coroutineScope = CoroutineScope(Dispatchers.Main) private val localTracks = mutableMapOf() private val remoteTracks = mutableMapOf() @@ -209,37 +218,38 @@ actual class PeerConnection actual constructor(rtcConfiguration: RtcConfiguratio remoteTracks.values.forEach(MediaStreamTrack::stop) remoteTracks.clear() android.dispose() + coroutineScope.cancel() } internal inner class AndroidPeerConnectionObserver : AndroidPeerConnection.Observer { override fun onSignalingChange(newState: AndroidPeerConnection.SignalingState) { - _peerConnectionEvent.tryEmit(SignalingStateChange(newState.asCommon())) + coroutineScope.launch { _peerConnectionEvent.emit(SignalingStateChange(newState.asCommon())) } } override fun onIceConnectionChange(newState: AndroidPeerConnection.IceConnectionState) { - _peerConnectionEvent.tryEmit(IceConnectionStateChange(newState.asCommon())) + coroutineScope.launch { _peerConnectionEvent.emit(IceConnectionStateChange(newState.asCommon())) } } override fun onStandardizedIceConnectionChange(newState: AndroidPeerConnection.IceConnectionState) { - _peerConnectionEvent.tryEmit(StandardizedIceConnectionChange(newState.asCommon())) + coroutineScope.launch { _peerConnectionEvent.emit(StandardizedIceConnectionChange(newState.asCommon())) } } override fun onConnectionChange(newState: AndroidPeerConnection.PeerConnectionState) { - _peerConnectionEvent.tryEmit(ConnectionStateChange(newState.asCommon())) + coroutineScope.launch { _peerConnectionEvent.emit(ConnectionStateChange(newState.asCommon())) } } override fun onIceConnectionReceivingChange(receiving: Boolean) {} override fun onIceGatheringChange(newState: AndroidPeerConnection.IceGatheringState) { - _peerConnectionEvent.tryEmit(IceGatheringStateChange(newState.asCommon())) + coroutineScope.launch { _peerConnectionEvent.emit(IceGatheringStateChange(newState.asCommon())) } } override fun onIceCandidate(candidate: AndroidIceCandidate) { - _peerConnectionEvent.tryEmit(NewIceCandidate(IceCandidate(candidate))) + coroutineScope.launch { _peerConnectionEvent.emit(NewIceCandidate(IceCandidate(candidate))) } } override fun onIceCandidatesRemoved(candidates: Array) { - _peerConnectionEvent.tryEmit(RemovedIceCandidates(candidates.map { IceCandidate(it) })) + coroutineScope.launch { _peerConnectionEvent.emit(RemovedIceCandidates(candidates.map { IceCandidate(it) })) } } override fun onAddStream(nativeStream: AndroidMediaStream) { @@ -257,11 +267,11 @@ actual class PeerConnection actual constructor(rtcConfiguration: RtcConfiguratio } override fun onDataChannel(dataChannel: AndroidDataChannel) { - _peerConnectionEvent.tryEmit(NewDataChannel(DataChannel(dataChannel))) + coroutineScope.launch { _peerConnectionEvent.emit(NewDataChannel(DataChannel(dataChannel))) } } override fun onRenegotiationNeeded() { - _peerConnectionEvent.tryEmit(NegotiationNeeded) + coroutineScope.launch { _peerConnectionEvent.emit(NegotiationNeeded) } } override fun onAddTrack( @@ -269,28 +279,28 @@ actual class PeerConnection actual constructor(rtcConfiguration: RtcConfiguratio androidStreams: Array ) { val transceiver = android.transceivers.find { it.receiver.id() == receiver.id() } ?: return + val senderTrack = localTracks[transceiver.sender.track()?.id()] - val audioTracks = androidStreams - .flatMap { it.audioTracks } - .map { remoteTracks.getOrPut(it.id()) { RemoteAudioStreamTrack(it) } } - - val videoTracks = androidStreams - .flatMap { it.videoTracks } - .map { remoteTracks.getOrPut(it.id()) { RemoteVideoStreamTrack(it) } } + val receiverTrack = receiver.track()?.let { + remoteTracks.getOrPut(it.id()) { + when (it.kind()) { + AUDIO_TRACK_KIND -> RemoteAudioStreamTrack(it as AudioTrack) + VIDEO_TRACK_KIND -> RemoteVideoStreamTrack(it as VideoTrack) + else -> error("Unsupported track kind: ${it.kind()}") + } + } + } val streams = androidStreams.map { androidStream -> MediaStream( android = androidStream, id = androidStream.id, ).also { stream -> - audioTracks.forEach(stream::addTrack) - videoTracks.forEach(stream::addTrack) + androidStream.audioTracks.forEach { stream.addTrack(RemoteAudioStreamTrack(it)) } + androidStream.videoTracks.forEach { stream.addTrack(RemoteVideoStreamTrack(it)) } } } - val senderTrack = localTracks[transceiver.sender.track()?.id()] - val receiverTrack = remoteTracks[receiver.track()?.id()] - val trackEvent = TrackEvent( receiver = RtpReceiver(receiver, receiverTrack), streams = streams, @@ -298,12 +308,12 @@ actual class PeerConnection actual constructor(rtcConfiguration: RtcConfiguratio transceiver = RtpTransceiver(transceiver, senderTrack, receiverTrack) ) - _peerConnectionEvent.tryEmit(Track(trackEvent)) + coroutineScope.launch { _peerConnectionEvent.emit(Track(trackEvent)) } } override fun onRemoveTrack(receiver: AndroidRtpReceiver) { val track = remoteTracks.remove(receiver.track()?.id()) - _peerConnectionEvent.tryEmit(RemoveTrack(RtpReceiver(receiver, track))) + coroutineScope.launch { _peerConnectionEvent.emit(RemoveTrack(RtpReceiver(receiver, track))) } track?.stop() } diff --git a/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt b/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt index 2acbc892..d21263dd 100644 --- a/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt +++ b/webrtc-kmp/src/androidMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt @@ -8,42 +8,22 @@ import org.webrtc.CameraEnumerator import org.webrtc.DefaultVideoDecoderFactory import org.webrtc.DefaultVideoEncoderFactory import org.webrtc.EglBase -import org.webrtc.Logging import org.webrtc.PeerConnectionFactory +import org.webrtc.VideoDecoderFactory +import org.webrtc.VideoEncoderFactory +@Suppress("MemberVisibilityCanBePrivate") object WebRtc { - private const val TAG = "WebRtcKmp" - - var peerConnectionFactoryInitOptions: PeerConnectionFactory.InitializationOptions? = null - set(value) { - field = value - if (_eglBase != null) { - Logging.e( - TAG, - "Peer connection factory is already initialized. Setting " + - "peerConnectionFactoryInitOptions after initialization has no effect." - ) - } - } - - var peerConnectionFactoryBuilder: PeerConnectionFactory.Builder? = null - set(value) { - field = value - if (_peerConnectionFactory != null) { - Logging.e( - TAG, - "Peer connection factory is already initialized. Setting " + - "peerConnectionFactoryBuilder after initialization has no effect." - ) - } - } - - private var _eglBase: EglBase? = null - val rootEglBase: EglBase - get() { - if (_eglBase == null) initialize() - return checkNotNull(_eglBase) - } + var videoEncoderFactory: VideoEncoderFactory? = null + var videoDecoderFactory: VideoDecoderFactory? = null + var customPeerConnectionFactory: PeerConnectionFactory? = null + lateinit var factoryInitializationOptionsBuilder: PeerConnectionFactory.InitializationOptions.Builder + internal set + + private var _rootEglBase: EglBase? = null + val rootEglBase: EglBase by lazy { + _rootEglBase ?: EglBase.create().also { _rootEglBase = it } + } var cameraEnumerator: CameraEnumerator = if (Camera2Enumerator.isSupported(ApplicationContextHolder.context)) { @@ -52,54 +32,25 @@ object WebRtc { Camera1Enumerator() } - private var _peerConnectionFactory: PeerConnectionFactory? = null - internal val peerConnectionFactory: PeerConnectionFactory - get() { - if (_peerConnectionFactory == null) initialize() - return checkNotNull(_peerConnectionFactory) - } - - private fun initialize() { - check(_eglBase == null) { "Peer connection factory is already initialized." } - _eglBase = EglBase.create() - initializePeerConnectionFactory() - val builder = peerConnectionFactoryBuilder ?: getDefaultPeerConnectionBuilder() - _peerConnectionFactory = builder.createPeerConnectionFactory() - } - - private fun initializePeerConnectionFactory() { - val initOptions = peerConnectionFactoryInitOptions - ?: getDefaultPeerConnectionFactoryInitOptions() - PeerConnectionFactory.initialize(initOptions) - } + internal val peerConnectionFactory: PeerConnectionFactory by lazy { + customPeerConnectionFactory ?: run { + PeerConnectionFactory.initialize(factoryInitializationOptionsBuilder.createInitializationOptions()) - private fun getDefaultPeerConnectionFactoryInitOptions() = - PeerConnectionFactory.InitializationOptions - .builder(ApplicationContextHolder.context) - .createInitializationOptions() + val videoEncoderFactory = videoEncoderFactory + ?: DefaultVideoEncoderFactory(rootEglBase.eglBaseContext, true, true) + val videoDecoderFactory = videoDecoderFactory ?: DefaultVideoDecoderFactory(rootEglBase.eglBaseContext) - private fun getDefaultPeerConnectionBuilder(): PeerConnectionFactory.Builder { - val videoEncoderFactory = DefaultVideoEncoderFactory( - rootEglBase.eglBaseContext, - true, - false - ) + val factoryBuilder = PeerConnectionFactory.builder() + .setVideoEncoderFactory(videoEncoderFactory) + .setVideoDecoderFactory(videoDecoderFactory) - val videoDecoderFactory = DefaultVideoDecoderFactory(rootEglBase.eglBaseContext) - return PeerConnectionFactory.builder() - .setVideoEncoderFactory(videoEncoderFactory) - .setVideoDecoderFactory(videoDecoderFactory) + factoryBuilder.createPeerConnectionFactory() + } } - fun disposePeerConnectionFactory() { - if (_peerConnectionFactory == null) return - - _eglBase?.release() - _eglBase = null - - _peerConnectionFactory?.dispose() - _peerConnectionFactory = null - - PeerConnectionFactory.shutdownInternalTracer() + @Suppress("unused") + fun setRootEglBase(eglBase: EglBase) { + check(_rootEglBase == null) { "Root EglBase is already set" } + _rootEglBase = eglBase } } diff --git a/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt b/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt index 5aafec55..1186dc4b 100644 --- a/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt +++ b/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/PeerConnection.kt @@ -20,6 +20,8 @@ import WebRTC.RTCSessionDescription import WebRTC.RTCSignalingState import WebRTC.RTCVideoTrack import WebRTC.dataChannelForLabel +import WebRTC.kRTCMediaStreamTrackKindAudio +import WebRTC.kRTCMediaStreamTrackKindVideo import com.shepeliev.webrtckmp.PeerConnectionEvent.ConnectionStateChange import com.shepeliev.webrtckmp.PeerConnectionEvent.IceConnectionStateChange import com.shepeliev.webrtckmp.PeerConnectionEvent.IceGatheringStateChange @@ -32,9 +34,13 @@ import com.shepeliev.webrtckmp.PeerConnectionEvent.SignalingStateChange import com.shepeliev.webrtckmp.PeerConnectionEvent.StandardizedIceConnectionChange import com.shepeliev.webrtckmp.PeerConnectionEvent.Track import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch import platform.darwin.NSObject actual class PeerConnection actual constructor( @@ -69,6 +75,7 @@ actual class PeerConnection actual constructor( MutableSharedFlow(extraBufferCapacity = FLOW_BUFFER_CAPACITY) internal actual val peerConnectionEvent: Flow = _peerConnectionEvent.asSharedFlow() + private val coroutineScope = CoroutineScope(Dispatchers.Main) private val localTracks = mutableMapOf() private val remoteTracks = mutableMapOf() @@ -180,11 +187,12 @@ actual class PeerConnection actual constructor( remoteTracks.values.forEach(MediaStreamTrack::stop) remoteTracks.clear() ios.close() + coroutineScope.cancel() } override fun peerConnection(peerConnection: RTCPeerConnection, didChangeSignalingState: RTCSignalingState) { val event = SignalingStateChange(rtcSignalingStateAsCommon(didChangeSignalingState)) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } @Suppress("CONFLICTING_OVERLOADS", "PARAMETER_NAME_CHANGED_ON_OVERRIDE") @@ -204,34 +212,34 @@ actual class PeerConnection actual constructor( } override fun peerConnectionShouldNegotiate(peerConnection: RTCPeerConnection) { - _peerConnectionEvent.tryEmit(NegotiationNeeded) + coroutineScope.launch { _peerConnectionEvent.emit(NegotiationNeeded) } } @Suppress("CONFLICTING_OVERLOADS", "PARAMETER_NAME_CHANGED_ON_OVERRIDE") override fun peerConnection(peerConnection: RTCPeerConnection, didChangeIceConnectionState: RTCIceConnectionState) { val event = IceConnectionStateChange(rtcIceConnectionStateAsCommon(didChangeIceConnectionState)) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } override fun peerConnection(peerConnection: RTCPeerConnection, didChangeIceGatheringState: RTCIceGatheringState) { val event = IceGatheringStateChange(rtcIceGatheringStateAsCommon(didChangeIceGatheringState)) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } override fun peerConnection(peerConnection: RTCPeerConnection, didGenerateIceCandidate: RTCIceCandidate) { val event = NewIceCandidate(IceCandidate(didGenerateIceCandidate)) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } override fun peerConnection(peerConnection: RTCPeerConnection, didRemoveIceCandidates: List<*>) { val candidates = didRemoveIceCandidates.map { IceCandidate(it as RTCIceCandidate) } val event = RemovedIceCandidates(candidates) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } override fun peerConnection(peerConnection: RTCPeerConnection, didOpenDataChannel: RTCDataChannel) { val event = NewDataChannel(DataChannel(didOpenDataChannel)) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } @Suppress("CONFLICTING_OVERLOADS", "PARAMETER_NAME_CHANGED_ON_OVERRIDE") @@ -242,12 +250,12 @@ actual class PeerConnection actual constructor( val event = StandardizedIceConnectionChange( rtcIceConnectionStateAsCommon(didChangeStandardizedIceConnectionState) ) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } override fun peerConnection(peerConnection: RTCPeerConnection, didChangeConnectionState: RTCPeerConnectionState) { val event = ConnectionStateChange(rtcPeerConnectionStateAsCommon(didChangeConnectionState)) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } override fun peerConnection(peerConnection: RTCPeerConnection, didAddReceiver: RTCRtpReceiver, streams: List<*>) { @@ -256,28 +264,27 @@ actual class PeerConnection actual constructor( .find { it.receiver.receiverId == didAddReceiver.receiverId } ?: return - val iosStreams = streams.map { it as RTCMediaStream } + val senderTrack = localTracks[transceiver.sender.track?.trackId] - val audioTracks = iosStreams - .flatMap { it.audioTracks } - .map { it as RTCAudioTrack } - .map { remoteTracks.getOrPut(it.trackId) { RemoteAudioStreamTrack(it) } } + val receiverTrack = didAddReceiver.track()?.let { + remoteTracks.getOrPut(it.trackId) { + when (val kind = it.kind()) { + kRTCMediaStreamTrackKindAudio -> RemoteAudioStreamTrack(it as RTCAudioTrack) + kRTCMediaStreamTrackKindVideo -> RemoteVideoStreamTrack(it as RTCVideoTrack) + else -> error("Unsupported track kind: $kind") + } + } + } - val videoTracks = iosStreams - .flatMap { it.videoTracks } - .map { it as RTCVideoTrack } - .map { remoteTracks.getOrPut(it.trackId) { RemoteVideoStreamTrack(it) } } + val iosStreams = streams.map { it as RTCMediaStream } val commonStreams = iosStreams.map { iosStream -> MediaStream(ios = iosStream, id = iosStream.streamId).also { stream -> - audioTracks.forEach(stream::addTrack) - videoTracks.forEach(stream::addTrack) + iosStream.audioTracks.forEach { stream.addTrack(RemoteAudioStreamTrack(it as RTCAudioTrack)) } + iosStream.videoTracks.forEach { stream.addTrack(RemoteVideoStreamTrack(it as RTCVideoTrack)) } } } - val receiverTrack = remoteTracks[didAddReceiver.track?.trackId] - val senderTrack = localTracks[transceiver.sender.track?.trackId] - val trackEvent = TrackEvent( receiver = RtpReceiver(didAddReceiver, receiverTrack), streams = commonStreams, @@ -286,13 +293,13 @@ actual class PeerConnection actual constructor( ) val event = Track(trackEvent) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } } override fun peerConnection(peerConnection: RTCPeerConnection, didRemoveReceiver: RTCRtpReceiver) { val track = remoteTracks.remove(didRemoveReceiver.track?.trackId) val event = RemoveTrack(RtpReceiver(didRemoveReceiver, track)) - _peerConnectionEvent.tryEmit(event) + coroutineScope.launch { _peerConnectionEvent.emit(event) } track?.stop() } } diff --git a/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt b/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt index 5334c077..3e3c31a0 100644 --- a/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt +++ b/webrtc-kmp/src/iosMain/kotlin/com/shepeliev/webrtckmp/WebRtc.kt @@ -2,75 +2,30 @@ package com.shepeliev.webrtckmp -import WebRTC.RTCCleanupSSL import WebRTC.RTCDefaultVideoDecoderFactory import WebRTC.RTCDefaultVideoEncoderFactory import WebRTC.RTCInitializeSSL -import WebRTC.RTCLogEx -import WebRTC.RTCLoggingSeverity import WebRTC.RTCPeerConnectionFactory import WebRTC.RTCPeerConnectionFactoryOptions -import WebRTC.RTCShutdownInternalTracer import WebRTC.RTCVideoDecoderFactoryProtocol import WebRTC.RTCVideoEncoderFactoryProtocol import kotlinx.cinterop.ExperimentalForeignApi object WebRtc { var videoEncoderFactory: RTCVideoEncoderFactoryProtocol? = null - set(value) { - field = value - if (_peerConnectionFactory != null) { - RTCLogEx( - RTCLoggingSeverity.RTCLoggingSeverityError, - "Peer connection factory is already initialized. " + - "Setting video encoder factory after initialization has no effect." - ) - } - } - var videoDecoderFactory: RTCVideoDecoderFactoryProtocol? = null - set(value) { - field = value - if (_peerConnectionFactory != null) { - RTCLogEx( - RTCLoggingSeverity.RTCLoggingSeverityError, - "Peer connection factory is already initialized. " + - "Setting video decoder factory after initialization has no effect." - ) - } - } - var peerConnectionFactoryOptions: RTCPeerConnectionFactoryOptions? = null - set(value) { - field = value - if (_peerConnectionFactory != null) { - RTCLogEx( - RTCLoggingSeverity.RTCLoggingSeverityError, - "Peer connection factory is already initialized. " + - "Setting peer connection factory options after initialization has no effect." - ) + var customPeerConnectionFactory: RTCPeerConnectionFactory? = null + + internal val peerConnectionFactory: RTCPeerConnectionFactory by lazy { + customPeerConnectionFactory ?: run { + RTCInitializeSSL() + RTCPeerConnectionFactory( + videoEncoderFactory ?: RTCDefaultVideoEncoderFactory(), + videoDecoderFactory ?: RTCDefaultVideoDecoderFactory() + ).apply { + peerConnectionFactoryOptions?.let { setOptions(it) } } } - - private var _peerConnectionFactory: RTCPeerConnectionFactory? = null - internal val peerConnectionFactory: RTCPeerConnectionFactory - get() { - if (_peerConnectionFactory == null) initialize() - return checkNotNull(_peerConnectionFactory) - } - - private fun initialize() { - RTCInitializeSSL() - _peerConnectionFactory = RTCPeerConnectionFactory( - videoEncoderFactory ?: RTCDefaultVideoEncoderFactory(), - videoDecoderFactory ?: RTCDefaultVideoDecoderFactory() - ).apply { - peerConnectionFactoryOptions?.let { setOptions(it) } - } - } - - fun disposePeerConnectionFactory() { - RTCShutdownInternalTracer() - RTCCleanupSSL() } } diff --git a/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/MediaStreamTrackImpl.kt b/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/MediaStreamTrackImpl.kt index 246fbfa5..50582349 100644 --- a/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/MediaStreamTrackImpl.kt +++ b/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/MediaStreamTrackImpl.kt @@ -9,7 +9,7 @@ import org.w3c.dom.mediacapture.LIVE import org.w3c.dom.mediacapture.MediaStreamTrack as JsMediaStreamTrack import org.w3c.dom.mediacapture.MediaStreamTrackState as JsMediaStreamTrackState -internal abstract class MediaStreamTrackImpl(val js: JsMediaStreamTrack) : MediaStreamTrack { +abstract class MediaStreamTrackImpl(val js: JsMediaStreamTrack) : MediaStreamTrack { override val id: String get() = js.id diff --git a/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/VideoStreamTrack.kt b/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/VideoStreamTrack.kt index 9cc4ff6f..6b3d364d 100644 --- a/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/VideoStreamTrack.kt +++ b/webrtc-kmp/src/jsMain/kotlin/com/shepeliev/webrtckmp/VideoStreamTrack.kt @@ -1,5 +1,9 @@ package com.shepeliev.webrtckmp +import org.w3c.dom.mediacapture.MediaStreamTrack as DomMediaStreamTrack + + actual interface VideoStreamTrack : MediaStreamTrack { + val js: DomMediaStreamTrack actual suspend fun switchCamera(deviceId: String?) }