From 3c9f64443187342fbdb6caadb0a0d6fe519c4a7b Mon Sep 17 00:00:00 2001 From: osakila Date: Wed, 5 Mar 2025 12:51:31 +0900 Subject: [PATCH 01/23] Fix build new react native --- react-native/android/build.gradle | 1 + react-native/android/src/main/AndroidManifest.xml | 4 +--- .../ricoh360/thetaclientreactnative/Converter.kt | 5 +++-- .../thetaclientreactnative/ThetaClientSdkModule.kt | 14 +++++++++----- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 9add1ed654d..13de3f03d0a 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -34,6 +34,7 @@ def getExtOrIntegerDefault(name) { } android { + namespace = "com.ricoh360.thetaclientreactnative" compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') defaultConfig { diff --git a/react-native/android/src/main/AndroidManifest.xml b/react-native/android/src/main/AndroidManifest.xml index a068fe92ab8..a2f47b6057d 100644 --- a/react-native/android/src/main/AndroidManifest.xml +++ b/react-native/android/src/main/AndroidManifest.xml @@ -1,4 +1,2 @@ - - + diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt index aecc356f3ca..e4fd8b0ed89 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt @@ -307,8 +307,9 @@ fun setContinuousCaptureBuilderParams(optionMap: ReadableMap, builder: Continuou fun toGetOptionsParam(optionNames: ReadableArray): MutableList { val optionNameList = mutableListOf() for (index in 0..(optionNames.size() - 1)) { - val option = optionNames.getString(index) - optionNameList.add(OptionNameEnum.valueOf(option)) + optionNames.getString(index)?.let { + optionNameList.add(OptionNameEnum.valueOf(it)) + } } return optionNameList } diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt index d797bd3fc5a..b70ec5c1c14 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt @@ -302,7 +302,9 @@ class ThetaClientReactNativeModule( } val fileList = mutableListOf() for (index in 0..(fileUrls.size() - 1)) { - fileList.add(fileUrls.getString(index)) + fileUrls.getString(index)?.let { + fileList.add(it) + } } launch { try { @@ -1889,8 +1891,9 @@ class ThetaClientReactNativeModule( try { val optionNameList = mutableListOf() for (index in 0..(optionNames.size() - 1)) { - val option = optionNames.getString(index) - optionNameList.add(ThetaRepository.OptionNameEnum.valueOf(option)) + optionNames.getString(index)?.let { + optionNameList.add(ThetaRepository.OptionNameEnum.valueOf(it)) + } } val options = theta.getMySetting(optionNameList) promise.resolve(toResult(options = options)) @@ -2109,8 +2112,9 @@ class ThetaClientReactNativeModule( try { val pluginList = mutableListOf() for (index in 0..(plugins.size() - 1)) { - val plugin = plugins.getString(index) - pluginList.add(plugin) + plugins.getString(index)?.let { + pluginList.add(it) + } } theta.setPluginOrders(pluginList) promise.resolve(true) From cae674a068f154949d88864e87b6466b13e280c2 Mon Sep 17 00:00:00 2001 From: osakila Date: Wed, 5 Mar 2025 12:51:53 +0900 Subject: [PATCH 02/23] Update Flutter * Update Flutter * Support old version * Delete unnecessary definitions * Update github actions to flutter --- .github/workflows/test-flutter.yaml | 6 +- demos/demo-flutter/.gitignore | 2 + demos/demo-flutter/android/app/build.gradle | 72 --------------- .../demo-flutter/android/app/build.gradle.kts | 44 ++++++++++ demos/demo-flutter/android/build.gradle | 31 ------- demos/demo-flutter/android/build.gradle.kts | 21 +++++ demos/demo-flutter/android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- demos/demo-flutter/android/settings.gradle | 11 --- .../demo-flutter/android/settings.gradle.kts | 25 ++++++ demos/demo-flutter/ios/Podfile | 2 +- demos/demo-flutter/ios/Podfile.lock | 6 +- demos/demo-flutter/pubspec.lock | 88 +++++++++---------- flutter/android/build.gradle | 52 ++++++----- flutter/android/src/main/AndroidManifest.xml | 4 +- flutter/pubspec.yaml | 2 +- 16 files changed, 181 insertions(+), 189 deletions(-) delete mode 100644 demos/demo-flutter/android/app/build.gradle create mode 100644 demos/demo-flutter/android/app/build.gradle.kts delete mode 100644 demos/demo-flutter/android/build.gradle create mode 100644 demos/demo-flutter/android/build.gradle.kts delete mode 100644 demos/demo-flutter/android/settings.gradle create mode 100644 demos/demo-flutter/android/settings.gradle.kts diff --git a/.github/workflows/test-flutter.yaml b/.github/workflows/test-flutter.yaml index 639dafe235d..05679503cb1 100644 --- a/.github/workflows/test-flutter.yaml +++ b/.github/workflows/test-flutter.yaml @@ -13,11 +13,13 @@ jobs: steps: - uses: actions/checkout@v3 - name: flutter - uses: subosito/flutter-action@v2.3.0 + uses: subosito/flutter-action@v2 with: - architecture: arm64 + channel: stable + flutter-version: 3.29.0 - name: test-flutter run: | + flutter --version cd ./flutter flutter pub get flutter test diff --git a/demos/demo-flutter/.gitignore b/demos/demo-flutter/.gitignore index e172cfd4eee..177455d342b 100644 --- a/demos/demo-flutter/.gitignore +++ b/demos/demo-flutter/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/demos/demo-flutter/android/app/build.gradle b/demos/demo-flutter/android/app/build.gradle deleted file mode 100644 index f1bd7c344e4..00000000000 --- a/demos/demo-flutter/android/app/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.demo_flutter" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion 26 - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/demos/demo-flutter/android/app/build.gradle.kts b/demos/demo-flutter/android/app/build.gradle.kts new file mode 100644 index 00000000000..1d294e9889b --- /dev/null +++ b/demos/demo-flutter/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.demo_flutter" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.demo_flutter" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 26 + targetSdk = flutter.targetSdkVersion + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/demos/demo-flutter/android/build.gradle b/demos/demo-flutter/android/build.gradle deleted file mode 100644 index 9bc02fa3db7..00000000000 --- a/demos/demo-flutter/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.8.20' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/demos/demo-flutter/android/build.gradle.kts b/demos/demo-flutter/android/build.gradle.kts new file mode 100644 index 00000000000..89176ef44e8 --- /dev/null +++ b/demos/demo-flutter/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/demos/demo-flutter/android/gradle.properties b/demos/demo-flutter/android/gradle.properties index 94adc3a3f97..f018a61817f 100644 --- a/demos/demo-flutter/android/gradle.properties +++ b/demos/demo-flutter/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/demos/demo-flutter/android/gradle/wrapper/gradle-wrapper.properties b/demos/demo-flutter/android/gradle/wrapper/gradle-wrapper.properties index cb24abda10a..afa1e8eb0a8 100644 --- a/demos/demo-flutter/android/gradle/wrapper/gradle-wrapper.properties +++ b/demos/demo-flutter/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/demos/demo-flutter/android/settings.gradle b/demos/demo-flutter/android/settings.gradle deleted file mode 100644 index 44e62bcf06a..00000000000 --- a/demos/demo-flutter/android/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/demos/demo-flutter/android/settings.gradle.kts b/demos/demo-flutter/android/settings.gradle.kts new file mode 100644 index 00000000000..4ad7050fc8b --- /dev/null +++ b/demos/demo-flutter/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.5.2" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/demos/demo-flutter/ios/Podfile b/demos/demo-flutter/ios/Podfile index 88359b225fa..f17bddc9196 100644 --- a/demos/demo-flutter/ios/Podfile +++ b/demos/demo-flutter/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/demos/demo-flutter/ios/Podfile.lock b/demos/demo-flutter/ios/Podfile.lock index c073b30aac7..ceeb38fe804 100644 --- a/demos/demo-flutter/ios/Podfile.lock +++ b/demos/demo-flutter/ios/Podfile.lock @@ -27,10 +27,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - theta_client_flutter: a81f4f0b3a9f371d88f18a7b0707728db76326b2 + theta_client_flutter: 4d81bcd7ae344e358aa72b1cb216afd55f322c89 THETAClient: 0fb33ddf2994806ef4dfad9296dc337d8c9013d2 - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b -PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 +PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb COCOAPODS: 1.16.2 diff --git a/demos/demo-flutter/pubspec.lock b/demos/demo-flutter/pubspec.lock index c919b1d2586..6f8a760b06d 100644 --- a/demos/demo-flutter/pubspec.lock +++ b/demos/demo-flutter/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" csslib: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" flutter: dependency: "direct main" description: flutter @@ -100,18 +100,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -132,34 +132,34 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: @@ -172,55 +172,55 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.4" theta_client_flutter: dependency: "direct main" description: @@ -240,10 +240,10 @@ packages: dependency: "direct main" description: name: video_player - sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + sha256: "48941c8b05732f9582116b1c01850b74dbee1d8520cd7e34ad4609d6df666845" url: "https://pub.dev" source: hosted - version: "2.9.2" + version: "2.9.3" video_player_android: dependency: transitive description: @@ -280,18 +280,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.1" web: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.22.0" diff --git a/flutter/android/build.gradle b/flutter/android/build.gradle index 511eecdc7bc..baa3ac5cba2 100644 --- a/flutter/android/build.gradle +++ b/flutter/android/build.gradle @@ -2,15 +2,15 @@ group 'com.ricoh360.thetaclient.theta_client_flutter' version '1.0.0' buildscript { - ext.kotlin_version = '1.8.20' + ext.kotlin_version = "1.8.22" repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath("com.android.tools.build:gradle:8.7.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") } } @@ -21,37 +21,49 @@ allprojects { } } -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +apply plugin: "com.android.library" +apply plugin: "kotlin-android" android { - compileSdkVersion 31 + namespace = "com.ricoh360.thetaclient.theta_client_flutter" + + compileSdk = 35 compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = JavaVersion.VERSION_11 } sourceSets { - main.java.srcDirs += 'src/main/kotlin' + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" } defaultConfig { - minSdkVersion 26 + minSdk = 26 } -} -dependencies { - implementation("io.ktor:ktor-client-core:2.3.9") - implementation("io.ktor:ktor-client-cio:2.3.9") - implementation("io.ktor:ktor-client-content-negotiation:2.3.9") - implementation("io.ktor:ktor-client-logging:2.3.9") - implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9") - implementation("com.soywiz.korlibs.krypto:krypto:4.0.10") + dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.mockito:mockito-core:5.0.0") - implementation("com.ricoh360.thetaclient:theta-client:1.12.1") + implementation("com.soywiz.korlibs.krypto:krypto:4.0.10") + implementation("com.ricoh360.thetaclient:theta-client:1.12.1") + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } } diff --git a/flutter/android/src/main/AndroidManifest.xml b/flutter/android/src/main/AndroidManifest.xml index 12eb8f512e5..a2f47b6057d 100644 --- a/flutter/android/src/main/AndroidManifest.xml +++ b/flutter/android/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + + diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index f09718959e9..cc6d601910e 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^3.0.0 + flutter_lints: ^3.0.2 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From f72953b51b3efd7c73c65a338ef8ab9dc3d83e79 Mon Sep 17 00:00:00 2001 From: osakila Date: Thu, 6 Mar 2025 10:55:08 +0900 Subject: [PATCH 03/23] Change request header Cache-Control to no-store --- .../com/ricoh360/thetaclient/ThetaApi.kt | 18 ++++++++++++++---- .../thetaclient/capture/PhotoCaptureTest.kt | 1 + .../thetaclient/repository/GetOptionsTest.kt | 1 + .../thetaclient/repository/GetThetaInfoTest.kt | 1 + .../repository/GetThetaLicenseTest.kt | 2 ++ .../repository/GetThetaStateTest.kt | 2 ++ 6 files changed, 21 insertions(+), 4 deletions(-) diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt index e26aec8bb68..3f30cc5855f 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt @@ -68,7 +68,12 @@ internal object ThetaApi { endpoint: String, ): InfoApiResponse { return syncExecutor(requestScope, ApiClient.timeout.requestTimeout) { - httpClient.get(getApiUrl(endpoint, InfoApi.PATH)).body() + httpClient.get(getApiUrl(endpoint, InfoApi.PATH)) { + headers { + append("Content-Type", "application/json; charset=utf-8") + append("Cache-Control", "no-store") + } + }.body() } } @@ -106,7 +111,12 @@ internal object ThetaApi { endpoint: String, ): StateApiResponse { return syncExecutor(requestScope, ApiClient.timeout.requestTimeout) { - httpClient.post(getApiUrl(endpoint, StateApi.PATH)).body() + httpClient.post(getApiUrl(endpoint, StateApi.PATH)) { + headers { + append("Content-Type", "application/json; charset=utf-8") + append("Cache-Control", "no-store") + } + }.body() } } @@ -133,7 +143,7 @@ internal object ThetaApi { val response = httpClient.post(getApiUrl(endpoint, StatusApi.PATH)) { headers { append("Content-Type", "application/json; charset=utf-8") - append("Cache-Control", "no-cache") + append("Cache-Control", "no-store") } setBody(request) } @@ -904,7 +914,7 @@ internal object ThetaApi { httpClient.post(getApiUrl(endpoint, CommandApi.PATH)) { headers { append("Content-Type", "application/json; charset=utf-8") - append("Cache-Control", "no-cache") + append("Cache-Control", "no-store") } setBody(body) } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt index 0493661b1d9..30508fbab7b 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt @@ -75,6 +75,7 @@ class PhotoCaptureTest { else -> { when (request.url.encodedPath) { "/osc/commands/status" -> { + assertEquals(request.headers.get("Cache-Control"), "no-store") assertEquals( request.url.encodedPath, requestPathArray[2], diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt index 28ba8d63f6d..e62ec288c32 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt @@ -39,6 +39,7 @@ class GetOptionsTest { } private fun checkRequest(request: HttpRequestData, optionNames: List) { + assertEquals(request.headers.get("Cache-Control"), "no-store") val body = request.body as TextContent val js = Json { encodeDefaults = true // Encode properties with default value. diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaInfoTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaInfoTest.kt index bb9faa77aa6..63cc412c618 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaInfoTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaInfoTest.kt @@ -34,6 +34,7 @@ class GetThetaInfoTest { // setup val jsonString = Resource("src/commonTest/resources/info/info_z1.json").readText() MockApiClient.onRequest = { request -> + assertEquals(request.headers.get("Cache-Control"), "no-store") assertEquals(request.url.encodedPath, "/osc/info", "request path") ByteReadChannel(jsonString) } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaLicenseTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaLicenseTest.kt index 36e77de6e6d..5b6c70b060b 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaLicenseTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaLicenseTest.kt @@ -12,6 +12,7 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull import kotlin.test.assertTrue @OptIn(ExperimentalSerializationApi::class, ExperimentalCoroutinesApi::class) @@ -32,6 +33,7 @@ class GetThetaLicenseTest { fun getPluginLicenseTest() = runTest { MockApiClient.onRequest = { request -> // check request + assertNull(request.headers.get("Cache-Control")) assertEquals(request.url.encodedPath, "/legal-information/open-source-licenses", "request path") ByteReadChannel(Resource("src/commonTest/resources/getThetaLicense/license.html").readText()) } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt index b41202ba596..c5ccc6af279 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt @@ -31,6 +31,7 @@ class GetThetaStateTest { // setup val jsonString = Resource("src/commonTest/resources/state/state_z1.json").readText() MockApiClient.onRequest = { request -> + assertEquals(request.headers.get("Cache-Control"), "no-store") assertEquals(request.url.encodedPath, "/osc/state", "request path") ByteReadChannel(jsonString) } @@ -86,6 +87,7 @@ class GetThetaStateTest { // setup val jsonString = Resource("src/commonTest/resources/state/state_x.json").readText() MockApiClient.onRequest = { request -> + assertEquals(request.headers.get("Cache-Control"), "no-store") assertEquals(request.url.encodedPath, "/osc/state", "request path") ByteReadChannel(jsonString) } From 64ed21211c62188c13a9180025a3fc8e0fe2e359 Mon Sep 17 00:00:00 2001 From: osakila Date: Thu, 6 Mar 2025 11:03:53 +0900 Subject: [PATCH 04/23] Update ktor to 2.3.13 --- demos/demo-android/app/build.gradle | 3 ++- kotlin-multiplatform/build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/demos/demo-android/app/build.gradle b/demos/demo-android/app/build.gradle index 252db953413..1c79f4e7644 100755 --- a/demos/demo-android/app/build.gradle +++ b/demos/demo-android/app/build.gradle @@ -59,6 +59,7 @@ android { dependencies { def lifecycle_version = "2.7.0" + def ktor_version = "2.3.13" implementation 'androidx.core:core-ktx:1.12.0' implementation "androidx.compose.ui:ui:$compose_version" @@ -74,7 +75,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0' implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'io.coil-kt:coil-compose:2.2.2' - implementation "io.ktor:ktor-client-cio:2.3.9" + implementation "io.ktor:ktor-client-cio:$ktor_version" implementation "com.ricoh360.thetaclient:theta-client:1.12.1" testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' diff --git a/kotlin-multiplatform/build.gradle.kts b/kotlin-multiplatform/build.gradle.kts index df7a9a39821..a9285dbc114 100644 --- a/kotlin-multiplatform/build.gradle.kts +++ b/kotlin-multiplatform/build.gradle.kts @@ -55,7 +55,7 @@ kotlin { sourceSets { val coroutinesVersion = "1.7.3" - val ktorVersion = "2.3.9" + val ktorVersion = "2.3.13" val kryptoVersion = "4.0.10" val commonMain by getting { From 609ab7335424428856f0c53d06a26bcd29c80282 Mon Sep 17 00:00:00 2001 From: osakila Date: Mon, 10 Mar 2025 09:21:09 +0900 Subject: [PATCH 05/23] Add STARTING to capturing status --- flutter/lib/capture/capture.dart | 3 +++ flutter/test/enum_name_test.dart | 1 + .../thetaclient/capture/BurstCapture.kt | 1 + .../ricoh360/thetaclient/capture/Capture.kt | 5 +++++ .../capture/CompositeIntervalCapture.kt | 1 + .../thetaclient/capture/ContinuousCapture.kt | 1 + .../capture/LimitlessIntervalCapture.kt | 2 ++ .../capture/MultiBracketCapture.kt | 1 + .../thetaclient/capture/PhotoCapture.kt | 3 +++ .../ShotCountSpecifiedIntervalCapture.kt | 1 + .../thetaclient/capture/TimeShiftCapture.kt | 4 ++++ .../thetaclient/capture/VideoCapture.kt | 1 + .../thetaclient/capture/BurstCaptureTest.kt | 6 +++++- .../capture/CompositeIntervalCaptureTest.kt | 6 +++++- .../capture/ContinuousCaptureTest.kt | 6 +++++- .../capture/LimitlessIntervalCaptureTest.kt | 1 + .../capture/MultiBracketCaptureTest.kt | 20 +++++++++++++------ .../thetaclient/capture/PhotoCaptureTest.kt | 12 ++++++++--- .../ShotCountSpecifiedIntervalCaptureTest.kt | 15 ++++++++------ .../capture/TimeShiftCaptureTest.kt | 14 +++++++------ .../thetaclient/capture/VideoCaptureTest.kt | 1 + react-native/src/capture/capture.ts | 2 ++ 22 files changed, 83 insertions(+), 24 deletions(-) diff --git a/flutter/lib/capture/capture.dart b/flutter/lib/capture/capture.dart index 5cca4d932e9..696f1d71338 100644 --- a/flutter/lib/capture/capture.dart +++ b/flutter/lib/capture/capture.dart @@ -54,6 +54,9 @@ class Capture { /// Capturing status enum CapturingStatusEnum { + /// The process is starting + starting('STARTING'), + /// Capture in progress capturing('CAPTURING'), diff --git a/flutter/test/enum_name_test.dart b/flutter/test/enum_name_test.dart index 33ab264ca61..0a1c56b5fc1 100644 --- a/flutter/test/enum_name_test.dart +++ b/flutter/test/enum_name_test.dart @@ -912,6 +912,7 @@ void main() { test('CapturingStatusEnum', () async { List> data = [ + [CapturingStatusEnum.starting, 'STARTING'], [CapturingStatusEnum.capturing, 'CAPTURING'], [CapturingStatusEnum.selfTimerCountdown, 'SELF_TIMER_COUNTDOWN'], ]; diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt index 42a0adf7f56..c20a97361b6 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt @@ -190,6 +190,7 @@ class BurstCapture private constructor( callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = error.message)) return@launch } + callback.onCapturing(CapturingStatusEnum.STARTING) startCaptureResponse.id?.let { monitorCommandStatus(it, callback) diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt index 9862b1d2e00..fd1b4ac1857 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt @@ -12,6 +12,11 @@ import io.ktor.client.statement.HttpResponse * Identify the self-timer during capture */ enum class CapturingStatusEnum { + /** + * The process is starting + */ + STARTING, + /** * Capture in progress */ diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt index 9af3014290f..a7b1100f179 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt @@ -159,6 +159,7 @@ class CompositeIntervalCapture private constructor( callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = error.message)) return@launch } + callback.onCapturing(CapturingStatusEnum.STARTING) startCaptureResponse.id?.let { monitorCommandStatus(it, callback) diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt index 673d660574b..f5f6ce97746 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt @@ -193,6 +193,7 @@ class ContinuousCapture private constructor( callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = error.message)) return@launch } + callback.onCapturing(CapturingStatusEnum.STARTING) startCaptureResponse.id?.let { monitorCommandStatus(it, callback) diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt index a89c38931e6..9ee8fef1618 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt @@ -150,7 +150,9 @@ class LimitlessIntervalCapture private constructor( } ThetaApi.callStartCaptureCommand(endpoint, params).error?.let { callOnCaptureFailed(ThetaRepository.ThetaWebApiException(it.message)) + return@launch } + callback.onCapturing(CapturingStatusEnum.STARTING) } catch (e: JsonConvertException) { callOnCaptureFailed(ThetaRepository.ThetaWebApiException(e.message ?: e.toString())) } catch (e: ResponseException) { diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt index 5c7328b94ec..2411013b365 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt @@ -267,6 +267,7 @@ class MultiBracketCapture private constructor( ) return@launch } + callback.onCapturing(CapturingStatusEnum.STARTING) when (cameraModel) { ThetaRepository.ThetaModel.THETA_SC2, ThetaRepository.ThetaModel.THETA_SC2_B -> { diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt index 595b226efa6..2bbdbb101bd 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt @@ -97,6 +97,9 @@ class PhotoCapture private constructor( ) try { takePictureResponse = ThetaApi.callTakePictureCommand(endpoint = endpoint) + takePictureResponse.error ?: let { + callback.onCapturing(CapturingStatusEnum.STARTING) + } monitor.start() val id = takePictureResponse.id while (takePictureResponse.state == CommandState.IN_PROGRESS) { diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt index a6fbdd8203d..1dc51464a43 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt @@ -203,6 +203,7 @@ class ShotCountSpecifiedIntervalCapture private constructor( callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = error.message)) return@launch } + callback.onCapturing(CapturingStatusEnum.STARTING) when (cameraModel) { ThetaRepository.ThetaModel.THETA_SC2, ThetaRepository.ThetaModel.THETA_SC2_B -> { diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt index e1bfeac8f6d..54c4771f1a2 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt @@ -107,6 +107,10 @@ class TimeShiftCapture private constructor( endpoint = endpoint, params = StartCaptureParams(_mode = ShootingMode.TIME_SHIFT_SHOOTING) ) + startCaptureResponse.error ?: let { + callback.onCapturing(CapturingStatusEnum.STARTING) + } + monitor.start() /* diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt index 4145fd4c269..464bbabd112 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt @@ -184,6 +184,7 @@ class VideoCapture private constructor( response.error?.let { callOnCaptureFailed(ThetaRepository.ThetaWebApiException(it.message)) } + callback.onCapturing(CapturingStatusEnum.STARTING) } catch (e: JsonConvertException) { callOnCaptureFailed(ThetaRepository.ThetaWebApiException(e.message ?: e.toString())) } catch (e: ResponseException) { diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt index 63ee1a44b0b..7dca362e46f 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt @@ -1052,7 +1052,11 @@ class BurstCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { when { - stateCounter < 2 -> assertEquals( + stateCounter == 0 -> assertEquals( + status, + CapturingStatusEnum.STARTING + ) + stateCounter == 1 -> assertEquals( status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN ) diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt index 6a0457e18f1..6ab2d877f67 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt @@ -963,7 +963,11 @@ class CompositeIntervalCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { when { - stateCounter < 2 -> assertEquals( + stateCounter == 0 -> assertEquals( + status, + CapturingStatusEnum.STARTING + ) + stateCounter == 1 -> assertEquals( status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN ) diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt index 1df02cf8822..48d05f140c3 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt @@ -576,7 +576,11 @@ class ContinuousCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { when { - stateCounter < 2 -> assertEquals( + stateCounter == 0 -> assertEquals( + status, + CapturingStatusEnum.STARTING + ) + stateCounter == 1 -> assertEquals( status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN ) diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt index c756e886888..6e1a3bcda7e 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt @@ -120,6 +120,7 @@ class LimitlessIntervalCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { when (stateCounter) { + 0 -> assertEquals(status, CapturingStatusEnum.STARTING) 1 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) else -> assertEquals(status, CapturingStatusEnum.CAPTURING) } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt index b0404baa818..1fafe946ae1 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt @@ -130,11 +130,13 @@ class MultiBracketCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { onCapturingCounter++ - if (onSelfTimer) { - assertEquals(status, CapturingStatusEnum.CAPTURING) - } else { - onSelfTimer = true - assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + when { + onCapturingCounter == 1 -> assertEquals(status, CapturingStatusEnum.STARTING) + onSelfTimer -> assertEquals(status, CapturingStatusEnum.CAPTURING) + else -> { + onSelfTimer = true + assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + } } } @@ -243,6 +245,7 @@ class MultiBracketCaptureTest { ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null + var onCapturingCount = 0 multiBracketCapture.startCapture(object : MultiBracketCapture.StartCaptureCallback { override fun onCaptureCompleted(fileUrls: List?) { assertTrue(true, "onCaptureCompleted") @@ -255,7 +258,11 @@ class MultiBracketCaptureTest { } override fun onCapturing(status: CapturingStatusEnum) { - assertEquals(status, CapturingStatusEnum.CAPTURING) + when (onCapturingCount) { + 0 -> assertEquals(status, CapturingStatusEnum.STARTING) + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + onCapturingCount += 1 } override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { @@ -345,6 +352,7 @@ class MultiBracketCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { when (counter) { + 0 -> assertEquals(status, CapturingStatusEnum.STARTING) 4 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) 5 -> assertEquals(status, CapturingStatusEnum.CAPTURING) 6 -> assertEquals(status, CapturingStatusEnum.CAPTURING) diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt index 30508fbab7b..480d8fbdd8d 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt @@ -103,6 +103,7 @@ class PhotoCaptureTest { assertNull(photoCapture.getFileFormat(), "set option fileFormat") var file: String? = null + var onCapturingCount = 0 photoCapture.takePicture(object : PhotoCapture.TakePictureCallback { override fun onSuccess(fileUrl: String?) { file = fileUrl @@ -110,7 +111,11 @@ class PhotoCaptureTest { } override fun onCapturing(status: CapturingStatusEnum) { - assertEquals(status, CapturingStatusEnum.CAPTURING) + when (onCapturingCount) { + 0 -> assertEquals(status, CapturingStatusEnum.STARTING) + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + onCapturingCount += 1 } override fun onError(exception: ThetaRepository.ThetaRepositoryException) { @@ -1502,7 +1507,8 @@ class PhotoCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { when (onCapturingCount) { - 0 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + 0 -> assertEquals(status, CapturingStatusEnum.STARTING) + 1 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) else -> assertEquals(status, CapturingStatusEnum.CAPTURING) } onCapturingCount += 1 @@ -1521,6 +1527,6 @@ class PhotoCaptureTest { // check result assertTrue(file?.startsWith("http://") ?: false, "take picture") - assertEquals(onCapturingCount, 2) + assertEquals(onCapturingCount, 3) } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt index 010da96cc95..3d25df1c7ec 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt @@ -106,12 +106,14 @@ class ShotCountSpecifiedIntervalCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { onCapturingCounter++ - if (onSelfTimer) { - assertEquals(status, CapturingStatusEnum.CAPTURING) - } else { - onSelfTimer = true - assertEquals(onCapturingCounter, 1) - assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + when { + onCapturingCounter == 1 -> assertEquals(status, CapturingStatusEnum.STARTING) + onSelfTimer -> assertEquals(status, CapturingStatusEnum.CAPTURING) + else -> { + onSelfTimer = true + assertEquals(onCapturingCounter, 2) + assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + } } } @@ -182,6 +184,7 @@ class ShotCountSpecifiedIntervalCaptureTest { } override fun onCapturing(status: CapturingStatusEnum) { when (counter) { + 3 -> assertEquals(status, CapturingStatusEnum.STARTING) 4 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) else -> assertEquals(status, CapturingStatusEnum.CAPTURING) } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt index af315e7120e..1f9e534799a 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt @@ -132,12 +132,14 @@ class TimeShiftCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { onCapturingCounter++ - if (onSelfTimer) { - assertEquals(status, CapturingStatusEnum.CAPTURING) - } else { - onSelfTimer = true - assertEquals(onCapturingCounter, 1) - assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + when { + onCapturingCounter == 1 -> assertEquals(status, CapturingStatusEnum.STARTING) + onSelfTimer -> assertEquals(status, CapturingStatusEnum.CAPTURING) + else -> { + onSelfTimer = true + assertEquals(onCapturingCounter, 2) + assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + } } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt index 594eb57740e..d72ef31ecba 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt @@ -125,6 +125,7 @@ class VideoCaptureTest { override fun onCapturing(status: CapturingStatusEnum) { when (stateCounter) { + 0 -> assertEquals(status, CapturingStatusEnum.STARTING) 1 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) else -> assertEquals(status, CapturingStatusEnum.CAPTURING) } diff --git a/react-native/src/capture/capture.ts b/react-native/src/capture/capture.ts index d9c543b9f48..2595b3fb26e 100644 --- a/react-native/src/capture/capture.ts +++ b/react-native/src/capture/capture.ts @@ -126,6 +126,8 @@ export abstract class CaptureBuilder> { /** Capturing status */ export const CapturingStatusEnum = { + /** The process is starting */ + STARTING: 'STARTING', /** Capture in progress */ CAPTURING: 'CAPTURING', /** Self-timer in progress */ From 177ea929f947f22f96d8c349be0ba4a0fa51ba7a Mon Sep 17 00:00:00 2001 From: osakila Date: Mon, 10 Mar 2025 09:22:36 +0900 Subject: [PATCH 06/23] Improve params of set access point --- .../theta_client_flutter/ConvertUtil.kt | 59 +++++++- .../ThetaClientFlutterPlugin.kt | 51 +++---- flutter/ios/Classes/ConvertUtil.swift | 76 ++++++++++ .../SwiftThetaClientFlutterPlugin.swift | 116 +++++++--------- flutter/lib/theta_client_flutter.dart | 16 +-- .../theta_client_flutter_method_channel.dart | 12 +- ...eta_client_flutter_platform_interface.dart | 12 +- flutter/test/theta_client_flutter_test.dart | 12 +- .../ricoh360/thetaclient/ThetaRepository.kt | 14 +- .../thetaclientreactnative/Converter.kt | 63 +++++++++ .../ThetaClientSdkModule.kt | 67 +++------ react-native/ios/ConvertUtil.swift | 72 ++++++++++ react-native/ios/ThetaClientReactNative.m | 17 +-- react-native/ios/ThetaClientReactNative.swift | 131 ++++++------------ .../src/theta-repository/theta-repository.ts | 72 ++++++---- 15 files changed, 490 insertions(+), 300 deletions(-) diff --git a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt index 9fad0955eb1..881bbe8afe9 100644 --- a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt +++ b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt @@ -253,7 +253,7 @@ fun toOffDelay(value: Any): OffDelay? { return null } -fun toProxy(map: Map): Proxy { +fun toProxy(map: Map<*, *>): Proxy { return Proxy( use = map["use"] as? Boolean ?: false, url = map["url"] as? String, @@ -936,3 +936,60 @@ fun toStartedNotifyParam(value: String): Map { KEY_NOTIFY_PARAM_FILE_URL to value ) } + +data class SetAccessPointParams( + val ssid: String, + val ssidStealth: Boolean?, + val authMode: AuthModeEnum, + val password: String?, + val connectionPriority: Int?, + val proxy: Proxy?, +) + +const val KEY_SSID = "ssid" +const val KEY_SSID_STEALTH = "ssidStealth" +const val KEY_AUTH_MODE = "authMode" +const val KEY_PASSWORD = "password" +const val KEY_CONNECTION_PRIORITY = "connectionPriority" +const val KEY_IP_ADDRESS = "ipAddress" +const val KEY_SUBNET_MASK = "subnetMask" +const val KEY_DEFAULT_GATEWAY = "defaultGateway" +const val KEY_PROXY = "proxy" + +fun toSetAccessPointParams(arguments: Map<*, *>): SetAccessPointParams { + val ssid = arguments[KEY_SSID] as? String ?: throw IllegalArgumentException(KEY_SSID) + val ssidStealth = arguments[KEY_SSID_STEALTH] as? Boolean + val authMode = (arguments[KEY_AUTH_MODE] as? String?)?.let { + AuthModeEnum.valueOf(it) + }?: throw IllegalArgumentException(KEY_AUTH_MODE) + val password = arguments[KEY_PASSWORD] as? String + val connectionPriority = arguments[KEY_CONNECTION_PRIORITY] as? Int + val proxy = (arguments[KEY_PROXY] as? Map<*, *>)?.let { toProxy(it) } + + return SetAccessPointParams( + ssid = ssid, + ssidStealth = ssidStealth, + authMode = authMode, + password = password, + connectionPriority = connectionPriority, + proxy = proxy, + ) +} + +data class SetAccessPointStaticallyParams( + val ipAddress: String, + val subnetMask: String, + val defaultGateway: String, +) + +fun toSetAccessPointStaticallyParams(arguments: Map<*, *>): SetAccessPointStaticallyParams { + val ipAddress = arguments[KEY_IP_ADDRESS] as? String ?: throw IllegalArgumentException(KEY_IP_ADDRESS) + val subnetMask = arguments[KEY_SUBNET_MASK] as? String ?: throw IllegalArgumentException(KEY_SUBNET_MASK) + val defaultGateway = arguments[KEY_DEFAULT_GATEWAY] as? String ?: throw IllegalArgumentException(KEY_DEFAULT_GATEWAY) + + return SetAccessPointStaticallyParams( + ipAddress = ipAddress, + subnetMask = subnetMask, + defaultGateway = defaultGateway, + ) +} diff --git a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt index 0f356171832..17f06221356 100644 --- a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt @@ -1552,20 +1552,14 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { return } try { - val ssid = call.argument("ssid")!! - val ssidStealth = call.argument("ssidStealth")!! - val authModeName = call.argument("authMode")!! - val authMode = ThetaRepository.AuthModeEnum.values().find { - it.name == authModeName - }!! - val password = call.argument("password")!! - val connectionPriority = call.argument("connectionPriority")!! - - var proxy: ThetaRepository.Proxy? = null - (call.argument("proxy") as? Map)?.let { - proxy = toProxy(map = it) - } - thetaRepository?.setAccessPointDynamically(ssid, ssidStealth, authMode, password, connectionPriority, proxy) + val params = toSetAccessPointParams(call.arguments as Map<*, *>) + thetaRepository?.setAccessPointDynamically( + params.ssid, + params.ssidStealth, + params.authMode, + params.password, + params.connectionPriority, + params.proxy) result.success(null) } catch (e: Exception) { result.error(e.javaClass.simpleName, e.message, null) @@ -1578,23 +1572,18 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { return } try { - val ssid = call.argument("ssid")!! - val ssidStealth = call.argument("ssidStealth")!! - val authModeName = call.argument("authMode")!! - val authMode = ThetaRepository.AuthModeEnum.values().find { - it.name == authModeName - }!! - val password = call.argument("password")!! - val connectionPriority = call.argument("connectionPriority")!! - val ipAddress = call.argument("ipAddress")!! - val subnetMask = call.argument("subnetMask")!! - val defaultGateway = call.argument("defaultGateway")!! - - var proxy: ThetaRepository.Proxy? = null - (call.argument("proxy") as? Map)?.let { - proxy = toProxy(map = it) - } - thetaRepository?.setAccessPointStatically(ssid, ssidStealth, authMode, password, connectionPriority, ipAddress, subnetMask, defaultGateway, proxy) + val params = toSetAccessPointParams(call.arguments as Map<*, *>) + val staticallyParams = toSetAccessPointStaticallyParams(call.arguments as Map<*, *>) + thetaRepository?.setAccessPointStatically( + params.ssid, + params.ssidStealth, + params.authMode, + params.password, + params.connectionPriority, + staticallyParams.ipAddress, + staticallyParams.subnetMask, + staticallyParams.defaultGateway, + params.proxy) result.success(null) } catch (e: Exception) { result.error(e.javaClass.simpleName, e.message, null) diff --git a/flutter/ios/Classes/ConvertUtil.swift b/flutter/ios/Classes/ConvertUtil.swift index 169c20e0ac6..49b3cb70c4f 100644 --- a/flutter/ios/Classes/ConvertUtil.swift +++ b/flutter/ios/Classes/ConvertUtil.swift @@ -15,6 +15,15 @@ let KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" let KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" let KEY_STATE_BOARD_TEMP = "boardTemp" let KEY_STATE_BATTERY_TEMP = "batteryTemp" +let KEY_SSID = "ssid" +let KEY_SSID_STEALTH = "ssidStealth" +let KEY_CONNECTION_PRIORITY = "connectionPriority" +let KEY_AUTH_MODE = "authMode" +let KEY_PASSWORD = "password" +let KEY_PROXY = "proxy" +let KEY_IP_ADDRESS = "ipAddress" +let KEY_SUBNET_MASK = "subnetMask" +let KEY_DEFAULT_GATEWAY = "defaultGateway" public class ConvertUtil: NSObject {} @@ -1135,3 +1144,70 @@ func toStartedNotifyParam(value: String) -> [String: Any] { KEY_NOTIFY_PARAM_FILE_URL: value, ] } + +struct SetAccessPointParams { + let ssid: String + let ssidStealth: KotlinBoolean? + let authMode: ThetaRepository.AuthModeEnum + let password: String? + let connectionPriority: KotlinInt? + let proxy: ThetaRepository.Proxy? +} + +func toSetAccessPointParams(params: [String: Any?]) throws -> SetAccessPointParams { + guard let ssid = params[KEY_SSID] as? String else { + throw ThetaClientError.invalidArgument(KEY_SSID) + } + let ssidStealth = toKotlinBoolean(value: params[KEY_SSID_STEALTH] as? Bool) + guard let authMode = params[KEY_AUTH_MODE] as? String else { + throw ThetaClientError.invalidArgument(KEY_AUTH_MODE) + } + guard + let authModeEnum = getEnumValue( + values: ThetaRepository.AuthModeEnum.values(), name: authMode + ) else { + throw ThetaClientError.invalidArgument(KEY_AUTH_MODE) + } + let password = params[KEY_PASSWORD] as? String + let connectionPriority = toKotlinInt(value: params[KEY_CONNECTION_PRIORITY] as? Int) + let proxy = params[KEY_PROXY] + let proxyParam: ThetaRepository.Proxy? = { + if let proxy = proxy as? [String: Any] { + return toProxy(params: proxy) + } + return nil + }() + + return SetAccessPointParams( + ssid: ssid, + ssidStealth: ssidStealth, + authMode: authModeEnum, + password: password, + connectionPriority: connectionPriority, + proxy: proxyParam + ) +} + +struct SetAccessPointStaticallyParams { + let ipAddress: String + let subnetMask: String + let defaultGateway: String +} + +func toSetAccessPointStaticallyParams(params: [String: Any?]) throws -> SetAccessPointStaticallyParams { + guard let ipAddress = params[KEY_IP_ADDRESS] as? String else { + throw ThetaClientError.invalidArgument(KEY_IP_ADDRESS) + } + guard let subnetMask = params[KEY_SUBNET_MASK] as? String else { + throw ThetaClientError.invalidArgument(KEY_SUBNET_MASK) + } + guard let defaultGateway = params[KEY_DEFAULT_GATEWAY] as? String else { + throw ThetaClientError.invalidArgument(KEY_DEFAULT_GATEWAY) + } + + return SetAccessPointStaticallyParams( + ipAddress: ipAddress, + subnetMask: subnetMask, + defaultGateway: defaultGateway + ) +} diff --git a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift index 61afa350479..41e06c415a0 100644 --- a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift +++ b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift @@ -29,6 +29,10 @@ let NOTIFY_VIDEO_CAPTURE_CAPTURING = 10082 let NOTIFY_VIDEO_CAPTURE_STARTED = 10083 let NOTIFY_CONVERT_VIDEO_FORMATS_PROGRESS = 10091 +enum ThetaClientError: Error { + case invalidArgument(String) +} + public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { public func onListen(withArguments _: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { eventSink = events @@ -1514,40 +1518,35 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre return } guard - let arguments = call.arguments as? [String: Any], - let ssid = arguments["ssid"] as? String, - let ssidStealth = arguments["ssidStealth"] as? Bool, - let authModeName = arguments["authMode"] as? String, - let authMode = getEnumValue(values: ThetaRepository.AuthModeEnum.values(), name: authModeName), - let password = arguments["password"] as? String, - let connectionPriority = arguments["connectionPriority"] as? Int32 + let arguments = call.arguments as? [String: Any?] else { let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: SwiftThetaClientFlutterPlugin.messageNoArgument, details: nil) result(flutterError) return } - - var proxy: ThetaRepository.Proxy? - if let proxyMap = arguments["proxy"] as? [String: Any] { - proxy = toProxy(params: proxyMap) - } - - thetaRepository.setAccessPointDynamically( - ssid: ssid, - ssidStealth: ssidStealth, - authMode: authMode, - password: password, - connectionPriority: connectionPriority, - proxy: proxy, - completionHandler: { error in - if let thetaError = error { - let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: thetaError.localizedDescription, details: nil) - result(flutterError) - } else { - result(nil) + + do { + let accessPointParams = try toSetAccessPointParams(params: arguments) + thetaRepository.setAccessPointDynamically( + ssid: accessPointParams.ssid, + ssidStealth: accessPointParams.ssidStealth, + authMode: accessPointParams.authMode, + password: accessPointParams.password, + connectionPriority: accessPointParams.connectionPriority, + proxy: accessPointParams.proxy, + completionHandler: { error in + if let thetaError = error { + let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: thetaError.localizedDescription, details: nil) + result(flutterError) + } else { + result(nil) + } } - } - ) + ) + } catch { + let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: error.localizedDescription, details: nil) + result(flutterError) + } } func setAccessPointStatically(call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -1557,47 +1556,38 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre return } guard - let arguments = call.arguments as? [String: Any], - let ssid = arguments["ssid"] as? String, - let ssidStealth = arguments["ssidStealth"] as? Bool, - let authModeName = arguments["authMode"] as? String, - let authMode = getEnumValue(values: ThetaRepository.AuthModeEnum.values(), name: authModeName), - let connectionPriority = arguments["connectionPriority"] as? Int32, - let ipAddress = arguments["ipAddress"] as? String, - let subnetMask = arguments["subnetMask"] as? String, - let defaultGateway = arguments["defaultGateway"] as? String + let arguments = call.arguments as? [String: Any?] else { let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: SwiftThetaClientFlutterPlugin.messageNoArgument, details: nil) result(flutterError) return } - - let password = arguments["password"] as? String - - var proxy: ThetaRepository.Proxy? - if let proxyMap = arguments["proxy"] as? [String: Any] { - proxy = toProxy(params: proxyMap) - } - - thetaRepository.setAccessPointStatically( - ssid: ssid, - ssidStealth: ssidStealth, - authMode: authMode, - password: password, - connectionPriority: connectionPriority, - ipAddress: ipAddress, - subnetMask: subnetMask, - defaultGateway: defaultGateway, - proxy: proxy, - completionHandler: { error in - if let thetaError = error { - let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: thetaError.localizedDescription, details: nil) - result(flutterError) - } else { - result(nil) + do { + let accessPointParams = try toSetAccessPointParams(params: arguments) + let staticallyParams = try toSetAccessPointStaticallyParams(params: arguments) + thetaRepository.setAccessPointStatically( + ssid: accessPointParams.ssid, + ssidStealth: accessPointParams.ssidStealth, + authMode: accessPointParams.authMode, + password: accessPointParams.password, + connectionPriority: accessPointParams.connectionPriority, + ipAddress: staticallyParams.ipAddress, + subnetMask: staticallyParams.subnetMask, + defaultGateway: staticallyParams.defaultGateway, + proxy: accessPointParams.proxy, + completionHandler: { error in + if let thetaError = error { + let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: thetaError.localizedDescription, details: nil) + result(flutterError) + } else { + result(nil) + } } - } - ) + ) + } catch { + let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: error.localizedDescription, details: nil) + result(flutterError) + } } func deleteAccessPoint(call: FlutterMethodCall, result: @escaping FlutterResult) { diff --git a/flutter/lib/theta_client_flutter.dart b/flutter/lib/theta_client_flutter.dart index d875280e47a..a0c9b492df6 100644 --- a/flutter/lib/theta_client_flutter.dart +++ b/flutter/lib/theta_client_flutter.dart @@ -301,15 +301,15 @@ class ThetaClientFlutter { /// - @param ssid SSID of the access point. /// - @param ssidStealth True if SSID stealth is enabled. /// - @param authMode Authentication mode. - /// - @param password Password. If [authMode] is "[none]", pass empty String. + /// - @param password Password. Not set if [authMode] is "[none]". /// - @param connectionPriority Connection priority 1 to 5. Theta X fixes to 1 (The access point registered later has a higher priority.) /// - @param proxy Proxy information to be used for the access point. /// - @throws If an error occurs in THETA. Future setAccessPointDynamically(String ssid, - {bool ssidStealth = false, + {bool? ssidStealth, AuthModeEnum authMode = AuthModeEnum.none, - String password = '', - int connectionPriority = 1, + String? password, + int? connectionPriority, Proxy? proxy}) { return ThetaClientFlutterPlatform.instance.setAccessPointDynamically( ssid, ssidStealth, authMode, password, connectionPriority, proxy); @@ -320,7 +320,7 @@ class ThetaClientFlutter { /// - @param ssid SSID of the access point. /// - @param ssidStealth True if SSID stealth is enabled. /// - @param authMode Authentication mode. - /// - @param password Password. If [authMode] is "[none]", pass empty String. + /// - @param password Password. Not set if [authMode] is "[none]". /// - @param connectionPriority Connection priority 1 to 5. Theta X fixes to 1 (The access point registered later has a higher priority.) /// - @param ipAddress IP address assigns to Theta. /// - @param subnetMask Subnet mask. @@ -328,10 +328,10 @@ class ThetaClientFlutter { /// - @param proxy Proxy information to be used for the access point. /// - @throws If an error occurs in THETA. Future setAccessPointStatically(String ssid, - {bool ssidStealth = false, + {bool? ssidStealth, AuthModeEnum authMode = AuthModeEnum.none, - String password = '', - int connectionPriority = 1, + String? password, + int? connectionPriority, required String ipAddress, required String subnetMask, required String defaultGateway, diff --git a/flutter/lib/theta_client_flutter_method_channel.dart b/flutter/lib/theta_client_flutter_method_channel.dart index 0cf9e6440a5..4a59df96ddb 100644 --- a/flutter/lib/theta_client_flutter_method_channel.dart +++ b/flutter/lib/theta_client_flutter_method_channel.dart @@ -956,10 +956,10 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @override Future setAccessPointDynamically( String ssid, - bool ssidStealth, + bool? ssidStealth, AuthModeEnum authMode, - String password, - int connectionPriority, + String? password, + int? connectionPriority, Proxy? proxy) async { final Map params = { 'ssid': ssid, @@ -976,10 +976,10 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @override Future setAccessPointStatically( String ssid, - bool ssidStealth, + bool? ssidStealth, AuthModeEnum authMode, - String password, - int connectionPriority, + String? password, + int? connectionPriority, String ipAddress, String subnetMask, String defaultGateway, diff --git a/flutter/lib/theta_client_flutter_platform_interface.dart b/flutter/lib/theta_client_flutter_platform_interface.dart index 89418165f08..51b3f33ca49 100644 --- a/flutter/lib/theta_client_flutter_platform_interface.dart +++ b/flutter/lib/theta_client_flutter_platform_interface.dart @@ -322,10 +322,10 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { Future setAccessPointDynamically( String ssid, - bool ssidStealth, + bool? ssidStealth, AuthModeEnum authMode, - String password, - int connectionPriority, + String? password, + int? connectionPriority, Proxy? proxy) { throw UnimplementedError( 'setAccessPointDynamically() has not been implemented.'); @@ -333,10 +333,10 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { Future setAccessPointStatically( String ssid, - bool ssidStealth, + bool? ssidStealth, AuthModeEnum authMode, - String password, - int connectionPriority, + String? password, + int? connectionPriority, String ipAddress, String subnetMask, String defaultGateway, diff --git a/flutter/test/theta_client_flutter_test.dart b/flutter/test/theta_client_flutter_test.dart index 86e6148b0c7..70cf6c38f2a 100644 --- a/flutter/test/theta_client_flutter_test.dart +++ b/flutter/test/theta_client_flutter_test.dart @@ -338,10 +338,10 @@ class MockThetaClientFlutterPlatform @override Future setAccessPointDynamically( String ssid, - bool ssidStealth, + bool? ssidStealth, AuthModeEnum authMode, - String password, - int connectionPriority, + String? password, + int? connectionPriority, Proxy? proxy) { return Future.value(); } @@ -349,10 +349,10 @@ class MockThetaClientFlutterPlatform @override Future setAccessPointStatically( String ssid, - bool ssidStealth, + bool? ssidStealth, AuthModeEnum authMode, - String password, - int connectionPriority, + String? password, + int? connectionPriority, String ipAddress, String subnetMask, String defaultGateway, diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt index 0a3b9015cf4..1546c78a434 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -7298,10 +7298,10 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? @Throws(Throwable::class) internal suspend fun setAccessPoint( ssid: String, - ssidStealth: Boolean = false, + ssidStealth: Boolean? = null, authMode: AuthModeEnum = AuthModeEnum.NONE, password: String? = null, - connectionPriority: Int = 1, + connectionPriority: Int? = null, ipAddressAllocation: IpAddressAllocation, ipAddress: String? = null, subnetMask: String? = null, @@ -7350,10 +7350,10 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? @Throws(Throwable::class) suspend fun setAccessPointDynamically( ssid: String, - ssidStealth: Boolean = false, + ssidStealth: Boolean? = null, authMode: AuthModeEnum = AuthModeEnum.NONE, - password: String = "", - connectionPriority: Int = 1, + password: String? = null, + connectionPriority: Int? = null, proxy: Proxy? = null, ) { setAccessPoint( @@ -7385,10 +7385,10 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? @Throws(Throwable::class) suspend fun setAccessPointStatically( ssid: String, - ssidStealth: Boolean = false, + ssidStealth: Boolean? = null, authMode: AuthModeEnum = AuthModeEnum.NONE, password: String? = null, - connectionPriority: Int = 1, + connectionPriority: Int? = null, ipAddress: String, subnetMask: String, defaultGateway: String, diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt index e4fd8b0ed89..92b62841d3d 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt @@ -23,6 +23,15 @@ const val KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" const val KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" const val KEY_STATE_BOARD_TEMP = "boardTemp" const val KEY_STATE_BATTERY_TEMP = "batteryTemp" +const val KEY_SSID = "ssid" +const val KEY_SSID_STEALTH = "ssidStealth" +const val KEY_AUTH_MODE = "authMode" +const val KEY_PASSWORD = "password" +const val KEY_CONNECTION_PRIORITY = "connectionPriority" +const val KEY_IP_ADDRESS = "ipAddress" +const val KEY_SUBNET_MASK = "subnetMask" +const val KEY_DEFAULT_GATEWAY = "defaultGateway" +const val KEY_PROXY = "proxy" val optionItemNameToEnum: Map = mutableMapOf( "aiAutoThumbnail" to OptionNameEnum.AiAutoThumbnail, @@ -1014,3 +1023,57 @@ fun toSleepDelay(objects: ReadableMap): SleepDelay? { } return null } + +data class SetAccessPointParams( + val ssid: String, + val ssidStealth: Boolean?, + val authMode: AuthModeEnum, + val password: String?, + val connectionPriority: Int?, + val proxy: Proxy?, +) + +fun toSetAccessPointParams(objects: ReadableMap): SetAccessPointParams { + val ssid = objects.getString(KEY_SSID) ?: throw IllegalArgumentException(KEY_SSID) + val ssidStealth = if (objects.hasKey(KEY_SSID_STEALTH)) { + objects.getBoolean(KEY_SSID_STEALTH) + } else { + null + } + val authMode = objects.getString(KEY_AUTH_MODE)?.let { AuthModeEnum.valueOf(it) } + ?: throw IllegalArgumentException(KEY_AUTH_MODE) + val password = objects.getString(KEY_PASSWORD) + val connectionPriority = if (objects.hasKey(KEY_CONNECTION_PRIORITY)) { + objects.getInt(KEY_CONNECTION_PRIORITY) + } else { + null + } + val proxy = if (objects.hasKey(KEY_PROXY)) toProxy(objects.getMap(KEY_PROXY)) else null + + return SetAccessPointParams( + ssid = ssid, + ssidStealth = ssidStealth, + authMode = authMode, + password = password, + connectionPriority = connectionPriority, + proxy = proxy, + ) +} + +data class SetAccessPointStaticallyParams( + val ipAddress: String, + val subnetMask: String, + val defaultGateway: String, +) + +fun toSetAccessPointStaticallyParams(objects: ReadableMap): SetAccessPointStaticallyParams { + val ipAddress = objects.getString(KEY_IP_ADDRESS) ?: throw IllegalArgumentException(KEY_IP_ADDRESS) + val subnetMask = objects.getString(KEY_SUBNET_MASK) ?: throw IllegalArgumentException(KEY_SUBNET_MASK) + val defaultGateway = objects.getString(KEY_DEFAULT_GATEWAY) ?: throw IllegalArgumentException(KEY_DEFAULT_GATEWAY) + + return SetAccessPointStaticallyParams( + ipAddress = ipAddress, + subnetMask = subnetMask, + defaultGateway = defaultGateway, + ) +} diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt index b70ec5c1c14..056ffc9a496 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt @@ -1716,22 +1716,12 @@ class ThetaClientReactNativeModule( /** * setAccessPointDynamically - set access point with dhcp - * @param ssid ssid to connect - * @param ssidStealth ssid is stealth or not - * @param authMode auth mode to connect - * @param password password to connect with auth - * @param connectionPriority connection priority - * @param proxy Proxy information to be used for the access point. + * @param params parameters of setAccessPointDynamically * @param promise promise to set result */ @ReactMethod fun setAccessPointDynamically( - ssid: String, - ssidStealth: Boolean, - authMode: String, - password: String, - connectionPriority: Int, - proxy: ReadableMap?, + params: ReadableMap, promise: Promise ) { val theta = theta @@ -1741,13 +1731,14 @@ class ThetaClientReactNativeModule( } launch { try { + val accessPointParams = toSetAccessPointParams(params) theta.setAccessPointDynamically( - ssid, - ssidStealth, - ThetaRepository.AuthModeEnum.valueOf(authMode), - password, - connectionPriority, - toProxy(map = proxy) + accessPointParams.ssid, + accessPointParams.ssidStealth, + accessPointParams.authMode, + accessPointParams.password, + accessPointParams.connectionPriority, + accessPointParams.proxy, ) promise.resolve(true) } catch (t: Throwable) { @@ -1758,28 +1749,12 @@ class ThetaClientReactNativeModule( /** * setAccessPointStatically - set access point with static connection info - * @param ssid ssid to connect - * @param ssidStealth ssid is stealth or not - * @param authMode auth mode to connect - * @param password password to connect with auth - * @param connectionPriority connection priority - * @param ipAddress static ipaddress to connect - * @param subnetMask subnet mask for ip address - * @param defaultGateway default gateway address + * @param params parameters of setAccessPointStatically * @param promise promise to set result - * @param proxy Proxy information to be used for the access point. */ @ReactMethod fun setAccessPointStatically( - ssid: String, - ssidStealth: Boolean, - authMode: String, - password: String, - connectionPriority: Int, - ipAddress: String, - subnetMask: String, - defaultGateway: String, - proxy: ReadableMap?, + params: ReadableMap, promise: Promise ) { val theta = theta @@ -1789,16 +1764,18 @@ class ThetaClientReactNativeModule( } launch { try { + val accessPointParams = toSetAccessPointParams(params) + val staticallyParams = toSetAccessPointStaticallyParams(params) theta.setAccessPointStatically( - ssid, - ssidStealth, - ThetaRepository.AuthModeEnum.valueOf(authMode), - password, - connectionPriority, - ipAddress, - subnetMask, - defaultGateway, - toProxy(map = proxy) + accessPointParams.ssid, + accessPointParams.ssidStealth, + accessPointParams.authMode, + accessPointParams.password, + accessPointParams.connectionPriority, + staticallyParams.ipAddress, + staticallyParams.subnetMask, + staticallyParams.defaultGateway, + accessPointParams.proxy ) promise.resolve(true) } catch (t: Throwable) { diff --git a/react-native/ios/ConvertUtil.swift b/react-native/ios/ConvertUtil.swift index 4d64cbf757d..a3fc7b694ce 100644 --- a/react-native/ios/ConvertUtil.swift +++ b/react-native/ios/ConvertUtil.swift @@ -72,6 +72,11 @@ let KEY_SUBNET_MASK = "subnetMask" let KEY_DEFAULT_GATEWAY = "defaultGateway" let KEY_OPTIONS = "options" let KEY_STATE = "state" +let KEY_SSID = "ssid" +let KEY_SSID_STEALTH = "ssidStealth" +let KEY_CONNECTION_PRIORITY = "connectionPriority" +let KEY_AUTH_MODE = "authMode" +let KEY_PASSWORD = "password" public class ConvertUtil: NSObject {} @@ -1470,3 +1475,70 @@ func toTimeout(params: [String: Any]) -> ThetaRepository.Timeout? { socketTimeout: socketTimeout ) } + +struct SetAccessPointParams { + let ssid: String + let ssidStealth: KotlinBoolean? + let authMode: ThetaRepository.AuthModeEnum + let password: String? + let connectionPriority: KotlinInt? + let proxy: ThetaRepository.Proxy? +} + +func toSetAccessPointParams(params: [String: Any?]) throws -> SetAccessPointParams { + guard let ssid = params[KEY_SSID] as? String else { + throw ThetaClientError.invalidArgument(KEY_SSID) + } + let ssidStealth = toKotlinBoolean(value: params[KEY_SSID_STEALTH] as? Bool) + guard let authMode = params[KEY_AUTH_MODE] as? String else { + throw ThetaClientError.invalidArgument(KEY_AUTH_MODE) + } + guard + let authModeEnum = getEnumValue( + values: ThetaRepository.AuthModeEnum.values(), name: authMode + ) else { + throw ThetaClientError.invalidArgument(KEY_AUTH_MODE) + } + let password = params[KEY_PASSWORD] as? String + let connectionPriority = toKotlinInt(value: params[KEY_CONNECTION_PRIORITY] as? Int) + let proxy = params[KEY_PROXY] + let proxyParam: ThetaRepository.Proxy? = { + if let proxy = proxy as? [String: Any] { + return toProxy(params: proxy) + } + return nil + }() + + return SetAccessPointParams( + ssid: ssid, + ssidStealth: ssidStealth, + authMode: authModeEnum, + password: password, + connectionPriority: connectionPriority, + proxy: proxyParam + ) +} + +struct SetAccessPointStaticallyParams { + let ipAddress: String + let subnetMask: String + let defaultGateway: String +} + +func toSetAccessPointStaticallyParams(params: [String: Any?]) throws -> SetAccessPointStaticallyParams { + guard let ipAddress = params[KEY_IP_ADDRESS] as? String else { + throw ThetaClientError.invalidArgument(KEY_IP_ADDRESS) + } + guard let subnetMask = params[KEY_SUBNET_MASK] as? String else { + throw ThetaClientError.invalidArgument(KEY_SUBNET_MASK) + } + guard let defaultGateway = params[KEY_DEFAULT_GATEWAY] as? String else { + throw ThetaClientError.invalidArgument(KEY_DEFAULT_GATEWAY) + } + + return SetAccessPointStaticallyParams( + ipAddress: ipAddress, + subnetMask: subnetMask, + defaultGateway: defaultGateway + ) +} diff --git a/react-native/ios/ThetaClientReactNative.m b/react-native/ios/ThetaClientReactNative.m index c52874db26a..480a8bffa68 100644 --- a/react-native/ios/ThetaClientReactNative.m +++ b/react-native/ios/ThetaClientReactNative.m @@ -204,24 +204,11 @@ @interface RCT_EXTERN_MODULE(ThetaClientReactNative, NSObject) RCT_EXTERN_METHOD(listAccessPoints:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(setAccessPointDynamically:(NSString *)ssid - withSsidStealth:(BOOL)ssidStealth - withAuthMode:(NSString *)authMode - withPassword:(NSString *)password - withConnectionPriority:(int)connectionPriority - withProxy:(NSDictionary *)proxy +RCT_EXTERN_METHOD(setAccessPointDynamically:(NSDictionary *)params withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(setAccessPointStatically:(NSString *)ssid - withSsidStealth:(BOOL)ssidStealth - withAuthMode:(NSString *)authMode - withPassword:(NSString *)password - withConnectionPriority:(int)connectionPriority - withIpAddress:(NSString *)ipAddress - withSubnetMask:(NSString *)subnetMask - withDefaultGateway:(NSString *)defaultGateway - withProxy:(NSDictionary *)proxy +RCT_EXTERN_METHOD(setAccessPointStatically:(NSDictionary *)params withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) diff --git a/react-native/ios/ThetaClientReactNative.swift b/react-native/ios/ThetaClientReactNative.swift index 1ad50850b54..c0ff9791ea3 100644 --- a/react-native/ios/ThetaClientReactNative.swift +++ b/react-native/ios/ThetaClientReactNative.swift @@ -32,6 +32,10 @@ let MESSAGE_NO_CONTINUOUS_CAPTURE = "No continuousCapture." let MESSAGE_NO_CONTINUOUS_CAPTURE_BUILDER = "no continuousCaptureBuilder." let MESSAGE_NO_EVENT_WEBSOCKET = "no eventWebSocket." +enum ThetaClientError: Error { + case invalidArgument(String) +} + @objc(ThetaClientReactNative) class ThetaClientReactNative: RCTEventEmitter { var thetaRepository: ThetaRepository? @@ -1924,21 +1928,11 @@ class ThetaClientReactNative: RCTEventEmitter { @objc( setAccessPointDynamically: - withSsidStealth: - withAuthMode: - withPassword: - withConnectionPriority: - withProxy: withResolver: withRejecter: ) func setAccessPointDynamically( - ssid: String, - ssidStealth: Bool, - authMode: String, - password: String, - connectionPriority: Int, - proxy: [AnyHashable: Any]?, + params: [AnyHashable: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock ) { @@ -1946,60 +1940,34 @@ class ThetaClientReactNative: RCTEventEmitter { reject(ERROR_CODE_ERROR, MESSAGE_NOT_INIT, nil) return } - guard - let authMode = getEnumValue( - values: ThetaRepository.AuthModeEnum.values(), name: authMode - ) - else { - reject(ERROR_CODE_ERROR, MESSAGE_NO_ARGUMENT, nil) - return - } - - let proxyParam: ThetaRepository.Proxy? = { - if let proxy = proxy as? [String: Any] { - return toProxy(params: proxy) - } - return nil - }() - thetaRepository.setAccessPointDynamically( - ssid: ssid, - ssidStealth: ssidStealth, - authMode: authMode, - password: password, - connectionPriority: Int32(connectionPriority), - proxy: proxyParam - ) { error in - if let error { - reject(ERROR_CODE_ERROR, error.localizedDescription, error) - } else { - resolve(true) + do { + let accessPointParams = try toSetAccessPointParams(params: params as? [String: Any] ?? [:]) + thetaRepository.setAccessPointDynamically( + ssid: accessPointParams.ssid, + ssidStealth: accessPointParams.ssidStealth, + authMode: accessPointParams.authMode, + password: accessPointParams.password, + connectionPriority: accessPointParams.connectionPriority, + proxy: accessPointParams.proxy + ) { error in + if let error { + reject(ERROR_CODE_ERROR, error.localizedDescription, error) + } else { + resolve(true) + } } + } catch { + reject(ERROR_CODE_ERROR, error.localizedDescription, error) } } - + @objc( setAccessPointStatically: - withSsidStealth: - withAuthMode: - withPassword: - withConnectionPriority: - withIpAddress: - withSubnetMask: - withDefaultGateway: - withProxy: withResolver: withRejecter: ) func setAccessPointStatically( - ssid: String, - ssidStealth: Bool, - authMode: String, - password: String?, - connectionPriority: Int, - ipAddress: String, - subnetMask: String, - defaultGateway: String, - proxy: [AnyHashable: Any]?, + params: [AnyHashable: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock ) { @@ -2007,37 +1975,28 @@ class ThetaClientReactNative: RCTEventEmitter { reject(ERROR_CODE_ERROR, MESSAGE_NOT_INIT, nil) return } - guard - let authMode = getEnumValue( - values: ThetaRepository.AuthModeEnum.values(), name: authMode - ) - else { - reject(ERROR_CODE_ERROR, MESSAGE_NO_ARGUMENT, nil) - return - } - - let proxyParam: ThetaRepository.Proxy? = { - if let proxy = proxy as? [String: Any] { - return toProxy(params: proxy) - } - return nil - }() - thetaRepository.setAccessPointStatically( - ssid: ssid, - ssidStealth: ssidStealth, - authMode: authMode, - password: password, - connectionPriority: Int32(connectionPriority), - ipAddress: ipAddress, - subnetMask: subnetMask, - defaultGateway: defaultGateway, - proxy: proxyParam - ) { error in - if let error { - reject(ERROR_CODE_ERROR, error.localizedDescription, error) - } else { - resolve(true) + do { + let accessPointParams = try toSetAccessPointParams(params: params as? [String: Any] ?? [:]) + let staticallyParams = try toSetAccessPointStaticallyParams(params: params as? [String: Any] ?? [:]) + thetaRepository.setAccessPointStatically( + ssid: accessPointParams.ssid, + ssidStealth: accessPointParams.ssidStealth, + authMode: accessPointParams.authMode, + password: accessPointParams.password, + connectionPriority: accessPointParams.connectionPriority, + ipAddress: staticallyParams.ipAddress, + subnetMask: staticallyParams.subnetMask, + defaultGateway: staticallyParams.defaultGateway, + proxy: accessPointParams.proxy + ) { error in + if let error { + reject(ERROR_CODE_ERROR, error.localizedDescription, error) + } else { + resolve(true) + } } + } catch { + reject(ERROR_CODE_ERROR, error.localizedDescription, error) } } diff --git a/react-native/src/theta-repository/theta-repository.ts b/react-native/src/theta-repository/theta-repository.ts index 51076d742e7..4be2b0ce1f4 100644 --- a/react-native/src/theta-repository/theta-repository.ts +++ b/react-native/src/theta-repository/theta-repository.ts @@ -456,29 +456,39 @@ export function listAccessPoints(): Promise { * * @function setAccessPointDynamically * @param {string} ssid SSID of the access point. - * @param {boolean} ssidStealth true if SSID stealth is enabled. - * @param {AuthModeEnum} authMode Authentication mode. - * @param {string} password Password. If authMode is "NONE", pass empty String. - * @param {number} connectionPriority Connection priority 1 to 5. - * @param {Proxy} proxy Proxy information to be used for the access point. + * @param {} params - Optional parameters for additional configuration. + * @param {boolean} params.ssidStealth true if SSID stealth is enabled. + * @param {AuthModeEnum} params.authMode Authentication mode. + * @param {string} params.password Password. Not set if authMode is “NONE”. + * @param {number} params.connectionPriority Connection priority 1 to 5. + * @param {Proxy} params.proxy Proxy information to be used for the access point. * @return promise of boolean result */ export function setAccessPointDynamically( ssid: string, - ssidStealth: boolean = false, - authMode: AuthModeEnum = AuthModeEnum.NONE, - password: string = '', - connectionPriority: number = 1, - proxy?: Proxy + params?: { + ssidStealth?: boolean; + authMode?: AuthModeEnum; + password?: string; + connectionPriority?: number; + proxy?: Proxy; + } ): Promise { - return ThetaClientReactNative.setAccessPointDynamically( + const { + ssidStealth, + authMode = AuthModeEnum.NONE, + password, + connectionPriority, + proxy, + } = params ?? {}; + return ThetaClientReactNative.setAccessPointDynamically({ ssid, ssidStealth, authMode, password, connectionPriority, - proxy - ); + proxy, + }); } /** @@ -486,28 +496,38 @@ export function setAccessPointDynamically( * * @function setAccessPointStatically * @param {string} ssid SSID of the access point. - * @param {boolean} ssidStealth True if SSID stealth is enabled. - * @param {AuthModeEnum} authMode Authentication mode. - * @param {string} password Password. If authMode is "NONE", pass empty String. - * @param {number} connectionPriority Connection priority 1 to 5. * @param {string} ipAddress IP address assigns to Theta. * @param {string} subnetMask Subnet mask. * @param {string} defaultGateway Default gateway. - * @param {Proxy} proxy Proxy information to be used for the access point. + * @param {} params - Optional parameters for additional configuration. + * @param {boolean} params.ssidStealth True if SSID stealth is enabled. + * @param {AuthModeEnum} params.authMode Authentication mode. + * @param {string} params.password Password. Not set if authMode is “NONE”. + * @param {number} params.connectionPriority Connection priority 1 to 5. + * @param {Proxy} params.proxy Proxy information to be used for the access point. * @return promise of boolean result */ export function setAccessPointStatically( ssid: string, - ssidStealth: boolean = false, - authMode: AuthModeEnum = AuthModeEnum.NONE, - password: string = '', - connectionPriority: number = 1, ipAddress: string, subnetMask: string, defaultGateway: string, - proxy?: Proxy + params?: { + ssidStealth?: boolean; + authMode: AuthModeEnum; + password?: string; + connectionPriority?: number; + proxy?: Proxy; + } ): Promise { - return ThetaClientReactNative.setAccessPointStatically( + const { + ssidStealth, + authMode = AuthModeEnum.NONE, + password, + connectionPriority, + proxy, + } = params ?? {}; + return ThetaClientReactNative.setAccessPointStatically({ ssid, ssidStealth, authMode, @@ -516,8 +536,8 @@ export function setAccessPointStatically( ipAddress, subnetMask, defaultGateway, - proxy - ); + proxy, + }); } /** From fe93a33b1492ee440057f9508bd2b5f8d186edc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B2=A1=E3=80=80=E4=BE=91=E5=87=9B?= <129148471+LassicYM@users.noreply.github.com> Date: Wed, 12 Mar 2025 09:22:16 +0900 Subject: [PATCH 07/23] Reflect changes in v2.71.1 --- flutter/lib/theta_client_flutter.dart | 20 +++++++++++++++---- flutter/test/enum_name_test.dart | 1 + .../ricoh360/thetaclient/ThetaRepository.kt | 10 +++++++--- .../thetaclient/transferred/infoApi.kt | 4 ++++ .../transferred/setOptionsCommand.kt | 8 +++++--- .../repository/options/PreviewFormatTest.kt | 1 + .../options/option-preview-format.test.tsx | 1 + .../options/option-preview-format.ts | 2 ++ .../src/theta-repository/theta-info.ts | 8 +++++++- .../screen/options-screen/options-screen.tsx | 18 +++++++++++++++++ 10 files changed, 62 insertions(+), 11 deletions(-) diff --git a/flutter/lib/theta_client_flutter.dart b/flutter/lib/theta_client_flutter.dart index a0c9b492df6..e89cbb194dc 100644 --- a/flutter/lib/theta_client_flutter.dart +++ b/flutter/lib/theta_client_flutter.dart @@ -539,7 +539,12 @@ class ThetaInfo { /// Theta serial number. final String serialNumber; - /// MAC address of wireless LAN (RICOH THETA V firmware v2.11.1 or later) + /// MAC address of wireless LAN + /// (RICOH THETA V firmware v2.11.1 or later) + /// + /// For THETA X, firmware versions v2.63.0 and earlier display `the communication MAC address`, + /// while v2.71.1 and later diplay `the physical MAC address`. + /// For other than THETA X, `the physical MAC address` is displayed. final String? wlanMacAddress; /// MAC address of Bluetooth (RICOH THETA V firmware v2.11.1 or later) @@ -2473,6 +2478,10 @@ enum PreviewFormatEnum { // ignore: constant_identifier_names w1920_h960_f8('W1920_H960_F8'), + /// For Theta X firmware v2.71.1 or later + // ignore: constant_identifier_names + w1920_h960_f30('W1920_H960_F30'), + /// For Theta Z1 and V // ignore: constant_identifier_names w1024_h512_f8('W1024_H512_F8'), @@ -3386,9 +3395,12 @@ class Options { /// Also, when filter is enabled, the exposure program is set to the Normal program. /// /// The condition below will result in an error. - /// [fileFormat] is raw+ and _filter is Noise reduction, HDR or Handheld HDR - /// shootingMethod is except for Normal shooting and [filter] is enabled - /// Access during video capture mode + /// + /// - When attempting to set [filter] to Noise reduction, + /// HDR or Handheld HDR while [fileFormat] is set to raw+, + /// but this restriction is only for RICOH THETA Z1 firmware v1.80.1 or earlier. + /// - [shootingMethod] is except for Normal shooting and [filter] is enabled + /// - Access during video capture mode FilterEnum? filter; /// see [ShootingFunctionEnum] diff --git a/flutter/test/enum_name_test.dart b/flutter/test/enum_name_test.dart index 0a1c56b5fc1..d1834e4d9ec 100644 --- a/flutter/test/enum_name_test.dart +++ b/flutter/test/enum_name_test.dart @@ -747,6 +747,7 @@ void main() { [PreviewFormatEnum.w1024_h512_f15, 'W1024_H512_F15'], [PreviewFormatEnum.w512_h512_f30, 'W512_H512_F30'], [PreviewFormatEnum.w1920_h960_f8, 'W1920_H960_F8'], + [PreviewFormatEnum.w1920_h960_f30, 'W1920_H960_F30'], [PreviewFormatEnum.w1024_h512_f8, 'W1024_H512_F8'], [PreviewFormatEnum.w640_h320_f30, 'W640_H320_F30'], [PreviewFormatEnum.w640_h320_f8, 'W640_H320_F8'], diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt index 1546c78a434..9f42f5739a0 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -1272,9 +1272,12 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? * Also, when filter is enabled, the exposure program is set to the Normal program. * * The condition below will result in an error. - * [fileFormat] is raw+ and _filter is Noise reduction, HDR or Handheld HDR - * shootingMethod is except for Normal shooting and [filter] is enabled - * Access during video capture mode + * + * - When attempting to set [filter] to Noise reduction, + * HDR or Handheld HDR while [fileFormat] is set to raw+, + * but this restriction is only for RICOH THETA Z1 firmware v1.80.1 or earlier. + * - [shootingMethod] is except for Normal shooting and [filter] is enabled + * - Access during video capture mode */ var filter: FilterEnum? = null, @@ -5062,6 +5065,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? W1024_H512_F15(1024, 512, 15), // For Theta X. This value can't set. W512_H512_F30(512, 512, 30), // For Theta X W1920_H960_F8(1920, 960, 8), // For Theta Z1 and V + W1920_H960_F30(1920, 960, 30), // For Theta X firmware v2.71.1 or later W1024_H512_F8(1024, 512, 8), // For Theta Z1 and V W640_H320_F30(640, 320, 30), // For Theta Z1 and V W640_H320_F8(640, 320, 8), // For Theta Z1 and V diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/infoApi.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/infoApi.kt index 1986dc831d8..91171e2f75f 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/infoApi.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/infoApi.kt @@ -37,6 +37,10 @@ internal data class InfoApiResponse( /** * MAC address of wireless LAN * (RICOH THETA V firmware v2.11.1 or later) + * + * For THETA X, firmware versions v2.63.0 and earlier display `the communication MAC address`, + * while v2.71.1 and later diplay `the physical MAC address`. + * For other than THETA X, `the physical MAC address` is displayed. */ val _wlanMacAddress: String?, diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt index 218129c4d3d..113c2367111 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt @@ -423,9 +423,11 @@ internal data class Options( * * The condition below will result in an error. * - * fileFormat is raw+ and _filter is Noise reduction, HDR or - * Handheld HDR _shootingMethod is except for Normal shooting and - * _filter is enabled Access during video capture mode + * - When attempting to set [_filter] to Noise reduction, + * HDR or Handheld HDR while [fileFormat] is set to raw+, + * but this restriction is only for RICOH THETA Z1 firmware v1.80.1 or earlier. + * - [_shootingMethod] is except for Normal shooting and [_filter] is enabled + * - Access during video capture mode * * @see ImageFilter */ diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/PreviewFormatTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/PreviewFormatTest.kt index 9ef3e018529..472e9d2475c 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/PreviewFormatTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/PreviewFormatTest.kt @@ -91,6 +91,7 @@ class PreviewFormatTest { Pair(ThetaRepository.PreviewFormatEnum.W1024_H512_F30, PreviewFormat(1024, 512, 30)), Pair(ThetaRepository.PreviewFormatEnum.W1024_H512_F8, PreviewFormat(1024, 512, 8)), Pair(ThetaRepository.PreviewFormatEnum.W1920_H960_F8, PreviewFormat(1920, 960, 8)), + Pair(ThetaRepository.PreviewFormatEnum.W1920_H960_F30, PreviewFormat(1920, 960, 30)), Pair(ThetaRepository.PreviewFormatEnum.W512_H512_F30, PreviewFormat(512, 512, 30)), Pair(ThetaRepository.PreviewFormatEnum.W640_H320_F30, PreviewFormat(640, 320, 30)), Pair(ThetaRepository.PreviewFormatEnum.W640_H320_F10, PreviewFormat(640, 320, 10)), diff --git a/react-native/src/__tests__/options/option-preview-format.test.tsx b/react-native/src/__tests__/options/option-preview-format.test.tsx index 5d140ee9c79..b53a0c4ca89 100644 --- a/react-native/src/__tests__/options/option-preview-format.test.tsx +++ b/react-native/src/__tests__/options/option-preview-format.test.tsx @@ -6,6 +6,7 @@ describe('PreviewFormatEnum', () => { [PreviewFormatEnum.W1024_H512_F15, 'W1024_H512_F15'], [PreviewFormatEnum.W512_H512_F30, 'W512_H512_F30'], [PreviewFormatEnum.W1920_H960_F8, 'W1920_H960_F8'], + [PreviewFormatEnum.W1920_H960_F30, 'W1920_H960_F30'], [PreviewFormatEnum.W1024_H512_F8, 'W1024_H512_F8'], [PreviewFormatEnum.W640_H320_F30, 'W640_H320_F30'], [PreviewFormatEnum.W640_H320_F8, 'W640_H320_F8'], diff --git a/react-native/src/theta-repository/options/option-preview-format.ts b/react-native/src/theta-repository/options/option-preview-format.ts index f8df494b19c..4f2133d1f12 100644 --- a/react-native/src/theta-repository/options/option-preview-format.ts +++ b/react-native/src/theta-repository/options/option-preview-format.ts @@ -11,6 +11,8 @@ export const PreviewFormatEnum = { W512_H512_F30: 'W512_H512_F30', /** For Theta Z1 and V */ W1920_H960_F8: 'W1920_H960_F8', + /** For Theta X firmware v2.71.1 or later */ + W1920_H960_F30: 'W1920_H960_F30', /** For Theta Z1 and V */ W1024_H512_F8: 'W1024_H512_F8', /** For Theta Z1 and V */ diff --git a/react-native/src/theta-repository/theta-info.ts b/react-native/src/theta-repository/theta-info.ts index 1de150a3ebd..28d399057ce 100644 --- a/react-native/src/theta-repository/theta-info.ts +++ b/react-native/src/theta-repository/theta-info.ts @@ -27,7 +27,13 @@ export type ThetaInfo = { model: string; /** Theta serial number */ serialNumber: string; - /** MAC address of wireless LAN (RICOH THETA V firmware v2.11.1 or later) */ + /** MAC address of wireless LAN + * (RICOH THETA V firmware v2.11.1 or later) + * + * For THETA X, firmware versions v2.63.0 and earlier display `the communication MAC address`, + * while v2.71.1 and later diplay `the physical MAC address`. + * For other than THETA X, `the physical MAC address` is displayed. + */ wlanMacAddress: string | null; /** MAC address of Bluetooth (RICOH THETA V firmware v2.11.1 or later) */ bluetoothMacAddress: string | null; diff --git a/react-native/verification-tool/src/screen/options-screen/options-screen.tsx b/react-native/verification-tool/src/screen/options-screen/options-screen.tsx index 3fc32aba0db..24adad8576e 100644 --- a/react-native/verification-tool/src/screen/options-screen/options-screen.tsx +++ b/react-native/verification-tool/src/screen/options-screen/options-screen.tsx @@ -21,6 +21,7 @@ import { OptionNameEnum, Options, PresetEnum, + PreviewFormatEnum, SleepDelayEnum, TopBottomCorrectionOptionEnum, VideoStitchingEnum, @@ -388,6 +389,23 @@ const optionList: OptionItem[] = [ defaultValue: { preset: PresetEnum.FACE }, }, }, + { + name: 'previewFormat', + value: { + optionName: OptionNameEnum.PreviewFormat, + editor: (options, onChange) => ( + { + onChange({ previewFormat }); + }} + optionEnum={PreviewFormatEnum} + /> + ), + defaultValue: { previewFormat: PreviewFormatEnum.W1024_H512_F30 }, + }, + }, { name: 'sleepDelay enum', value: { From b2f479b43007bf77a584aea7864632a53deac762 Mon Sep 17 00:00:00 2001 From: osakila Date: Tue, 18 Mar 2025 14:00:31 +0900 Subject: [PATCH 08/23] Add cancel to update firmware --- .../UpdateFirmwareOnActualTheta.kt | 3 +- .../thetaclient/MultipartPostClient.kt | 39 +++++++++++++++---- .../com/ricoh360/thetaclient/ThetaApi.kt | 2 +- .../ricoh360/thetaclient/ThetaRepository.kt | 11 +++++- .../com/ricoh360/thetaclient/MockApiClient.kt | 2 +- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/kotlin-multiplatform/src/androidUnitTest/kotlin/com/ricoh360/thetaclient/UpdateFirmwareOnActualTheta.kt b/kotlin-multiplatform/src/androidUnitTest/kotlin/com/ricoh360/thetaclient/UpdateFirmwareOnActualTheta.kt index 4d5371d3526..b4a44ca5c14 100644 --- a/kotlin-multiplatform/src/androidUnitTest/kotlin/com/ricoh360/thetaclient/UpdateFirmwareOnActualTheta.kt +++ b/kotlin-multiplatform/src/androidUnitTest/kotlin/com/ricoh360/thetaclient/UpdateFirmwareOnActualTheta.kt @@ -62,8 +62,9 @@ class UpdateFirmwareOnActualTheta { assertTrue(true, "call updateFirmware()") } - fun getSentPercentage(percent: Int) { + fun getSentPercentage(percent: Int): Boolean { println("Sent $percent %") + return true } /** diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/MultipartPostClient.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/MultipartPostClient.kt index 277eef64859..c606bbc072b 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/MultipartPostClient.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/MultipartPostClient.kt @@ -498,7 +498,7 @@ interface MultipartPostClient { filePaths: List, connectionTimeout: Long, socketTimeout: Long, - callback: ((Int) -> Unit)?, + callback: ((Int) -> Boolean)?, boundary: String = BOUNDARY, ): ByteArray } @@ -679,14 +679,25 @@ class MultipartPostClientImpl : MultipartPostClient, BaseHttpClient() { filePaths: List, connectionTimeout: Long, socketTimeout: Long, - callback: ((Int) -> Unit)?, + callback: ((Int) -> Boolean)?, boundary: String, ): ByteArray { val authorizationHeader: String? = checkAuthenticationNeeded(endpoint, path, connectionTimeout, socketTimeout) - requestWithAuth(endpoint, path, filePaths, connectionTimeout, socketTimeout, callback, boundary, authorizationHeader) + val isSuccess = requestWithAuth( + endpoint, + path, + filePaths, + connectionTimeout, + socketTimeout, + callback, + boundary, + authorizationHeader + ) close() val httpStatusCode = HttpStatusCode(this.status, this.statusMessage ?: "") - if (httpStatusCode.isSuccess() || httpStatusCode.value == 0) { + if (!isSuccess) { + throw (BaseHttpClientException("Request canceled")) + } else if (httpStatusCode.isSuccess() || httpStatusCode.value == 0) { return this.responseBody ?: byteArrayOf() } else if (httpStatusCode == HttpStatusCode.NotFound) { throw (BaseHttpClientException("Request failed: ${this.status} ${this.statusMessage}: API path \"$path\" may be wrong")) @@ -720,10 +731,10 @@ class MultipartPostClientImpl : MultipartPostClient, BaseHttpClient() { filePaths: List, connectionTimeout: Long, socketTimeout: Long, - callback: ((Int) -> Unit)?, + callback: ((Int) -> Boolean)?, boundary: String, digest: String? = null, - ) { + ): Boolean { val contentLength = getContentLength(filePaths, boundary) if (!isConnected()) connect(endpoint, connectionTimeout, socketTimeout) writeRequestLine(path, HttpMethod.Post) @@ -731,6 +742,8 @@ class MultipartPostClientImpl : MultipartPostClient, BaseHttpClient() { val buffer = ByteArray(READ_BUFFER_SIZE) var sentCount = 0L var lastPercent = 0 + var isActive = true + var isCanceled = false filePaths.forEach { var src: Source? = null try { @@ -744,16 +757,25 @@ class MultipartPostClientImpl : MultipartPostClient, BaseHttpClient() { callback?.let { val percent = (sentCount * PERCENTAGE_100 / contentLength).toInt() if (percent > lastPercent) { - it(percent) + if (!it(percent)) { + isActive = false + } lastPercent = percent } } count = src.readAtMostTo(buffer, 0, READ_BUFFER_SIZE) - } while (count != -1) + } while (count != -1 && isActive) + if (count != -1 && !isActive) { + isCanceled = true + } } finally { src?.close() } } + if (isCanceled) { + close() + return false + } writeCloseDelimiter(boundary) callback?.let { it(PERCENTAGE_100) @@ -767,6 +789,7 @@ class MultipartPostClientImpl : MultipartPostClient, BaseHttpClient() { } finally { close() } + return true } /** diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt index 3f30cc5855f..c27694b9a8a 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt @@ -176,7 +176,7 @@ internal object ThetaApi { filePaths: List, connectTimeout: Long, socketTimeout: Long, - callback: ((Int) -> Unit)?, + callback: ((Int) -> Boolean)?, ): UpdateFirmwareApiResponse { val DUMMY_RESPONSE = "{\"name\":\"camera.${apiPath}\",\"state\":\"done\"}" if (filePaths.isEmpty()) { diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt index 9f42f5739a0..53c01f9257d 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -460,11 +460,18 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? * @param socketTimeout Timeout (milli seconds) of socket * @Param callback function to pass the percentage of sent firmware. * After sending firmware, several minutes may be needed to start firmware update. + * If callback returns false, the update is canceled * @exception ThetaWebApiException If an error occurs in THETA. * @exception NotConnectedException */ @Throws(Throwable::class) - suspend fun updateFirmware(apiPath: String, filePaths: List, connectionTimeout: Long = 20_000L, socketTimeout: Long = 600_000L, callback: ((Int) -> Unit)? = null) { + suspend fun updateFirmware( + apiPath: String, + filePaths: List, + connectionTimeout: Long = 20_000L, + socketTimeout: Long = 600_000L, + callback: ((Int) -> Boolean)? = null + ) { try { val response = ThetaApi.callUpdateFirmwareApi(endpoint, apiPath, filePaths, connectionTimeout, socketTimeout, callback) response.error?.let { @@ -476,6 +483,8 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? throw ThetaWebApiException(e.toString()) } catch (e: IllegalArgumentException) { throw ThetaWebApiException(e.toString()) + } catch (e: BaseHttpClientException) { + throw ThetaWebApiException(e.message ?: e.toString()) } catch (e: Exception) { throw NotConnectedException(e.toString()) } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/MockApiClient.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/MockApiClient.kt index a4166c0f621..b3474af877a 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/MockApiClient.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/MockApiClient.kt @@ -113,7 +113,7 @@ internal object MockApiClient { filePaths: List, connectionTimeout: Long, socketTimeout: Long, - callback: ((Int) -> Unit)?, + callback: ((Int) -> Boolean)?, boundary: String, ): ByteArray { onMultipartPostRequest?.let { From 0f8f11ebc659a5661030a9e8d8449aaee8519393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B2=A1=E3=80=80=E4=BE=91=E5=87=9B?= <129148471+LassicYM@users.noreply.github.com> Date: Mon, 24 Mar 2025 09:22:01 +0900 Subject: [PATCH 09/23] add option _colorTemperatureSupport. --- .../theta_client_flutter/ConvertUtil.kt | 19 +++- flutter/ios/Classes/ConvertUtil.swift | 13 +++ .../options/color_temperature_support.dart | 21 +++++ flutter/lib/options/importer.dart | 1 + flutter/lib/theta_client_flutter.dart | 11 +++ flutter/lib/utils/convert_utils.dart | 17 ++++ .../ricoh360/thetaclient/ThetaRepository.kt | 53 ++++++++++++ .../transferred/setOptionsCommand.kt | 29 +++++++ .../options/ColorTemperatureSupportTest.kt | 86 +++++++++++++++++++ .../repository/options/OptionsTest.kt | 11 +++ .../option_color_temperature_support.json | 13 +++ .../thetaclientreactnative/Converter.kt | 13 +++ react-native/ios/ConvertUtil.swift | 17 ++++ .../src/theta-repository/options/index.ts | 1 + .../option-color-temperature-support.ts | 11 +++ .../src/theta-repository/options/options.ts | 5 ++ .../screen/options-screen/options-screen.tsx | 6 ++ 17 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 flutter/lib/options/color_temperature_support.dart create mode 100644 kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/ColorTemperatureSupportTest.kt create mode 100644 kotlin-multiplatform/src/commonTest/resources/options/option_color_temperature_support.json create mode 100644 react-native/src/theta-repository/options/option-color-temperature-support.ts diff --git a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt index 881bbe8afe9..3ce22a25096 100644 --- a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt +++ b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt @@ -16,6 +16,9 @@ const val KEY_NOTIFY_PARAM_FILE_URL = "fileUrl" const val KEY_GPS_INFO = "gpsInfo" const val KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" const val KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" +const val KEY_COLOR_TEMPERATURE_SUPPORT_MAX = "maxTemperature" +const val KEY_COLOR_TEMPERATURE_SUPPORT_MIN = "minTemperature" +const val KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE = "stepSize" fun toResult(thetaInfo: ThetaInfo): Map { return mapOf( @@ -220,7 +223,7 @@ fun toResult(burstOption: BurstOption): Map { fun toEthernetConfig(map: Map): EthernetConfig { val proxy = map["proxy"]?.let { @Suppress("UNCHECKED_CAST") - (it as? Map)?.let{ map -> + (it as? Map)?.let { map -> toProxy(map) } } @@ -497,6 +500,14 @@ fun toGetOptionsParam(data: List): List { return optionNames } +fun toResult(colorTemperatureSupport: ColorTemperatureSupport): Map { + return mapOf( + KEY_COLOR_TEMPERATURE_SUPPORT_MAX to colorTemperatureSupport.maxTemperature, + KEY_COLOR_TEMPERATURE_SUPPORT_MIN to colorTemperatureSupport.minTemperature, + KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE to colorTemperatureSupport.stepSize + ) +} + fun toResult(ethernetConfig: EthernetConfig): Map { val result = mutableMapOf() @@ -600,6 +611,10 @@ fun toResult(options: Options): Map { options.getValue(OptionNameEnum.BurstOption)?.let { burstOption -> result[OptionNameEnum.BurstOption.name] = toResult(burstOption) } + } else if (name == OptionNameEnum.ColorTemperatureSupport) { + options.getValue(OptionNameEnum.ColorTemperatureSupport)?.let { colorTemperatureSupport -> + result[OptionNameEnum.ColorTemperatureSupport.name] = toResult(colorTemperatureSupport) + } } else if (name == OptionNameEnum.EthernetConfig) { options.getValue(OptionNameEnum.EthernetConfig)?.let { ethernetConfig -> result[OptionNameEnum.EthernetConfig.name] = toResult(ethernetConfig = ethernetConfig) @@ -961,7 +976,7 @@ fun toSetAccessPointParams(arguments: Map<*, *>): SetAccessPointParams { val ssidStealth = arguments[KEY_SSID_STEALTH] as? Boolean val authMode = (arguments[KEY_AUTH_MODE] as? String?)?.let { AuthModeEnum.valueOf(it) - }?: throw IllegalArgumentException(KEY_AUTH_MODE) + } ?: throw IllegalArgumentException(KEY_AUTH_MODE) val password = arguments[KEY_PASSWORD] as? String val connectionPriority = arguments[KEY_CONNECTION_PRIORITY] as? Int val proxy = (arguments[KEY_PROXY] as? Map<*, *>)?.let { toProxy(it) } diff --git a/flutter/ios/Classes/ConvertUtil.swift b/flutter/ios/Classes/ConvertUtil.swift index 49b3cb70c4f..281c250c1d5 100644 --- a/flutter/ios/Classes/ConvertUtil.swift +++ b/flutter/ios/Classes/ConvertUtil.swift @@ -24,6 +24,9 @@ let KEY_PROXY = "proxy" let KEY_IP_ADDRESS = "ipAddress" let KEY_SUBNET_MASK = "subnetMask" let KEY_DEFAULT_GATEWAY = "defaultGateway" +let KEY_COLOR_TEMPERATURE_SUPPORT_MAX = "maxTemperature" +let KEY_COLOR_TEMPERATURE_SUPPORT_MIN = "minTemperature" +let KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE = "stepSize" public class ConvertUtil: NSObject {} @@ -709,6 +712,14 @@ func convertGetOptionsParam(params: [String]) -> [ThetaRepository.OptionNameEnum return array } +func convertResult(colorTemperatureSupport: ThetaRepository.ColorTemperatureSupport) -> [String: Any] { + return [ + KEY_COLOR_TEMPERATURE_SUPPORT_MAX: colorTemperatureSupport.maxTemperature, + KEY_COLOR_TEMPERATURE_SUPPORT_MIN: colorTemperatureSupport.minTemperature, + KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE: colorTemperatureSupport.stepSize, + ] +} + func convertResult(ethernetConfig: ThetaRepository.EthernetConfig) -> [String: Any] { var result: [String: Any] = [ "usingDhcp": ethernetConfig.usingDhcp @@ -789,6 +800,8 @@ func convertResult(options: ThetaRepository.Options) -> [String: Any] { result[name.name] = convertResult(autoBracket: autoBracket) } else if value is ThetaRepository.BurstOption, let burstOption = value as? ThetaRepository.BurstOption { result[name.name] = convertResult(burstOption: burstOption) + } else if value is ThetaRepository.ColorTemperatureSupport, let colorTemperatureSupport = value as? ThetaRepository.ColorTemperatureSupport { + result[name.name] = convertResult(colorTemperatureSupport: colorTemperatureSupport) } else if value is ThetaRepository.EthernetConfig, let ethernetConfig = value as? ThetaRepository.EthernetConfig { result[name.name] = convertResult(ethernetConfig: ethernetConfig) } else if value is ThetaRepository.GpsInfo { diff --git a/flutter/lib/options/color_temperature_support.dart b/flutter/lib/options/color_temperature_support.dart new file mode 100644 index 00000000000..cb8a9b52bb7 --- /dev/null +++ b/flutter/lib/options/color_temperature_support.dart @@ -0,0 +1,21 @@ +/// supported color temperature. +class ColorTemperatureSupport { + /// maximum value + int maxTemperature; + + /// minimum value + int minTemperature; + + /// step size + int stepSize; + + ColorTemperatureSupport( + this.maxTemperature, this.minTemperature, this.stepSize); + + @override + bool operator ==(Object other) => hashCode == other.hashCode; + + @override + int get hashCode => + Object.hashAll([maxTemperature, minTemperature, stepSize]); +} diff --git a/flutter/lib/options/importer.dart b/flutter/lib/options/importer.dart index fc0f5447f1f..79d1fbc61f0 100644 --- a/flutter/lib/options/importer.dart +++ b/flutter/lib/options/importer.dart @@ -3,6 +3,7 @@ library; export 'bluetooth_role.dart'; export 'camera_power.dart'; +export 'color_temperature_support.dart'; export 'ethernet_config.dart'; export 'file_format.dart'; export 'max_recordable_time.dart'; diff --git a/flutter/lib/theta_client_flutter.dart b/flutter/lib/theta_client_flutter.dart index e89cbb194dc..a6e629b5002 100644 --- a/flutter/lib/theta_client_flutter.dart +++ b/flutter/lib/theta_client_flutter.dart @@ -1262,6 +1262,9 @@ enum OptionNameEnum { /// Option name _colorTemperature colorTemperature('ColorTemperature', int), + /// Option name _colorTemperatureSupport + colorTemperatureSupport('ColorTemperatureSupport', int), + /// Option name _compositeShootingOutputInterval compositeShootingOutputInterval('CompositeShootingOutputInterval', int), @@ -3322,6 +3325,9 @@ class Options { /// 2500 to 10000. In 100-Kelvin units. int? colorTemperature; + /// supported color temperature. + ColorTemperatureSupport? colorTemperatureSupport; + /// In-progress save interval for interval composite shooting (sec). /// /// 0 (no saving), 60 to 600. In 60-second units. @@ -3561,6 +3567,8 @@ class Options { return captureNumber as T; case OptionNameEnum.colorTemperature: return colorTemperature as T; + case OptionNameEnum.colorTemperatureSupport: + return colorTemperatureSupport as T; case OptionNameEnum.compositeShootingOutputInterval: return compositeShootingOutputInterval as T; case OptionNameEnum.compositeShootingTime: @@ -3706,6 +3714,9 @@ class Options { case OptionNameEnum.colorTemperature: colorTemperature = value; break; + case OptionNameEnum.colorTemperatureSupport: + colorTemperatureSupport = value; + break; case OptionNameEnum.compositeShootingOutputInterval: compositeShootingOutputInterval = value; break; diff --git a/flutter/lib/utils/convert_utils.dart b/flutter/lib/utils/convert_utils.dart index 78af22e5fa7..0929dda8b15 100644 --- a/flutter/lib/utils/convert_utils.dart +++ b/flutter/lib/utils/convert_utils.dart @@ -329,6 +329,19 @@ class ConvertUtils { }; } + static ColorTemperatureSupport? convertColorTemperatureSupport( + Map? data) { + if (data == null) { + return null; + } + + return ColorTemperatureSupport( + data['maxTemperature'], + data['minTemperature'], + data['stepSize'], + ); + } + static EthernetConfig? convertEthernetConfig(Map? data) { if (data == null) { return null; @@ -446,6 +459,10 @@ class ConvertUtils { case OptionNameEnum.colorTemperature: result.colorTemperature = entry.value; break; + case OptionNameEnum.colorTemperatureSupport: + result.colorTemperatureSupport = + convertColorTemperatureSupport(entry.value); + break; case OptionNameEnum.compositeShootingOutputInterval: result.compositeShootingOutputInterval = entry.value; break; diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt index 53c01f9257d..210de706565 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -786,6 +786,12 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? */ ColorTemperature("_colorTemperature", Int::class), + /** + * Option name + * _colorTemperatureSupport + */ + ColorTemperatureSupport("_colorTemperatureSupport", ThetaRepository.ColorTemperatureSupport::class), + /** * Option name * _compositeShootingOutputInterval @@ -1187,6 +1193,11 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? */ var colorTemperature: Int? = null, + /** + * supported color temperature. + */ + var colorTemperatureSupport: ColorTemperatureSupport? = null, + /** * In-progress save interval for interval composite shooting (sec). * @@ -1503,6 +1514,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? captureMode = null, captureNumber = null, colorTemperature = null, + colorTemperatureSupport = null, compositeShootingOutputInterval = null, compositeShootingTime = null, continuousNumber = null, @@ -1566,6 +1578,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? captureMode = options.captureMode?.let { CaptureModeEnum.get(it) }, captureNumber = options.captureNumber, colorTemperature = options._colorTemperature, + colorTemperatureSupport = options._colorTemperatureSupport?.let { ColorTemperatureSupport(it) }, compositeShootingOutputInterval = options._compositeShootingOutputInterval, compositeShootingTime = options._compositeShootingTime, continuousNumber = options.continuousNumber?.let { ContinuousNumberEnum.get(it) }, @@ -1638,6 +1651,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? captureMode = captureMode?.value, captureNumber = captureNumber, _colorTemperature = colorTemperature, + _colorTemperatureSupport = colorTemperatureSupport?.toTransferredColorTemperatureSupport(), _compositeShootingOutputInterval = compositeShootingOutputInterval, _compositeShootingTime = compositeShootingTime, continuousNumber = continuousNumber?.value, @@ -1713,6 +1727,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? OptionNameEnum.CaptureMode -> captureMode OptionNameEnum.CaptureNumber -> captureNumber OptionNameEnum.ColorTemperature -> colorTemperature + OptionNameEnum.ColorTemperatureSupport -> colorTemperatureSupport OptionNameEnum.CompositeShootingOutputInterval -> compositeShootingOutputInterval OptionNameEnum.CompositeShootingTime -> compositeShootingTime OptionNameEnum.ContinuousNumber -> continuousNumber @@ -1789,6 +1804,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? OptionNameEnum.CaptureMode -> captureMode = value as CaptureModeEnum OptionNameEnum.CaptureNumber -> captureNumber = value as Int OptionNameEnum.ColorTemperature -> colorTemperature = value as Int + OptionNameEnum.ColorTemperatureSupport -> colorTemperatureSupport = value as ColorTemperatureSupport OptionNameEnum.CompositeShootingOutputInterval -> compositeShootingOutputInterval = value as Int OptionNameEnum.CompositeShootingTime -> compositeShootingTime = value as Int OptionNameEnum.ContinuousNumber -> continuousNumber = value as ContinuousNumberEnum @@ -2736,6 +2752,43 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? } } + /** + * supported color temperature. + */ + data class ColorTemperatureSupport( + /** + * maximum value + */ + val maxTemperature: Int, + /** + * minimum value + */ + val minTemperature: Int, + /** + * step size + */ + val stepSize: Int, + ) { + internal constructor(support: com.ricoh360.thetaclient.transferred.ColorTemperatureSupport) : this( + maxTemperature = support.maxTemperature, + minTemperature = support.minTemperature, + stepSize = support.stepSize + ) + + /** + * Convert ColorTemperatureSupport to transferred.ColorTemperatureSupport + * + * @return transferred.ColorTemperatureSupport + */ + internal fun toTransferredColorTemperatureSupport(): com.ricoh360.thetaclient.transferred.ColorTemperatureSupport { + return com.ricoh360.thetaclient.transferred.ColorTemperatureSupport( + maxTemperature = maxTemperature, + minTemperature = minTemperature, + stepSize = stepSize + ) + } + } + /** * Number of shots for continuous shooting. * It can be acquired by camera.getOptions. diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt index 113c2367111..ae8e732bd1a 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt @@ -264,6 +264,11 @@ internal data class Options( */ var _colorTemperature: Int? = null, + /** + * supported color temperature. + */ + var _colorTemperatureSupport: ColorTemperatureSupport? = null, + /** * In-progress save interval for interval composite shooting (sec). * @@ -2418,3 +2423,27 @@ internal data class CaptureNumberSupport( @Serializable(with = NumberAsIntSerializer::class) val maxNumber: Int? = null, ) + +/** + * supported color temperature. + */ +@Serializable +internal data class ColorTemperatureSupport( + /** + * maximum value + */ + @Serializable(with = NumberAsIntSerializer::class) + val maxTemperature: Int, + + /** + * minimum value + */ + @Serializable(with = NumberAsIntSerializer::class) + val minTemperature: Int, + + /** + * step size + */ + @Serializable(with = NumberAsIntSerializer::class) + val stepSize: Int +) \ No newline at end of file diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/ColorTemperatureSupportTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/ColorTemperatureSupportTest.kt new file mode 100644 index 00000000000..090908a6997 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/ColorTemperatureSupportTest.kt @@ -0,0 +1,86 @@ +package com.ricoh360.thetaclient.repository.options + +import com.goncalossilva.resources.Resource +import com.ricoh360.thetaclient.CheckRequest +import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaRepository +import com.ricoh360.thetaclient.transferred.ColorTemperatureSupport +import com.ricoh360.thetaclient.transferred.Options +import io.ktor.http.HttpStatusCode +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.test.runTest +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ColorTemperatureSupportTest { + private val endpoint = "http://192.168.1.1:80/" + + @BeforeTest + fun setup() { + MockApiClient.status = HttpStatusCode.OK + } + + @AfterTest + fun teardown() { + MockApiClient.status = HttpStatusCode.OK + } + + /** + * Get option. + */ + @Test + fun getOption() = runTest { + val optionNames = listOf( + ThetaRepository.OptionNameEnum.ColorTemperatureSupport + ) + val stringOptionNames = listOf( + "_colorTemperatureSupport" + ) + + MockApiClient.onRequest = { request -> + // check request + CheckRequest.checkGetOptions(request, stringOptionNames) + + ByteReadChannel(Resource("src/commonTest/resources/options/option_color_temperature_support.json").readText()) + } + + val thetaRepository = ThetaRepository(endpoint) + val options = thetaRepository.getOptions(optionNames) + assertEquals(options.colorTemperatureSupport?.maxTemperature, 10000, "maxTemperature") + assertEquals(options.colorTemperatureSupport?.minTemperature, 2500, "minTemperature") + assertEquals(options.colorTemperatureSupport?.stepSize, 100, "stepSize") + } + + /** + * Convert ThetaRepository.Options to Options. + */ + @Test + fun convertOptionTest() = runTest { + val values = Pair( + ColorTemperatureSupport(10000, 3000, 100), + ThetaRepository.ColorTemperatureSupport(10000, 3000, 100) + ) + + val orgOptions = Options( + _colorTemperatureSupport = values.first + ) + val optionsTR = ThetaRepository.Options(orgOptions) + assertEquals( + optionsTR.colorTemperatureSupport, + values.second, + "colorTemperatureSupport ${values.second}" + ) + + val orgOptionsTR = ThetaRepository.Options( + colorTemperatureSupport = values.second + ) + val options = orgOptionsTR.toOptions() + assertEquals( + options._colorTemperatureSupport, + values.first, + "_colorTemperatureSupport ${values.first}" + ) + } +} \ No newline at end of file diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt index 0e851864817..6ca3107f356 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt @@ -18,6 +18,7 @@ import com.ricoh360.thetaclient.transferred.CameraControlSource import com.ricoh360.thetaclient.transferred.CameraMode import com.ricoh360.thetaclient.transferred.CameraPower import com.ricoh360.thetaclient.transferred.CaptureMode +import com.ricoh360.thetaclient.transferred.ColorTemperatureSupport import com.ricoh360.thetaclient.transferred.EthernetConfig import com.ricoh360.thetaclient.transferred.FaceDetect import com.ricoh360.thetaclient.transferred.FirstShootingEnum @@ -101,6 +102,7 @@ class OptionsTest { val captureMode = ThetaRepository.CaptureModeEnum.IMAGE val captureNumber = 0 val colorTemperature = 10 + val colorTemperatureSupport = ThetaRepository.ColorTemperatureSupport(10000, 2000, 100) val compositeShootingOutputInterval = 60 val compositeShootingTime = 600 val continuousNumber = ThetaRepository.ContinuousNumberEnum.MAX_1 @@ -173,6 +175,7 @@ class OptionsTest { captureMode = captureMode, captureNumber = captureNumber, colorTemperature = colorTemperature, + colorTemperatureSupport = colorTemperatureSupport, compositeShootingOutputInterval = compositeShootingOutputInterval, compositeShootingTime = compositeShootingTime, continuousNumber = continuousNumber, @@ -239,6 +242,7 @@ class OptionsTest { assertEquals(options.getValue(ThetaRepository.OptionNameEnum.CaptureMode), captureMode, "captureMode") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.CaptureNumber), captureNumber, "captureNumber") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.ColorTemperature), colorTemperature, "colorTemperature") + assertEquals(options.getValue(ThetaRepository.OptionNameEnum.ColorTemperatureSupport), colorTemperatureSupport, "colorTemperatureSupport") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.CompositeShootingOutputInterval), compositeShootingOutputInterval, "compositeShootingOutputInterval") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.CompositeShootingTime), compositeShootingTime, "compositeShootingTime") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.ContinuousNumber), continuousNumber, "continuousNumber") @@ -333,6 +337,7 @@ class OptionsTest { Pair(ThetaRepository.OptionNameEnum.CaptureMode, ThetaRepository.CaptureModeEnum.IMAGE), Pair(ThetaRepository.OptionNameEnum.CaptureNumber, 0), Pair(ThetaRepository.OptionNameEnum.ColorTemperature, 10), + Pair(ThetaRepository.OptionNameEnum.ColorTemperatureSupport, ThetaRepository.ColorTemperatureSupport(10000, 2000, 100)), Pair(ThetaRepository.OptionNameEnum.CompositeShootingOutputInterval, 60), Pair(ThetaRepository.OptionNameEnum.CompositeShootingTime, 600), Pair(ThetaRepository.OptionNameEnum.DateTimeZone, "2014:05:18 01:04:29+08:00"), @@ -465,6 +470,7 @@ class OptionsTest { val captureMode = Pair(CaptureMode.IMAGE, ThetaRepository.CaptureModeEnum.IMAGE) val captureNumber = Pair(9999, 9999) val colorTemperature = Pair(10, 10) + val colorTemperatureSupport = Pair(ColorTemperatureSupport(10000, 2000, 100), ThetaRepository.ColorTemperatureSupport(10000, 2000, 100)) val compositeShootingOutputInterval = Pair(60, 60) val compositeShootingTime = Pair(600, 600) val dateTimeZone = Pair("2014:05:18 01:04:29+08:00", "2014:05:18 01:04:29+08:00") @@ -546,6 +552,7 @@ class OptionsTest { captureMode = captureMode.first, captureNumber = captureNumber.first, _colorTemperature = colorTemperature.first, + _colorTemperatureSupport = colorTemperatureSupport.first, _compositeShootingOutputInterval = compositeShootingOutputInterval.first, _compositeShootingTime = compositeShootingTime.first, dateTimeZone = dateTimeZone.first, @@ -607,6 +614,7 @@ class OptionsTest { assertEquals(options.captureMode, captureMode.second, "captureMode") assertEquals(options.captureNumber, captureNumber.second, "captureNumber") assertEquals(options.colorTemperature, colorTemperature.second, "colorTemperature") + assertEquals(options.colorTemperatureSupport, colorTemperatureSupport.second, "colorTemperatureSupport") assertEquals(options.compositeShootingOutputInterval, compositeShootingOutputInterval.second, "compositeShootingOutputInterval") assertEquals(options.compositeShootingTime, compositeShootingTime.second, "compositeShootingTime") assertEquals(options.dateTimeZone, dateTimeZone.second, "dateTimeZone") @@ -714,6 +722,7 @@ class OptionsTest { val captureMode = Pair(CaptureMode.IMAGE, ThetaRepository.CaptureModeEnum.IMAGE) val captureNumber = Pair(30, 30) val colorTemperature = Pair(10, 10) + val colorTemperatureSupport = Pair(ColorTemperatureSupport(10000, 2000, 100), ThetaRepository.ColorTemperatureSupport(10000, 2000, 100)) val compositeShootingOutputInterval = Pair(60, 60) val compositeShootingTime = Pair(600, 600) val dateTimeZone = Pair("2014:05:18 01:04:29+08:00", "2014:05:18 01:04:29+08:00") @@ -798,6 +807,7 @@ class OptionsTest { captureMode = captureMode.second, captureNumber = captureNumber.second, colorTemperature = colorTemperature.second, + colorTemperatureSupport = colorTemperatureSupport.second, compositeShootingOutputInterval = compositeShootingOutputInterval.second, compositeShootingTime = compositeShootingTime.second, dateTimeZone = dateTimeZone.second, @@ -859,6 +869,7 @@ class OptionsTest { assertEquals(options.captureMode, captureMode.first, "captureMode") assertEquals(options.captureNumber, captureNumber.first, "captureNumber") assertEquals(options._colorTemperature, colorTemperature.first, "colorTemperature") + assertEquals(options._colorTemperatureSupport, colorTemperatureSupport.first, "colorTemperatureSupport") assertEquals(options._compositeShootingOutputInterval, compositeShootingOutputInterval.first, "compositeShootingOutputInterval") assertEquals(options._compositeShootingTime, compositeShootingTime.first, "compositeShootingTime") assertEquals(options.dateTimeZone, dateTimeZone.first, "dateTimeZone") diff --git a/kotlin-multiplatform/src/commonTest/resources/options/option_color_temperature_support.json b/kotlin-multiplatform/src/commonTest/resources/options/option_color_temperature_support.json new file mode 100644 index 00000000000..9aac30491b1 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/options/option_color_temperature_support.json @@ -0,0 +1,13 @@ +{ + "name": "camera.getOptions", + "results": { + "options": { + "_colorTemperatureSupport": { + "maxTemperature": 10000, + "minTemperature": 2500, + "stepSize": 100 + } + } + }, + "state": "done" +} diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt index 92b62841d3d..19114f9b1f8 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt @@ -49,6 +49,7 @@ val optionItemNameToEnum: Map = mutableMapOf( "captureMode" to OptionNameEnum.CaptureMode, "captureNumber" to OptionNameEnum.CaptureNumber, "colorTemperature" to OptionNameEnum.ColorTemperature, + "colorTemperatureSupport" to OptionNameEnum.ColorTemperatureSupport, "compositeShootingOutputInterval" to OptionNameEnum.CompositeShootingOutputInterval, "compositeShootingTime" to OptionNameEnum.CompositeShootingTime, "continuousNumber" to OptionNameEnum.ContinuousNumber, @@ -359,6 +360,10 @@ fun toResult(options: Options): WritableMap { options.burstOption?.let { result.putMap("burstOption", toResult(burstOption = it)) } + } else if (name == OptionNameEnum.ColorTemperatureSupport) { + options.colorTemperatureSupport?.let { + result.putMap("colorTemperatureSupport", toResult(colorTemperatureSupport = it)) + } } else if (name == OptionNameEnum.EthernetConfig) { options.ethernetConfig?.let { result.putMap("ethernetConfig", toResult(ethernetConfig = it)) @@ -550,6 +555,14 @@ fun toResult(fileInfo: FileInfo): ReadableMap { return result } +fun toResult(colorTemperatureSupport: ColorTemperatureSupport): WritableMap { + val result = Arguments.createMap() + result.putInt("maxTemperature", colorTemperatureSupport.maxTemperature) + result.putInt("minTemperature", colorTemperatureSupport.minTemperature) + result.putInt("stepSize", colorTemperatureSupport.stepSize) + return result +} + fun toResult(ethernetConfig: EthernetConfig): WritableMap { val result = Arguments.createMap() result.putBoolean("usingDhcp", ethernetConfig.usingDhcp) diff --git a/react-native/ios/ConvertUtil.swift b/react-native/ios/ConvertUtil.swift index a3fc7b694ce..06d11fbfab0 100644 --- a/react-native/ios/ConvertUtil.swift +++ b/react-native/ios/ConvertUtil.swift @@ -77,6 +77,10 @@ let KEY_SSID_STEALTH = "ssidStealth" let KEY_CONNECTION_PRIORITY = "connectionPriority" let KEY_AUTH_MODE = "authMode" let KEY_PASSWORD = "password" +let KEY_COLOR_TEMPERATURE_SUPPORT = "colorTemperatureSupport" +let KEY_COLOR_TEMPERATURE_SUPPORT_MAX = "maxTemperature" +let KEY_COLOR_TEMPERATURE_SUPPORT_MIN = "minTemperature" +let KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE = "stepSize" public class ConvertUtil: NSObject {} @@ -99,6 +103,7 @@ let optionItemNameToEnum = [ "captureMode": ThetaRepository.OptionNameEnum.capturemode, "captureNumber": ThetaRepository.OptionNameEnum.capturenumber, "colorTemperature": ThetaRepository.OptionNameEnum.colortemperature, + "colorTemperatureSupport": ThetaRepository.OptionNameEnum.colortemperaturesupport, "compositeShootingOutputInterval": ThetaRepository.OptionNameEnum .compositeshootingoutputinterval, "compositeShootingTime": ThetaRepository.OptionNameEnum.compositeshootingtime, @@ -407,6 +412,10 @@ func convertResult(options: ThetaRepository.Options) -> [String: Any] { let burstOption = value as? ThetaRepository.BurstOption { result[key] = convertResult(burstOption: burstOption) + } else if value is ThetaRepository.ColorTemperatureSupport, + let colorTemperatureSupport = value as? ThetaRepository.ColorTemperatureSupport + { + result[key] = convertResult(colorTemperatureSupport: colorTemperatureSupport) } else if value is ThetaRepository.EthernetConfig, let ethernetConfig = value as? ThetaRepository.EthernetConfig { @@ -1020,6 +1029,14 @@ func convertResult(autoBracket: ThetaRepository.BracketSettingList) -> [[String: return resultList } +func convertResult(colorTemperatureSupport: ThetaRepository.ColorTemperatureSupport) -> [String: Any] { + var result: [String: Any] = [:] + result[KEY_COLOR_TEMPERATURE_SUPPORT_MAX] = colorTemperatureSupport.maxTemperature + result[KEY_COLOR_TEMPERATURE_SUPPORT_MIN] = colorTemperatureSupport.minTemperature + result[KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE] = colorTemperatureSupport.stepSize + return result +} + func convertResult(ethernetConfig: ThetaRepository.EthernetConfig) -> [String: Any] { var result: [String: Any] = ["usingDhcp": ethernetConfig.usingDhcp] if let ipAddress = ethernetConfig.ipAddress { diff --git a/react-native/src/theta-repository/options/index.ts b/react-native/src/theta-repository/options/index.ts index 781aa3a04d1..48162d08cdd 100644 --- a/react-native/src/theta-repository/options/index.ts +++ b/react-native/src/theta-repository/options/index.ts @@ -10,6 +10,7 @@ export * from './option-camera-mode'; export * from './option-camera-power'; export * from './option-capture-mode'; export * from './option-continuous-number'; +export * from './option-color-temperature-support'; export * from './option-face-detect'; export * from './option-file-format'; export * from './option-filter'; diff --git a/react-native/src/theta-repository/options/option-color-temperature-support.ts b/react-native/src/theta-repository/options/option-color-temperature-support.ts new file mode 100644 index 00000000000..54e4388cd8e --- /dev/null +++ b/react-native/src/theta-repository/options/option-color-temperature-support.ts @@ -0,0 +1,11 @@ +/** + * supported color temperature. + */ +export type ColorTemperatureSupport = { + /** maximum value */ + maxTemperature: number; + /** minimum value */ + minTemperature: number; + /** step size */ + stepSize: number; +}; diff --git a/react-native/src/theta-repository/options/options.ts b/react-native/src/theta-repository/options/options.ts index 470f5e458dd..16148d98e27 100644 --- a/react-native/src/theta-repository/options/options.ts +++ b/react-native/src/theta-repository/options/options.ts @@ -33,6 +33,7 @@ import type { SleepDelayEnum } from './option-sleep-delay'; import type { EthernetConfig } from './option-ethernet-config'; import type { FileFormatEnum } from './option-file-format'; import type { CameraPowerEnum } from './option-camera-power'; +import type { ColorTemperatureSupport } from './option-color-temperature-support'; /** Aperture value. */ export const ApertureEnum = { @@ -359,6 +360,8 @@ export const OptionNameEnum = { CaptureNumber: 'CaptureNumber', /** colorTemperature */ ColorTemperature: 'ColorTemperature', + /** _colorTemperatureSupport */ + ColorTemperatureSupport: 'ColorTemperatureSupport', /** _compositeShootingOutputInterval */ CompositeShootingOutputInterval: 'CompositeShootingOutputInterval', /** _compositeShootingTime */ @@ -544,6 +547,8 @@ export type Options = { compositeShootingTime?: number; /** Number of shots for continuous shooting. */ continuousNumber?: ContinuousNumberEnum; + /** supported color temperature. */ + colorTemperatureSupport?: ColorTemperatureSupport; /** Current system time of RICOH THETA. Setting another options will result in an error. */ dateTimeZone?: string; /** IP address allocation to be used when wired LAN is enabled. */ diff --git a/react-native/verification-tool/src/screen/options-screen/options-screen.tsx b/react-native/verification-tool/src/screen/options-screen/options-screen.tsx index 24adad8576e..ae4e19e1f8f 100644 --- a/react-native/verification-tool/src/screen/options-screen/options-screen.tsx +++ b/react-native/verification-tool/src/screen/options-screen/options-screen.tsx @@ -150,6 +150,12 @@ const optionList: OptionItem[] = [ ), }, }, + { + name: 'colorTemperatureSupport', + value: { + optionName: OptionNameEnum.ColorTemperatureSupport, + }, + }, { name: 'continuousNumber', value: { From 036595c260cff7bbd2737b10ccc3c4c1c3ca07ca Mon Sep 17 00:00:00 2001 From: osakila Date: Mon, 31 Mar 2025 09:17:15 +0900 Subject: [PATCH 10/23] Change runs-os to macos-latest --- .github/workflows/buildAndTest.yaml | 2 +- .github/workflows/test-flutter.yaml | 2 +- .github/workflows/test-kmp.yaml | 2 +- .github/workflows/test-rn.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/buildAndTest.yaml b/.github/workflows/buildAndTest.yaml index f9eb1ea087d..70eeb8a16f0 100644 --- a/.github/workflows/buildAndTest.yaml +++ b/.github/workflows/buildAndTest.yaml @@ -12,7 +12,7 @@ permissions: jobs: build: - runs-on: macos-12 + runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 diff --git a/.github/workflows/test-flutter.yaml b/.github/workflows/test-flutter.yaml index 05679503cb1..812bdf68e0c 100644 --- a/.github/workflows/test-flutter.yaml +++ b/.github/workflows/test-flutter.yaml @@ -9,7 +9,7 @@ permissions: jobs: build: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: flutter diff --git a/.github/workflows/test-kmp.yaml b/.github/workflows/test-kmp.yaml index fe48cce7a54..71bbf96e6f3 100644 --- a/.github/workflows/test-kmp.yaml +++ b/.github/workflows/test-kmp.yaml @@ -9,7 +9,7 @@ permissions: jobs: build: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 diff --git a/.github/workflows/test-rn.yaml b/.github/workflows/test-rn.yaml index a6eb9516ebe..07bfd1e32e6 100644 --- a/.github/workflows/test-rn.yaml +++ b/.github/workflows/test-rn.yaml @@ -9,7 +9,7 @@ permissions: jobs: build: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Use Node.js From 1d97f04a4423ef9dbff574287c33091c6d372b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B2=A1=E3=80=80=E4=BE=91=E5=87=9B?= <129148471+LassicYM@users.noreply.github.com> Date: Tue, 1 Apr 2025 18:37:12 +0900 Subject: [PATCH 11/23] add option _topBottomCorrectionRotationSupport. --- .../theta_client_flutter/ConvertUtil.kt | 59 +++++-- flutter/ios/Classes/ConvertUtil.swift | 33 ++++ flutter/lib/options/importer.dart | 3 + .../lib/options/top_bottom_correction.dart | 48 ++++++ .../top_bottom_correction_rotation.dart | 23 +++ ...op_bottom_correction_rotation_support.dart | 59 +++++++ flutter/lib/theta_client_flutter.dart | 85 ++-------- flutter/lib/utils/convert_utils.dart | 34 ++++ ...ttom_correction_rotation_support_test.dart | 86 +++++++++++ .../ricoh360/thetaclient/ThetaRepository.kt | 146 +++++++++++++++++- .../transferred/setOptionsCommand.kt | 95 +++++++++++- .../com/ricoh360/thetaclient/CheckRequest.kt | 4 + .../repository/options/OptionsTest.kt | 48 +++++- .../TopBottomCorrectionRotationSupportTest.kt | 104 +++++++++++++ .../TopBottomCorrectionRotationTest.kt | 5 +- ...op_bottom_correction_rotation_support.json | 25 +++ ...n_top_bottom_correction_rotation_zero.json | 14 +- .../thetaclientreactnative/Converter.kt | 95 +++++++++++- react-native/ios/ConvertUtil.swift | 63 +++++++- .../capture/continuous-capture.test.ts | 2 +- ...ottom-correction-rotation-support.test.tsx | 44 ++++++ .../src/capture/continuous-capture.ts | 6 +- .../theta-repository/libs/convert-utils.ts | 27 ++++ .../src/theta-repository/libs/index.ts | 1 + .../src/theta-repository/options/index.ts | 1 + ...-top-bottom-correction-rotation-support.ts | 35 +++++ .../src/theta-repository/options/options.ts | 5 + .../src/theta-repository/theta-repository.ts | 11 +- .../screen/options-screen/options-screen.tsx | 32 ++-- .../src/screen/options-screen/styles.tsx | 6 +- 30 files changed, 1065 insertions(+), 134 deletions(-) create mode 100644 flutter/lib/options/top_bottom_correction.dart create mode 100644 flutter/lib/options/top_bottom_correction_rotation.dart create mode 100644 flutter/lib/options/top_bottom_correction_rotation_support.dart create mode 100644 flutter/test/options/option_top_bottom_correction_rotation_support_test.dart create mode 100644 kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationSupportTest.kt create mode 100644 kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_support.json create mode 100644 react-native/src/__tests__/options/option-top-bottom-correction-rotation-support.test.tsx create mode 100644 react-native/src/theta-repository/libs/convert-utils.ts create mode 100644 react-native/src/theta-repository/options/option-top-bottom-correction-rotation-support.ts diff --git a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt index 3ce22a25096..5f67b451d11 100644 --- a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt +++ b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt @@ -19,6 +19,28 @@ const val KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" const val KEY_COLOR_TEMPERATURE_SUPPORT_MAX = "maxTemperature" const val KEY_COLOR_TEMPERATURE_SUPPORT_MIN = "minTemperature" const val KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE = "stepSize" +const val KEY_WLAN_FREQUENCY_CL_MODE_2_4 = "enable2_4" +const val KEY_WLAN_FREQUENCY_CL_MODE_5_2 = "enable5_2" +const val KEY_WLAN_FREQUENCY_CL_MODE_5_8 = "enable5_8" +const val KEY_IP_ADDRESS = "ipAddress" +const val KEY_MAC_ADDRESS = "macAddress" +const val KEY_HOST_NAME = "hostName" +const val KEY_DHCP_LEASE_ADDRESS = "dhcpLeaseAddress" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH = "pitch" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL = "roll" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW = "yaw" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_SUPPORT = "topBottomCorrectionRotationSupport" +const val KEY_MAX = "max" +const val KEY_MIN = "min" +const val KEY_STEP_SIZE = "stepSize" +const val KEY_SSID = "ssid" +const val KEY_SSID_STEALTH = "ssidStealth" +const val KEY_AUTH_MODE = "authMode" +const val KEY_PASSWORD = "password" +const val KEY_CONNECTION_PRIORITY = "connectionPriority" +const val KEY_SUBNET_MASK = "subnetMask" +const val KEY_DEFAULT_GATEWAY = "defaultGateway" +const val KEY_PROXY = "proxy" fun toResult(thetaInfo: ThetaInfo): Map { return mapOf( @@ -569,12 +591,29 @@ fun toResult(timeShift: TimeShiftSetting): Map { fun toResult(rotation: TopBottomCorrectionRotation): Map { return mapOf( - "pitch" to rotation.pitch, - "roll" to rotation.roll, - "yaw" to rotation.yaw + KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH to rotation.pitch, + KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL to rotation.roll, + KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW to rotation.yaw ) } +fun toResult(topBottomCorrectionRotationSupport: TopBottomCorrectionRotationSupport): Map { + return mapOf( + KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH to toResult(rotationValueSupport = topBottomCorrectionRotationSupport.pitch), + KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL to toResult(rotationValueSupport = topBottomCorrectionRotationSupport.roll), + KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW to toResult(rotationValueSupport = topBottomCorrectionRotationSupport.yaw) + ) +} + +fun toResult(rotationValueSupport: TopBottomCorrectionRotationValueSupport): Map { + return mapOf( + KEY_MAX to rotationValueSupport.max.toString(), + KEY_MIN to rotationValueSupport.min.toString(), + KEY_STEP_SIZE to rotationValueSupport.stepSize.toString() + ) +} + + fun toResult(options: Options): Map { val result = mutableMapOf() @@ -645,6 +684,10 @@ fun toResult(options: Options): Map { options.getValue(OptionNameEnum.TopBottomCorrectionRotation)?.let { rotation -> result[OptionNameEnum.TopBottomCorrectionRotation.name] = toResult(rotation) } + } else if (name == OptionNameEnum.TopBottomCorrectionRotationSupport) { + options.getValue(OptionNameEnum.TopBottomCorrectionRotationSupport)?.let { support -> + result[OptionNameEnum.TopBottomCorrectionRotationSupport.name] = toResult(topBottomCorrectionRotationSupport = support) + } } else if (valueOptions.contains(name)) { addOptionsValueToMap(options, name, result) } else { @@ -961,16 +1004,6 @@ data class SetAccessPointParams( val proxy: Proxy?, ) -const val KEY_SSID = "ssid" -const val KEY_SSID_STEALTH = "ssidStealth" -const val KEY_AUTH_MODE = "authMode" -const val KEY_PASSWORD = "password" -const val KEY_CONNECTION_PRIORITY = "connectionPriority" -const val KEY_IP_ADDRESS = "ipAddress" -const val KEY_SUBNET_MASK = "subnetMask" -const val KEY_DEFAULT_GATEWAY = "defaultGateway" -const val KEY_PROXY = "proxy" - fun toSetAccessPointParams(arguments: Map<*, *>): SetAccessPointParams { val ssid = arguments[KEY_SSID] as? String ?: throw IllegalArgumentException(KEY_SSID) val ssidStealth = arguments[KEY_SSID_STEALTH] as? Boolean diff --git a/flutter/ios/Classes/ConvertUtil.swift b/flutter/ios/Classes/ConvertUtil.swift index 281c250c1d5..4e07bb9df0b 100644 --- a/flutter/ios/Classes/ConvertUtil.swift +++ b/flutter/ios/Classes/ConvertUtil.swift @@ -27,6 +27,16 @@ let KEY_DEFAULT_GATEWAY = "defaultGateway" let KEY_COLOR_TEMPERATURE_SUPPORT_MAX = "maxTemperature" let KEY_COLOR_TEMPERATURE_SUPPORT_MIN = "minTemperature" let KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE = "stepSize" +let KEY_MAC_ADDRESS = "macAddress" +let KEY_HOST_NAME = "hostName" +let KEY_DHCP_LEASE_ADDRESS = "dhcpLeaseAddress" +let KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH = "pitch" +let KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL = "roll" +let KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW = "yaw" +let KEY_TOP_BOTTOM_CORRECTION_ROTATION_SUPPORT = "topBottomCorrectionRotationSupport" +let KEY_MAX = "max" +let KEY_MIN = "min" +let KEY_STEP_SIZE = "stepSize" public class ConvertUtil: NSObject {} @@ -781,6 +791,27 @@ func convertResult(rotation: ThetaRepository.TopBottomCorrectionRotation) -> [St ] } +func convertResult(topBottomCorrectionRotationSupport: ThetaRepository.TopBottomCorrectionRotationSupport) -> [String: Any] { + return [ + KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH: [ + KEY_MAX: String(topBottomCorrectionRotationSupport.pitch.max), + KEY_MIN: String(topBottomCorrectionRotationSupport.pitch.min), + KEY_STEP_SIZE: String(topBottomCorrectionRotationSupport.pitch.stepSize) + ], + KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL: [ + KEY_MAX: String(topBottomCorrectionRotationSupport.roll.max), + KEY_MIN: String(topBottomCorrectionRotationSupport.roll.min), + KEY_STEP_SIZE: String(topBottomCorrectionRotationSupport.roll.stepSize) + ], + KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW: [ + KEY_MAX: String(topBottomCorrectionRotationSupport.yaw.max), + KEY_MIN: String(topBottomCorrectionRotationSupport.yaw.min), + KEY_STEP_SIZE: String(topBottomCorrectionRotationSupport.yaw.stepSize) + ] + ] + +} + func convertResult(options: ThetaRepository.Options) -> [String: Any] { var result = [String: Any]() let nameList = ThetaRepository.OptionNameEnum.values() @@ -825,6 +856,8 @@ func convertResult(options: ThetaRepository.Options) -> [String: Any] { result[name.name] = convertResult(timeshift: timeshift) } else if value is ThetaRepository.TopBottomCorrectionRotation, let rotation = value as? ThetaRepository.TopBottomCorrectionRotation { result[name.name] = convertResult(rotation: rotation) + } else if value is ThetaRepository.TopBottomCorrectionRotationSupport, let support = value as? ThetaRepository.TopBottomCorrectionRotationSupport { + result[name.name] = convertResult(topBottomCorrectionRotationSupport: support) } } } diff --git a/flutter/lib/options/importer.dart b/flutter/lib/options/importer.dart index 79d1fbc61f0..8a4544a1675 100644 --- a/flutter/lib/options/importer.dart +++ b/flutter/lib/options/importer.dart @@ -9,3 +9,6 @@ export 'file_format.dart'; export 'max_recordable_time.dart'; export 'off_delay.dart'; export 'sleep_delay.dart'; +export 'top_bottom_correction.dart'; +export 'top_bottom_correction_rotation.dart'; +export 'top_bottom_correction_rotation_support.dart'; diff --git a/flutter/lib/options/top_bottom_correction.dart b/flutter/lib/options/top_bottom_correction.dart new file mode 100644 index 00000000000..4a19ff3af2c --- /dev/null +++ b/flutter/lib/options/top_bottom_correction.dart @@ -0,0 +1,48 @@ +/// top bottom correction +/// +/// Sets the top/bottom correction. For RICOH THETA V and RICOH +/// THETA Z1, the top/bottom correction can be set only for still +/// images. For RICOH THETA X, the top/bottom correction can be +/// set for both still images and videos. +enum TopBottomCorrectionOptionEnum { + /// Top/bottom correction is performed. + apply('APPLY'), + + /// Refer to top/bottom correction when shooting with "ApplyAuto" + applyAuto('APPLY_AUTO'), + + /// Top/bottom correction is performed. The parameters used for + /// top/bottom correction for the first image are saved and used + /// for the 2nd and subsequent images.(RICOH THETA X or later) + applySemiauto('APPLY_SEMIAUTO'), + + /// Performs top/bottom correction and then saves the parameters. + applySave('APPLY_SAVE'), + + /// Performs top/bottom correction using the saved parameters. + applyLoad('APPLY_LOAD'), + + /// Does not perform top/bottom correction. + disapply('DISAPPLY'), + + /// Performs the top/bottom correction with the specified front + /// position. The front position can be specified with + /// _topBottomCorrectionRotation. + manual('MANUAL'); + + final String rawValue; + + const TopBottomCorrectionOptionEnum(this.rawValue); + + @override + String toString() { + return rawValue; + } + + static TopBottomCorrectionOptionEnum? getValue(String rawValue) { + return TopBottomCorrectionOptionEnum.values + .cast() + .firstWhere((element) => element?.rawValue == rawValue, + orElse: () => null); + } +} diff --git a/flutter/lib/options/top_bottom_correction_rotation.dart b/flutter/lib/options/top_bottom_correction_rotation.dart new file mode 100644 index 00000000000..03d7a158e08 --- /dev/null +++ b/flutter/lib/options/top_bottom_correction_rotation.dart @@ -0,0 +1,23 @@ +/// Sets the front position for the top/bottom correction. +/// Enabled only for _topBottomCorrection Manual. +class TopBottomCorrectionRotation { + /// Specifies the pitch. + /// Specified range is -90.0 to +90.0, stepSize is 0.1 + double pitch; + + /// Specifies the roll. + /// Specified range is -180.0 to +180.0, stepSize is 0.1 + double roll; + + /// Specifies the yaw. + /// Specified range is -180.0 to +180.0, stepSize is 0.1 + double yaw; + + TopBottomCorrectionRotation(this.pitch, this.roll, this.yaw); + + @override + bool operator ==(Object other) => hashCode == other.hashCode; + + @override + int get hashCode => Object.hashAll([pitch, roll, yaw]); +} diff --git a/flutter/lib/options/top_bottom_correction_rotation_support.dart b/flutter/lib/options/top_bottom_correction_rotation_support.dart new file mode 100644 index 00000000000..cf999cc56a9 --- /dev/null +++ b/flutter/lib/options/top_bottom_correction_rotation_support.dart @@ -0,0 +1,59 @@ +/// Supported TopBottomCorrectionRotation +class TopBottomCorrectionRotationSupport { + /// Supported pitch + final TopBottomCorrectionRotationValueSupport pitch; + + /// Supported roll + final TopBottomCorrectionRotationValueSupport roll; + + /// Supported yaw + final TopBottomCorrectionRotationValueSupport yaw; + + TopBottomCorrectionRotationSupport({ + required this.pitch, + required this.roll, + required this.yaw, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TopBottomCorrectionRotationSupport && + runtimeType == other.runtimeType && + pitch == other.pitch && + roll == other.roll && + yaw == other.yaw; + + @override + int get hashCode => Object.hash(pitch, roll, yaw); +} + +/// Supported value of TopBottomCorrectionRotation +class TopBottomCorrectionRotationValueSupport { + /// maximum value + final double max; + + /// minimum value + final double min; + + /// step size + final double stepSize; + + TopBottomCorrectionRotationValueSupport({ + required this.max, + required this.min, + required this.stepSize, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TopBottomCorrectionRotationValueSupport && + runtimeType == other.runtimeType && + max == other.max && + min == other.min && + stepSize == other.stepSize; + + @override + int get hashCode => Object.hash(max, min, stepSize); +} diff --git a/flutter/lib/theta_client_flutter.dart b/flutter/lib/theta_client_flutter.dart index a6e629b5002..7c15da454ca 100644 --- a/flutter/lib/theta_client_flutter.dart +++ b/flutter/lib/theta_client_flutter.dart @@ -1383,6 +1383,10 @@ enum OptionNameEnum { topBottomCorrectionRotation( 'TopBottomCorrectionRotation', TopBottomCorrectionRotation), + /// Option name topBottomCorrectionRotationSupport + topBottomCorrectionRotationSupport( + 'TopBottomCorrectionRotationSupport', TopBottomCorrectionRotationSupport), + /// Option name totalSpace totalSpace('TotalSpace', int), @@ -2967,55 +2971,6 @@ enum TimeShiftIntervalEnum { } } -/// top bottom correction -/// -/// Sets the top/bottom correction. For RICOH THETA V and RICOH -/// THETA Z1, the top/bottom correction can be set only for still -/// images. For RICOH THETA X, the top/bottom correction can be -/// set for both still images and videos. -enum TopBottomCorrectionOptionEnum { - /// Top/bottom correction is performed. - apply('APPLY'), - - /// Refer to top/bottom correction when shooting with "ApplyAuto" - applyAuto('APPLY_AUTO'), - - /// Top/bottom correction is performed. The parameters used for - /// top/bottom correction for the first image are saved and used - /// for the 2nd and subsequent images.(RICOH THETA X or later) - applySemiauto('APPLY_SEMIAUTO'), - - /// Performs top/bottom correction and then saves the parameters. - applySave('APPLY_SAVE'), - - /// Performs top/bottom correction using the saved parameters. - applyLoad('APPLY_LOAD'), - - /// Does not perform top/bottom correction. - disapply('DISAPPLY'), - - /// Performs the top/bottom correction with the specified front - /// position. The front position can be specified with - /// _topBottomCorrectionRotation. - manual('MANUAL'); - - final String rawValue; - - const TopBottomCorrectionOptionEnum(this.rawValue); - - @override - String toString() { - return rawValue; - } - - static TopBottomCorrectionOptionEnum? getValue(String rawValue) { - return TopBottomCorrectionOptionEnum.values - .cast() - .firstWhere((element) => element?.rawValue == rawValue, - orElse: () => null); - } -} - /// Video stitching during shooting. enum VideoStitchingEnum { /// Stitching is OFF @@ -3065,30 +3020,6 @@ enum VisibilityReductionEnum { } } -/// Sets the front position for the top/bottom correction. -/// Enabled only for _topBottomCorrection Manual. -class TopBottomCorrectionRotation { - /// Specifies the pitch. - /// Specified range is -90.0 to +90.0, stepSize is 0.1 - double pitch; - - /// Specifies the roll. - /// Specified range is -180.0 to +180.0, stepSize is 0.1 - double roll; - - /// Specifies the yaw. - /// Specified range is -180.0 to +180.0, stepSize is 0.1 - double yaw; - - TopBottomCorrectionRotation(this.pitch, this.roll, this.yaw); - - @override - bool operator ==(Object other) => hashCode == other.hashCode; - - @override - int get hashCode => Object.hashAll([pitch, roll, yaw]); -} - /// White balance auto strength. /// /// To set the strength of white balance auto for low color temperature scene. @@ -3506,6 +3437,9 @@ class Options { /// see [TopBottomCorrectionRotation] TopBottomCorrectionRotation? topBottomCorrectionRotation; + /// see [TopBottomCorrectionRotationSupport] + TopBottomCorrectionRotationSupport? topBottomCorrectionRotationSupport; + /// Total storage space (byte). int? totalSpace; @@ -3645,6 +3579,8 @@ class Options { return topBottomCorrection as T; case OptionNameEnum.topBottomCorrectionRotation: return topBottomCorrectionRotation as T; + case OptionNameEnum.topBottomCorrectionRotationSupport: + return topBottomCorrectionRotationSupport as T; case OptionNameEnum.totalSpace: return totalSpace as T; case OptionNameEnum.username: @@ -3831,6 +3767,9 @@ class Options { case OptionNameEnum.topBottomCorrectionRotation: topBottomCorrectionRotation = value; break; + case OptionNameEnum.topBottomCorrectionRotationSupport: + topBottomCorrectionRotationSupport = value; + break; case OptionNameEnum.totalSpace: totalSpace = value; break; diff --git a/flutter/lib/utils/convert_utils.dart b/flutter/lib/utils/convert_utils.dart index 0929dda8b15..36ed102d897 100644 --- a/flutter/lib/utils/convert_utils.dart +++ b/flutter/lib/utils/convert_utils.dart @@ -406,6 +406,36 @@ class ConvertUtils { return rotation; } + static TopBottomCorrectionRotationSupport? + convertTopBottomCorrectionRotationSupport(Map? data) { + if (data == null) { + return null; + } + + var pitchData = data['pitch']; + var rollData = data['roll']; + var yawData = data['yaw']; + + var support = TopBottomCorrectionRotationSupport( + pitch: TopBottomCorrectionRotationValueSupport( + max: double.parse(pitchData['max']), + min: double.parse(pitchData['min']), + stepSize: double.parse(pitchData['stepSize']), + ), + roll: TopBottomCorrectionRotationValueSupport( + max: double.parse(rollData['max']), + min: double.parse(rollData['min']), + stepSize: double.parse(rollData['stepSize']), + ), + yaw: TopBottomCorrectionRotationValueSupport( + max: double.parse(yawData['max']), + min: double.parse(yawData['min']), + stepSize: double.parse(yawData['stepSize']), + ), + ); + return support; + } + static Options convertOptions(Map data) { var result = Options(); for (var entry in data.entries) { @@ -582,6 +612,10 @@ class ConvertUtils { result.topBottomCorrectionRotation = convertTopBottomCorrectionRotation(entry.value); break; + case OptionNameEnum.topBottomCorrectionRotationSupport: + result.topBottomCorrectionRotationSupport = + convertTopBottomCorrectionRotationSupport(entry.value); + break; case OptionNameEnum.totalSpace: result.totalSpace = entry.value; break; diff --git a/flutter/test/options/option_top_bottom_correction_rotation_support_test.dart b/flutter/test/options/option_top_bottom_correction_rotation_support_test.dart new file mode 100644 index 00000000000..7f7d1983c58 --- /dev/null +++ b/flutter/test/options/option_top_bottom_correction_rotation_support_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:theta_client_flutter/theta_client_flutter.dart'; +import 'package:theta_client_flutter/theta_client_flutter_method_channel.dart'; + +void main() { + MethodChannelThetaClientFlutter platform = MethodChannelThetaClientFlutter(); + const MethodChannel channel = MethodChannel('theta_client_flutter'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + test('getOptions', () async { + final pitchData = {'max': '10.0', 'min': '-15.0', 'stepSize': '1.0'}; + final rollData = {'max': '20.0', 'min': '-5.0', 'stepSize': '0.5'}; + final yawData = {'max': '30.0', 'min': '-10.0', 'stepSize': '2.0'}; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + Map optionMap = {}; + optionMap['TopBottomCorrectionRotationSupport'] = { + "pitch": pitchData, + "roll": rollData, + "yaw": yawData + }; + return Future.value(optionMap); + }); + Options options = await platform + .getOptions([OptionNameEnum.topBottomCorrectionRotationSupport]); + expect( + options.topBottomCorrectionRotationSupport, + TopBottomCorrectionRotationSupport( + pitch: TopBottomCorrectionRotationValueSupport( + max: double.parse(pitchData['max'] ?? '0.0'), + min: double.parse(pitchData['min'] ?? '0.0'), + stepSize: double.parse(pitchData['stepSize'] ?? '0.0'), + ), + roll: TopBottomCorrectionRotationValueSupport( + max: double.parse(rollData['max'] ?? '0.0'), + min: double.parse(rollData['min'] ?? '0.0'), + stepSize: double.parse(rollData['stepSize'] ?? '0.0'), + ), + yaw: TopBottomCorrectionRotationValueSupport( + max: double.parse(yawData['max'] ?? '0.0'), + min: double.parse(yawData['min'] ?? '0.0'), + stepSize: double.parse(yawData['stepSize'] ?? '0.0'), + )), + reason: 'quality'); + }); + + test('getOptionsError', () async { + const pitchData = null; + final rollData = {'max': '20.0', 'min': '-5.0', 'stepSize': '0.5'}; + final yawData = {'max': 'abc'}; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + Map optionMap = {}; + optionMap['TopBottomCorrectionRotationSupport'] = { + "pitch": pitchData, + "roll": rollData, + "yaw": yawData + }; + return Future.value(optionMap); + }); + try { + await platform + .getOptions([OptionNameEnum.topBottomCorrectionRotationSupport]); + fail('Expected an exception due to invalid yaw data'); + } on NoSuchMethodError catch (e) { + expect(e, isA(), + reason: 'Expecting NoSuchMethodError due to invalid yaw data'); + } catch (e) { + fail('Unexpected exception type: $e'); + } + }); +} diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt index 210de706565..7ec4dd6d0ec 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -1011,6 +1011,12 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? */ TopBottomCorrectionRotation("_topBottomCorrectionRotation", ThetaRepository.TopBottomCorrectionRotation::class), + /** + * Option name + * _topBottomCorrectionRotationSupport + */ + TopBottomCorrectionRotationSupport("_topBottomCorrectionRotationSupport", ThetaRepository.TopBottomCorrectionRotationSupport::class), + /** * Option name * totalSpace @@ -1448,6 +1454,11 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? */ var topBottomCorrectionRotation: TopBottomCorrectionRotation? = null, + /** + * @see TopBottomCorrectionRotationSupport + */ + var topBottomCorrectionRotationSupport: TopBottomCorrectionRotationSupport? = null, + /** * Total storage space (byte). */ @@ -1553,6 +1564,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? timeShift = null, topBottomCorrection = null, topBottomCorrectionRotation = null, + topBottomCorrectionRotationSupport = null, totalSpace = null, username = null, videoStitching = null, @@ -1620,6 +1632,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? timeShift = options._timeShift?.let { TimeShiftSetting(it) }, topBottomCorrection = options._topBottomCorrection?.let { TopBottomCorrectionOptionEnum.get(it) }, topBottomCorrectionRotation = options._topBottomCorrectionRotation?.let { TopBottomCorrectionRotation(it) }, + topBottomCorrectionRotationSupport = options._topBottomCorrectionRotationSupport?.let { TopBottomCorrectionRotationSupport(it) }, totalSpace = options.totalSpace, shutterVolume = options._shutterVolume, username = options._username, @@ -1687,6 +1700,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? _timeShift = timeShift?.toTransferredTimeShift(), _topBottomCorrection = topBottomCorrection?.value, _topBottomCorrectionRotation = topBottomCorrectionRotation?.toTransferredTopBottomCorrectionRotation(), + _topBottomCorrectionRotationSupport = topBottomCorrectionRotationSupport?.toTransferredTopBottomCorrectionRotationSupport(), totalSpace = totalSpace, _shootingMethod = shootingMethod?.value, shutterSpeed = shutterSpeed?.value, @@ -1766,6 +1780,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? OptionNameEnum.TimeShift -> timeShift OptionNameEnum.TopBottomCorrection -> topBottomCorrection OptionNameEnum.TopBottomCorrectionRotation -> topBottomCorrectionRotation + OptionNameEnum.TopBottomCorrectionRotationSupport -> topBottomCorrectionRotationSupport OptionNameEnum.TotalSpace -> totalSpace OptionNameEnum.Username -> username OptionNameEnum.VideoStitching -> videoStitching @@ -1843,6 +1858,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? OptionNameEnum.TimeShift -> timeShift = value as TimeShiftSetting OptionNameEnum.TopBottomCorrection -> topBottomCorrection = value as TopBottomCorrectionOptionEnum OptionNameEnum.TopBottomCorrectionRotation -> topBottomCorrectionRotation = value as TopBottomCorrectionRotation + OptionNameEnum.TopBottomCorrectionRotationSupport -> topBottomCorrectionRotationSupport = value as TopBottomCorrectionRotationSupport OptionNameEnum.TotalSpace -> totalSpace = value as Long OptionNameEnum.Username -> username = value as String OptionNameEnum.VideoStitching -> videoStitching = value as VideoStitchingEnum @@ -6000,9 +6016,9 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? val yaw: Float ) { internal constructor(rotation: com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotation) : this( - pitch = rotation.pitch ?: 0f, - roll = rotation.roll ?: 0f, - yaw = rotation.yaw ?: 0f + pitch = rotation.pitch.toFloatOrNull() ?: 0.0f, + roll = rotation.roll.toFloatOrNull() ?: 0.0f, + yaw = rotation.yaw.toFloatOrNull() ?: 0.0f ) /** @@ -6011,10 +6027,126 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? * @return transferred.TopBottomCorrectionRotation */ internal fun toTransferredTopBottomCorrectionRotation(): com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotation { - return com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotation( - pitch = pitch, - roll = roll, - yaw = yaw + return TopBottomCorrectionRotation( + pitch = pitch.toString(), + roll = roll.toString(), + yaw = yaw.toString() + ) + } + } + + /** + * Supported TopBottomCorrectionRotation + */ + data class TopBottomCorrectionRotationSupport( + /** + * Supported pitch + */ + val pitch: TopBottomCorrectionRotationValueSupport, + + /** + * Supported roll + */ + val roll: TopBottomCorrectionRotationValueSupport, + + /** + * Supported yaw + */ + val yaw: TopBottomCorrectionRotationValueSupport + ) { + internal constructor(rotation: com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotationSupport) : this( + pitch = TopBottomCorrectionRotationValueSupport(rotation.pitch), + roll = TopBottomCorrectionRotationValueSupport(rotation.roll), + yaw = TopBottomCorrectionRotationValueSupport(rotation.yaw) + ) + + /** + * Convert TopBottomCorrectionRotationSupport to transferred.TopBottomCorrectionRotationSupport. for ThetaApi. + * + * @return transferred.TopBottomCorrectionRotationSupport + */ + internal fun toTransferredTopBottomCorrectionRotationSupport(): com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotationSupport { + return TopBottomCorrectionRotationSupport( + pitch = pitch.toTransferredPitchSupport(), + roll = roll.toTransferredRollSupport(), + yaw = yaw.toTransferredYawSupport() + ) + } + } + + /** + * Supported value of TopBottomCorrectionRotation + */ + data class TopBottomCorrectionRotationValueSupport( + /** + * maximum value + */ + val max: Float, + + /** + * minimum value + */ + val min: Float, + + /** + * step size + */ + val stepSize: Float + ) { + internal constructor(support: PitchSupport) : this( + max = support.maxPitch, + min = support.minPitch, + stepSize = support.stepSize + ) + + internal constructor(support: RollSupport) : this( + max = support.maxRoll, + min = support.minRoll, + stepSize = support.stepSize + ) + + internal constructor(support: YawSupport) : this( + max = support.maxYaw, + min = support.minYaw, + stepSize = support.stepSize + ) + + /** + * Convert TopBottomCorrectionRotationValueSupport to transferred.PitchSupport. for ThetaApi. + * + * @return transferred.PitchSupport + */ + internal fun toTransferredPitchSupport(): PitchSupport { + return PitchSupport( + maxPitch = max, + minPitch = min, + stepSize = stepSize + ) + } + + /** + * Convert TopBottomCorrectionRotationValueSupport to transferred.RollSupport. for ThetaApi. + * + * @return transferred.RollSupport + */ + internal fun toTransferredRollSupport(): RollSupport { + return RollSupport( + maxRoll = max, + minRoll = min, + stepSize = stepSize + ) + } + + /** + * Convert TopBottomCorrectionRotationValueSupport to transferred.YawSupport. for ThetaApi. + * + * @return transferred.YawSupport + */ + internal fun toTransferredYawSupport(): YawSupport { + return YawSupport( + maxYaw = max, + minYaw = min, + stepSize = stepSize ) } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt index ae8e732bd1a..25cd398fb02 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt @@ -761,6 +761,11 @@ internal data class Options( */ var _topBottomCorrectionRotation: TopBottomCorrectionRotation? = null, + /** + * @see TopBottomCorrectionRotationSupport + */ + var _topBottomCorrectionRotationSupport: TopBottomCorrectionRotationSupport? = null, + /** * Total storage space (byte). */ @@ -1851,19 +1856,103 @@ internal data class TopBottomCorrectionRotation( * Specifies the pitch. * Specified range is -90.0 to +90.0, stepSize is 0.1 */ - val pitch: Float? = null, + val pitch: String, /** * Specifies the roll. * Specified range is -180.0 to +180.0, stepSize is 0.1 */ - val roll: Float? = null, + val roll: String, /** * Specifies the yaw. * Specified range is -180.0 to +180.0, stepSize is 0.1 */ - val yaw: Float? = null + val yaw: String +) + +/** + * Supported TopBottomCorrectionRotation + */ +@Serializable +internal data class TopBottomCorrectionRotationSupport( + /** + * Supported pitch + */ + val pitch: PitchSupport, + + /** + * Supported roll + */ + val roll: RollSupport, + + /** + * Supported yaw + */ + val yaw: YawSupport +) + +/** + * Supported pitch of TopBottomCorrectionRotation + */ +@Serializable +internal data class PitchSupport( + /** + * maximum pitch volume + */ + val maxPitch: Float, + + /** + * minimum pitch volume + */ + val minPitch: Float, + + /** + * pitch step size + */ + val stepSize: Float +) + +/** + * Supported roll of TopBottomCorrectionRotation + */ +@Serializable +internal data class RollSupport( + /** + * maximum roll volume + */ + val maxRoll: Float, + + /** + * minimum roll volume + */ + val minRoll: Float, + + /** + * roll step size + */ + val stepSize: Float +) + +/** + * Supported yaw of TopBottomCorrectionRotation + */ +@Serializable +internal data class YawSupport( + /** + * maximum yaw volume + */ + val maxYaw: Float, + + /** + * minimum yaw volume + */ + val minYaw: Float, + + /** + * yaw step size + */ + val stepSize: Float ) /** diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt index b90ea15f8c4..633294248ce 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt @@ -104,6 +104,7 @@ internal class CheckRequest { timeShift: TimeShift? = null, topBottomCorrection: TopBottomCorrectionOption? = null, topBottomCorrectionRotation: TopBottomCorrectionRotation? = null, + topBottomCorrectionRotationSupport: TopBottomCorrectionRotationSupport? = null, videoStitching: VideoStitching? = null, visibilityReduction: VisibilityReduction? = null, whiteBalance: WhiteBalance? = null, @@ -264,6 +265,9 @@ internal class CheckRequest { topBottomCorrectionRotation?.let { assertEquals(optionsRequest.parameters.options._topBottomCorrectionRotation, it, "setOptions topBottomCorrectionRotation") } + topBottomCorrectionRotationSupport?.let { + assertEquals(optionsRequest.parameters.options._topBottomCorrectionRotationSupport, it, "setOptions topBottomCorrectionRotationSupport") + } videoStitching?.let { assertEquals(optionsRequest.parameters.options.videoStitching, it, "setOptions videoStitching") } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt index 6ca3107f356..dd2b9e1b615 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/OptionsTest.kt @@ -33,19 +33,23 @@ import com.ricoh360.thetaclient.transferred.MediaFileFormat import com.ricoh360.thetaclient.transferred.MediaType import com.ricoh360.thetaclient.transferred.NetworkType import com.ricoh360.thetaclient.transferred.Options +import com.ricoh360.thetaclient.transferred.PitchSupport import com.ricoh360.thetaclient.transferred.PowerSaving import com.ricoh360.thetaclient.transferred.Preset import com.ricoh360.thetaclient.transferred.PreviewFormat import com.ricoh360.thetaclient.transferred.Proxy +import com.ricoh360.thetaclient.transferred.RollSupport import com.ricoh360.thetaclient.transferred.ShootingFunction import com.ricoh360.thetaclient.transferred.ShootingMethod import com.ricoh360.thetaclient.transferred.TimeShift import com.ricoh360.thetaclient.transferred.TopBottomCorrectionOption import com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotation +import com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotationSupport import com.ricoh360.thetaclient.transferred.VideoStitching import com.ricoh360.thetaclient.transferred.VisibilityReduction import com.ricoh360.thetaclient.transferred.WhiteBalance import com.ricoh360.thetaclient.transferred.WhiteBalanceAutoStrength +import com.ricoh360.thetaclient.transferred.YawSupport import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -151,6 +155,11 @@ class OptionsTest { ) val topBottomCorrection = ThetaRepository.TopBottomCorrectionOptionEnum.APPLY_AUTO val topBottomCorrectionRotation = ThetaRepository.TopBottomCorrectionRotation(1.0f, 2.0f, 3.0f) + val topBottomCorrectionRotationSupport = ThetaRepository.TopBottomCorrectionRotationSupport( + pitch = ThetaRepository.TopBottomCorrectionRotationValueSupport(100f, -100f, 0.2f), + roll = ThetaRepository.TopBottomCorrectionRotationValueSupport(200f, -200f, 0.4f), + yaw = ThetaRepository.TopBottomCorrectionRotationValueSupport(300f, -300f, 0.6f) + ) val totalSpace = 100L val username = "username" val videoStitching = ThetaRepository.VideoStitchingEnum.ONDEVICE @@ -214,6 +223,7 @@ class OptionsTest { timeShift = timeShift, topBottomCorrection = topBottomCorrection, topBottomCorrectionRotation = topBottomCorrectionRotation, + topBottomCorrectionRotationSupport = topBottomCorrectionRotationSupport, totalSpace = totalSpace, username = username, videoStitching = videoStitching, @@ -281,6 +291,7 @@ class OptionsTest { assertEquals(options.getValue(ThetaRepository.OptionNameEnum.TimeShift), timeShift, "timeShift") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.TopBottomCorrection), topBottomCorrection, "topBottomCorrection") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.TopBottomCorrectionRotation), topBottomCorrectionRotation, "topBottomCorrectionRotation") + assertEquals(options.getValue(ThetaRepository.OptionNameEnum.TopBottomCorrectionRotationSupport), topBottomCorrectionRotationSupport, "topBottomCorrectionRotationSupport") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.TotalSpace), totalSpace, "totalSpace") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.Username), username, "userName") assertEquals(options.getValue(ThetaRepository.OptionNameEnum.VideoStitching), videoStitching, "videoStitching") @@ -387,6 +398,13 @@ class OptionsTest { ), Pair(ThetaRepository.OptionNameEnum.TopBottomCorrection, ThetaRepository.TopBottomCorrectionOptionEnum.APPLY), Pair(ThetaRepository.OptionNameEnum.TopBottomCorrectionRotation, ThetaRepository.TopBottomCorrectionRotation(pitch = 1.0f, roll = 1.0f, yaw = 1.0f)), + Pair( + ThetaRepository.OptionNameEnum.TopBottomCorrectionRotationSupport, ThetaRepository.TopBottomCorrectionRotationSupport( + pitch = ThetaRepository.TopBottomCorrectionRotationValueSupport(100f, -100f, 0.2f), + roll = ThetaRepository.TopBottomCorrectionRotationValueSupport(200f, -200f, 0.4f), + yaw = ThetaRepository.TopBottomCorrectionRotationValueSupport(300f, -300f, 0.6f) + ) + ), Pair(ThetaRepository.OptionNameEnum.TotalSpace, 104L), Pair(ThetaRepository.OptionNameEnum.Username, "username"), Pair(ThetaRepository.OptionNameEnum.VideoStitching, ThetaRepository.VideoStitchingEnum.NONE), @@ -528,7 +546,18 @@ class OptionsTest { ThetaRepository.TimeShiftSetting(true, ThetaRepository.TimeShiftIntervalEnum.INTERVAL_4, ThetaRepository.TimeShiftIntervalEnum.INTERVAL_5) ) val topBottomCorrection = Pair(TopBottomCorrectionOption.DISAPPLY, ThetaRepository.TopBottomCorrectionOptionEnum.DISAPPLY) - val topBottomCorrectionRotation = Pair(TopBottomCorrectionRotation(3.0f, 2.0f, 1.0f), ThetaRepository.TopBottomCorrectionRotation(3.0f, 2.0f, 1.0f)) + val topBottomCorrectionRotation = Pair(TopBottomCorrectionRotation("3.0", "2.0", "1.0"), ThetaRepository.TopBottomCorrectionRotation(3.0f, 2.0f, 1.0f)) + val topBottomCorrectionRotationSupport = Pair( + TopBottomCorrectionRotationSupport( + pitch = PitchSupport(100f, -100f, 0.2f), + roll = RollSupport(200f, -200f, 0.4f), + yaw = YawSupport(300f, -300f, 0.6f) + ), ThetaRepository.TopBottomCorrectionRotationSupport( + pitch = ThetaRepository.TopBottomCorrectionRotationValueSupport(100f, -100f, 0.2f), + roll = ThetaRepository.TopBottomCorrectionRotationValueSupport(200f, -200f, 0.4f), + yaw = ThetaRepository.TopBottomCorrectionRotationValueSupport(300f, -300f, 0.6f) + ) + ) val totalSpace = Pair(104L, 104L) val username = Pair("username", "username") val videoStitching = Pair(VideoStitching.NONE, ThetaRepository.VideoStitchingEnum.NONE) @@ -590,6 +619,7 @@ class OptionsTest { _timeShift = timeShift.first, _topBottomCorrection = topBottomCorrection.first, _topBottomCorrectionRotation = topBottomCorrectionRotation.first, + _topBottomCorrectionRotationSupport = topBottomCorrectionRotationSupport.first, totalSpace = totalSpace.first, _username = username.first, videoStitching = videoStitching.first, @@ -652,6 +682,7 @@ class OptionsTest { assertEquals(options.timeShift, timeShift.second, "timeShift") assertEquals(options.topBottomCorrection, topBottomCorrection.second, "topBottomCorrection") assertEquals(options.topBottomCorrectionRotation, topBottomCorrectionRotation.second, "topBottomCorrectionRotation") + assertEquals(options.topBottomCorrectionRotationSupport, topBottomCorrectionRotationSupport.second, "topBottomCorrectionRotationSupport") assertEquals(options.totalSpace, totalSpace.second, "totalSpace") assertEquals(options.username, username.second, "username") assertEquals(options.videoStitching, videoStitching.second, "videoStitching") @@ -783,7 +814,18 @@ class OptionsTest { ThetaRepository.TimeShiftSetting(false, ThetaRepository.TimeShiftIntervalEnum.INTERVAL_6, ThetaRepository.TimeShiftIntervalEnum.INTERVAL_7) ) val topBottomCorrection = Pair(TopBottomCorrectionOption.MANUAL, ThetaRepository.TopBottomCorrectionOptionEnum.MANUAL) - val topBottomCorrectionRotation = Pair(TopBottomCorrectionRotation(0.0f, 0.0f, 0.0f), ThetaRepository.TopBottomCorrectionRotation(0.0f, 0.0f, 0.0f)) + val topBottomCorrectionRotation = Pair(TopBottomCorrectionRotation("0.0", "0.0", "0.0"), ThetaRepository.TopBottomCorrectionRotation(0.0f, 0.0f, 0.0f)) + val topBottomCorrectionRotationSupport = Pair( + TopBottomCorrectionRotationSupport( + pitch = PitchSupport(100f, -100f, 0.2f), + roll = RollSupport(200f, -200f, 0.4f), + yaw = YawSupport(300f, -300f, 0.6f) + ), ThetaRepository.TopBottomCorrectionRotationSupport( + pitch = ThetaRepository.TopBottomCorrectionRotationValueSupport(100f, -100f, 0.2f), + roll = ThetaRepository.TopBottomCorrectionRotationValueSupport(200f, -200f, 0.4f), + yaw = ThetaRepository.TopBottomCorrectionRotationValueSupport(300f, -300f, 0.6f) + ) + ) val totalSpace = Pair(104L, 104L) val userName = Pair("username", "username") val videoStitching = Pair(VideoStitching.NONE, ThetaRepository.VideoStitchingEnum.NONE) @@ -845,6 +887,7 @@ class OptionsTest { timeShift = timeShift.second, topBottomCorrection = topBottomCorrection.second, topBottomCorrectionRotation = topBottomCorrectionRotation.second, + topBottomCorrectionRotationSupport = topBottomCorrectionRotationSupport.second, totalSpace = totalSpace.second, username = userName.second, videoStitching = videoStitching.second, @@ -907,6 +950,7 @@ class OptionsTest { assertEquals(options._timeShift, timeShift.first, "timeShift") assertEquals(options._topBottomCorrection, topBottomCorrection.first, "topBottomCorrection") assertEquals(options._topBottomCorrectionRotation, topBottomCorrectionRotation.first, "topBottomCorrectionRotation") + assertEquals(options._topBottomCorrectionRotationSupport, topBottomCorrectionRotationSupport.first, "topBottomCorrectionRotationSupport") assertEquals(options.totalSpace, totalSpace.first, "totalSpace") assertEquals(options._username, userName.first, "userName") assertEquals(options.videoStitching, videoStitching.first, "videoStitching") diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationSupportTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationSupportTest.kt new file mode 100644 index 00000000000..a4a51b25889 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationSupportTest.kt @@ -0,0 +1,104 @@ +package com.ricoh360.thetaclient.repository.options + +import com.goncalossilva.resources.Resource +import com.ricoh360.thetaclient.CheckRequest +import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaRepository +import com.ricoh360.thetaclient.transferred.Options +import com.ricoh360.thetaclient.transferred.PitchSupport +import com.ricoh360.thetaclient.transferred.RollSupport +import com.ricoh360.thetaclient.transferred.TopBottomCorrectionRotationSupport +import com.ricoh360.thetaclient.transferred.YawSupport +import io.ktor.http.HttpStatusCode +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.test.runTest +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class TopBottomCorrectionRotationSupportTest { + private val endpoint = "http://192.168.1.1:80/" + + @BeforeTest + fun setup() { + MockApiClient.status = HttpStatusCode.OK + } + + @AfterTest + fun teardown() { + MockApiClient.status = HttpStatusCode.OK + } + + /** + * Get option + */ + @Test + fun getOptionTest() = runTest { + val optionNames = listOf( + ThetaRepository.OptionNameEnum.TopBottomCorrectionRotationSupport + ) + val stringOptionNames = listOf( + "_topBottomCorrectionRotationSupport" + ) + + MockApiClient.onRequest = { request -> + // check request + CheckRequest.checkGetOptions(request, stringOptionNames) + + ByteReadChannel(Resource("src/commonTest/resources/options/option_top_bottom_correction_rotation_support.json").readText()) + } + + val thetaRepository = ThetaRepository(endpoint) + val options = thetaRepository.getOptions(optionNames) + options.topBottomCorrectionRotationSupport?.let { + assertEquals(it.pitch.max, 90.0f) + assertEquals(it.pitch.min, -90.0f) + assertEquals(it.pitch.stepSize, 0.1f) + + assertEquals(it.roll.max, 180.0f) + assertEquals(it.roll.min, -180.0f) + assertEquals(it.roll.stepSize, 0.1f) + + assertEquals(it.yaw.max, 180.0f) + assertEquals(it.yaw.min, -180.0f) + assertEquals(it.yaw.stepSize, 0.1f) + } + } + + /** + * Convert ThetaRepository.Options to Options. + */ + @Test + fun convertOptionTest() = runTest { + val values = listOf( + Pair( + ThetaRepository.TopBottomCorrectionRotationSupport( + pitch = ThetaRepository.TopBottomCorrectionRotationValueSupport(100f, -100f, 0.2f), + roll = ThetaRepository.TopBottomCorrectionRotationValueSupport(200f, -200f, 0.4f), + yaw = ThetaRepository.TopBottomCorrectionRotationValueSupport(300f, -300f, 0.6f) + ), TopBottomCorrectionRotationSupport( + pitch = PitchSupport(100f, -100f, 0.2f), + roll = RollSupport(200f, -200f, 0.4f), + yaw = YawSupport(300f, -300f, 0.6f) + ) + ), + ) + + values.forEach { + val orgOptions = Options( + _topBottomCorrectionRotationSupport = it.second + ) + val options = ThetaRepository.Options(orgOptions) + assertEquals(options.topBottomCorrectionRotationSupport, it.first, "topBottomCorrectionRotationSupport ${it.second}") + } + + values.forEach { + val orgOptions = ThetaRepository.Options( + topBottomCorrectionRotationSupport = it.first + ) + val options = orgOptions.toOptions() + assertEquals(options._topBottomCorrectionRotationSupport, it.second, "_topBottomCorrectionRotationSupport ${it.second}") + } + } +} \ No newline at end of file diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationTest.kt index 83c82a4658b..dbae676cc2b 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/options/TopBottomCorrectionRotationTest.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class TopBottomCorrectionRotationTest { private val endpoint = "http://192.168.1.1:80/" @@ -59,7 +58,7 @@ class TopBottomCorrectionRotationTest { */ @Test fun setOptionTest() = runTest { - val value = Pair(ThetaRepository.TopBottomCorrectionRotation(pitch = 1.0f, roll = 1.0f, yaw = 1.0f), TopBottomCorrectionRotation(pitch = 1.0f, roll = 1.0f, yaw = 1.0f)) + val value = Pair(ThetaRepository.TopBottomCorrectionRotation(pitch = 1.0f, roll = 1.0f, yaw = 1.0f), TopBottomCorrectionRotation(pitch = "1.0", roll = "1.0", yaw = "1.0")) MockApiClient.onRequest = { request -> // check request @@ -81,7 +80,7 @@ class TopBottomCorrectionRotationTest { @Test fun convertOptionTest() = runTest { val values = listOf( - Pair(ThetaRepository.TopBottomCorrectionRotation(pitch = 1.0f, roll = 1.0f, yaw = 1.0f), TopBottomCorrectionRotation(pitch = 1.0f, roll = 1.0f, yaw = 1.0f)), + Pair(ThetaRepository.TopBottomCorrectionRotation(pitch = 1.0f, roll = 1.0f, yaw = 1.0f), TopBottomCorrectionRotation(pitch = "1.0", roll = "1.0", yaw = "1.0")), ) values.forEach { diff --git a/kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_support.json b/kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_support.json new file mode 100644 index 00000000000..d4d3f28cf8d --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_support.json @@ -0,0 +1,25 @@ +{ + "name": "camera.getOptions", + "state": "done", + "results": { + "options": { + "_topBottomCorrectionRotationSupport": { + "pitch": { + "maxPitch": "+90.0", + "minPitch": "-90.0", + "stepSize": "0.1" + }, + "roll": { + "maxRoll": "+180.0", + "minRoll": "-180.0", + "stepSize": "0.1" + }, + "yaw": { + "maxYaw": "+180.0", + "minYaw": "-180.0", + "stepSize": "0.1" + } + } + } + } +} \ No newline at end of file diff --git a/kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_zero.json b/kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_zero.json index 777ceab48d9..919f3acc7fd 100644 --- a/kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_zero.json +++ b/kotlin-multiplatform/src/commonTest/resources/options/option_top_bottom_correction_rotation_zero.json @@ -1 +1,13 @@ -{"results":{"options":{"_topBottomCorrectionRotation":{"pitch":"0","roll":"0","yaw":"0"}}},"name":"camera.getOptions","state":"done"} \ No newline at end of file +{ + "results": { + "options": { + "_topBottomCorrectionRotation": { + "pitch": "0", + "roll": "0", + "yaw": "0" + } + } + }, + "name": "camera.getOptions", + "state": "done" +} \ No newline at end of file diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt index 19114f9b1f8..93ed65b50a7 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt @@ -10,6 +10,8 @@ import com.ricoh360.thetaclient.DigestAuth import com.ricoh360.thetaclient.ThetaRepository.* import com.ricoh360.thetaclient.capture.* import com.ricoh360.thetaclient.websocket.CameraEvent +import kotlin.reflect.KClass +import kotlin.Pair const val KEY_NOTIFY_NAME = "name" const val KEY_NOTIFY_PARAMS = "params" @@ -32,6 +34,17 @@ const val KEY_IP_ADDRESS = "ipAddress" const val KEY_SUBNET_MASK = "subnetMask" const val KEY_DEFAULT_GATEWAY = "defaultGateway" const val KEY_PROXY = "proxy" +const val KEY_MAC_ADDRESS = "macAddress" +const val KEY_HOST_NAME = "hostName" +const val KEY_DHCP_LEASE_ADDRESS = "dhcpLeaseAddress" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION = "topBottomCorrectionRotation" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH = "pitch" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL = "roll" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW = "yaw" +const val KEY_TOP_BOTTOM_CORRECTION_ROTATION_SUPPORT = "topBottomCorrectionRotationSupport" +const val KEY_MAX = "max" +const val KEY_MIN = "min" +const val KEY_STEP_SIZE = "stepSize" val optionItemNameToEnum: Map = mutableMapOf( "aiAutoThumbnail" to OptionNameEnum.AiAutoThumbnail, @@ -86,6 +99,9 @@ val optionItemNameToEnum: Map = mutableMapOf( "shutterVolume" to OptionNameEnum.ShutterVolume, "sleepDelay" to OptionNameEnum.SleepDelay, "timeShift" to OptionNameEnum.TimeShift, + "topBottomCorrection" to OptionNameEnum.TopBottomCorrection, + KEY_TOP_BOTTOM_CORRECTION_ROTATION to OptionNameEnum.TopBottomCorrectionRotation, + KEY_TOP_BOTTOM_CORRECTION_ROTATION_SUPPORT to OptionNameEnum.TopBottomCorrectionRotationSupport, "totalSpace" to OptionNameEnum.TotalSpace, "username" to OptionNameEnum.Username, "videoStitching" to OptionNameEnum.VideoStitching, @@ -326,6 +342,7 @@ fun toGetOptionsParam(optionNames: ReadableArray): MutableList { fun toResult(options: Options): WritableMap { val result = Arguments.createMap() + val jsonResult = Arguments.createMap() val valueOptions = listOf( OptionNameEnum.CaptureInterval, @@ -396,13 +413,26 @@ fun toResult(options: Options): WritableMap { options.timeShift?.let { result.putMap("timeShift", toResult(timeShift = it)) } + } else if (name == OptionNameEnum.TopBottomCorrectionRotation) { + options.topBottomCorrectionRotation?.let { + result.putMap(KEY_TOP_BOTTOM_CORRECTION_ROTATION, toResult(rotation = it)) + } + } else if (name == OptionNameEnum.TopBottomCorrectionRotationSupport) { + options.topBottomCorrectionRotationSupport?.let { + val json = toJson(rotationSupport = it) + jsonResult.putString(KEY_TOP_BOTTOM_CORRECTION_ROTATION_SUPPORT, json) + } } else if (valueOptions.contains(name)) { addOptionsValueToMap(options, name, result) } else { addOptionsEnumToMap(options, name, result) } } - return result + + val response = Arguments.createMap() + response.putMap("options", result) + response.putMap("json", jsonResult) + return response } fun > addOptionsEnumToMap(options: Options, name: OptionNameEnum, objects: WritableMap) { @@ -630,6 +660,55 @@ fun toResult(timeShift: TimeShiftSetting): WritableMap { return result } +fun toResult(rotation: TopBottomCorrectionRotation): WritableMap { + val result = Arguments.createMap() + rotation.pitch?.let { value -> + result.putDouble(KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH, value.toDouble()) + } + rotation.roll?.let { value -> + result.putDouble(KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL, value.toDouble()) + } + rotation.yaw?.let { value -> + result.putDouble(KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW, value.toDouble()) + } + return result +} + +/** + * Avoided floating-point errors by converting to JSON string in the bridge, + * then to object in TypeScript. + */ +fun toJson(rotationSupport: TopBottomCorrectionRotationSupport): String { + val convertJson = """ + { + "pitch": { + "max": ${rotationSupport.pitch.max}, + "min": ${rotationSupport.pitch.min}, + "stepSize": ${rotationSupport.pitch.stepSize} + }, + "roll": { + "max": ${rotationSupport.roll.max}, + "min": ${rotationSupport.roll.min}, + "stepSize": ${rotationSupport.roll.stepSize} + }, + "yaw": { + "max": ${rotationSupport.yaw.max}, + "min": ${rotationSupport.yaw.min}, + "stepSize": ${rotationSupport.yaw.stepSize} + } + } + """.trimIndent() + return convertJson +} + +fun toResult(rotationValueSupport: TopBottomCorrectionRotationValueSupport): WritableMap { + val result = Arguments.createMap() + result.putString(KEY_MAX, rotationValueSupport.max.toString()) + result.putString(KEY_MIN, rotationValueSupport.min.toString()) + result.putString(KEY_STEP_SIZE, rotationValueSupport.stepSize.toString()) + return result +} + fun toResult(state: ThetaState): WritableMap { val result = Arguments.createMap() state.fingerprint?.let { @@ -710,7 +789,7 @@ fun toResult(state: ThetaState): WritableMap { fun toResult(cameraEvent: CameraEvent): WritableMap { val result = Arguments.createMap() cameraEvent.options?.let { - result.putMap("options", toResult(it)) + result.putMap("options", toResult(options = it).getMap("options")) } cameraEvent.state?.let { result.putMap("state", toResult(it)) @@ -806,6 +885,10 @@ fun setOptionValue(options: Options, name: OptionNameEnum, optionsMap: ReadableM optionsMap.getMap(key)?.let { options.setValue(name, toTimeShift(map = it)) } + } else if (name == OptionNameEnum.TopBottomCorrectionRotation) { + optionsMap.getMap(key)?.let { + options.setValue(name, toTopBottomCorrectionRotation(map = it)) + } } else { (optionsMap.getString(key))?.let { value -> getOptionValueEnum(name, value)?.let { @@ -849,6 +932,7 @@ fun getOptionValueEnum(name: OptionNameEnum, valueName: String): Any? { OptionNameEnum.ShootingMethod -> ShootingMethodEnum.values().find { it.name == valueName } OptionNameEnum.ShutterSpeed -> ShutterSpeedEnum.values().find { it.name == valueName } OptionNameEnum.SleepDelay -> SleepDelayEnum.values().find { it.name == valueName } + OptionNameEnum.TopBottomCorrection -> TopBottomCorrectionOptionEnum.values().find { it.name == valueName } OptionNameEnum.VideoStitching -> VideoStitchingEnum.values().find { it.name == valueName } OptionNameEnum.VisibilityReduction -> VisibilityReductionEnum.values().find { it.name == valueName } OptionNameEnum.WhiteBalance -> WhiteBalanceEnum.values().find { it.name == valueName } @@ -948,6 +1032,13 @@ fun toTimeShift(map: ReadableMap): TimeShiftSetting { return timeShift } +fun toTopBottomCorrectionRotation(map: ReadableMap): TopBottomCorrectionRotation { + val pitch = map.getDouble(KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH)?.toFloat() ?: 0.0f + val roll = map.getDouble(KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL)?.toFloat() ?: 0.0f + val yaw = map.getDouble(KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW)?.toFloat() ?: 0.0f + return TopBottomCorrectionRotation(pitch = pitch, roll = roll, yaw = yaw) +} + fun configToTheta(objects: ReadableMap): Config { val config = Config() config.dateTime = objects.getString("dateTime") diff --git a/react-native/ios/ConvertUtil.swift b/react-native/ios/ConvertUtil.swift index 06d11fbfab0..12591edd19e 100644 --- a/react-native/ios/ConvertUtil.swift +++ b/react-native/ios/ConvertUtil.swift @@ -81,6 +81,23 @@ let KEY_COLOR_TEMPERATURE_SUPPORT = "colorTemperatureSupport" let KEY_COLOR_TEMPERATURE_SUPPORT_MAX = "maxTemperature" let KEY_COLOR_TEMPERATURE_SUPPORT_MIN = "minTemperature" let KEY_COLOR_TEMPERATURE_SUPPORT_STEP_SIZE = "stepSize" +let KEY_WLAN_FREQUENCY_CL_MODE = "wlanFrequencyClMode" +let KEY_WLAN_FREQUENCY_CL_MODE_2_4 = "enable2_4" +let KEY_WLAN_FREQUENCY_CL_MODE_5_2 = "enable5_2" +let KEY_WLAN_FREQUENCY_CL_MODE_5_8 = "enable5_8" +let KEY_ROAMING = "roaming" +let KEY_PLAN = "plan" +let KEY_CAMERA_LOCK_CONFIG_IS_POWER_KEY_LOCKED = "isPowerKeyLocked" +let KEY_CAMERA_LOCK_CONFIG_IS_SHUTTER_KEY_LOCKED = "isShutterKeyLocked" +let KEY_CAMERA_LOCK_CONFIG_IS_MODE_KEY_LOCKED = "isModeKeyLocked" +let KEY_CAMERA_LOCK_CONFIG_IS_WLAN_KEY_LOCKED = "isWlanKeyLocked" +let KEY_CAMERA_LOCK_CONFIG_IS_FN_KEY_LOCKED = "isFnKeyLocked" +let KEY_CAMERA_LOCK_CONFIG_IS_PANEL_LOCKED = "isPanelLocked" +let KEY_DHCP_LEASE_ADDRESS = "dhcpLeaseAddress" +let KEY_TOP_BOTTOM_CORRECTION_ROTATION_SUPPORT = "topBottomCorrectionRotationSupport" +let KEY_MAX = "max" +let KEY_MIN = "min" +let KEY_STEP_SIZE = "stepSize" public class ConvertUtil: NSObject {} @@ -143,6 +160,7 @@ let optionItemNameToEnum = [ "timeShift": ThetaRepository.OptionNameEnum.timeshift, "topBottomCorrection": ThetaRepository.OptionNameEnum.topbottomcorrection, "topBottomCorrectionRotation": ThetaRepository.OptionNameEnum.topbottomcorrectionrotation, + KEY_TOP_BOTTOM_CORRECTION_ROTATION_SUPPORT: ThetaRepository.OptionNameEnum.topbottomcorrectionrotationsupport, "totalSpace": ThetaRepository.OptionNameEnum.totalspace, "username": ThetaRepository.OptionNameEnum.username, "videoStitching": ThetaRepository.OptionNameEnum.videostitching, @@ -390,6 +408,7 @@ func setOptionsValue(options: ThetaRepository.Options, name: String, value: Any) func convertResult(options: ThetaRepository.Options) -> [String: Any] { var result = [String: Any]() + var jsonResult = [String: Any]() let nameList = ThetaRepository.OptionNameEnum.values() for i in 0 ..< nameList.size { if let name = nameList.get(index: i), @@ -433,6 +452,9 @@ func convertResult(options: ThetaRepository.Options) -> [String: Any] { result[key] = convertResult(timeshift: timeshift) } else if value is ThetaRepository.TopBottomCorrectionRotation, let rotation = value as? ThetaRepository.TopBottomCorrectionRotation { result[key] = convertResult(rotation: rotation) + } else if value is ThetaRepository.TopBottomCorrectionRotationSupport, + let support = value as? ThetaRepository.TopBottomCorrectionRotationSupport { + jsonResult[key] = convertJson(topBottomCorrectionRotationSupport: support) } else if let offDelay = value as? ThetaRepository.OffDelaySec { result[key] = offDelay.sec == 0 ? ThetaRepository.OffDelayEnum.disable.name : offDelay.sec @@ -445,7 +467,11 @@ func convertResult(options: ThetaRepository.Options) -> [String: Any] { } } } - return result + + var response = [String: Any]() + response["options"] = result + response["json"] = jsonResult + return response } // MARK: - Notify event @@ -1110,6 +1136,29 @@ func convertResult(rotation: ThetaRepository.TopBottomCorrectionRotation) -> [St ] } +func convertJson(topBottomCorrectionRotationSupport: ThetaRepository.TopBottomCorrectionRotationSupport) -> String { + let convertJson = """ + { + "pitch": { + "max": \(topBottomCorrectionRotationSupport.pitch.max), + "min": \(topBottomCorrectionRotationSupport.pitch.min), + "stepSize": \(topBottomCorrectionRotationSupport.pitch.stepSize) + }, + "roll": { + "max": \(topBottomCorrectionRotationSupport.roll.max), + "min": \(topBottomCorrectionRotationSupport.roll.min), + "stepSize": \(topBottomCorrectionRotationSupport.roll.stepSize) + }, + "yaw": { + "max": \(topBottomCorrectionRotationSupport.yaw.max), + "min": \(topBottomCorrectionRotationSupport.yaw.min), + "stepSize": \(topBottomCorrectionRotationSupport.yaw.stepSize) + } + } + """ + return convertJson +} + func convertResult(exif: ThetaRepository.Exif) -> [String: Any] { var result = [String: Any]() result["exifVersion"] = exif.exifVersion @@ -1434,14 +1483,14 @@ func toTimeShift(params: [String: Any]) -> ThetaRepository.TimeShiftSetting { } func toTopBottomCorrectionRotation(params: [String: Any]) -> ThetaRepository.TopBottomCorrectionRotation? { - guard let pitch = params[KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH] as? Double, - let roll = params[KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL] as? Double, - let yaw = params[KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW] as? Double else { return nil } + let pitch = Float(params[KEY_TOP_BOTTOM_CORRECTION_ROTATION_PITCH] as? Double ?? 0) + let roll = Float(params[KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL] as? Double ?? 0) + let yaw = Float(params[KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW] as? Double ?? 0) return ThetaRepository.TopBottomCorrectionRotation( - pitch: Float(pitch), - roll: Float(roll), - yaw: Float(yaw) + pitch: pitch, + roll: roll, + yaw: yaw ) } diff --git a/react-native/src/__tests__/capture/continuous-capture.test.ts b/react-native/src/__tests__/capture/continuous-capture.test.ts index b8061e4540e..aeea8e9c366 100644 --- a/react-native/src/__tests__/capture/continuous-capture.test.ts +++ b/react-native/src/__tests__/capture/continuous-capture.test.ts @@ -259,7 +259,7 @@ describe('continuous shooting', () => { const options = { continuousNumber: ContinuousNumberEnum.MAX_10, }; - return options; + return { options: options }; }) ); diff --git a/react-native/src/__tests__/options/option-top-bottom-correction-rotation-support.test.tsx b/react-native/src/__tests__/options/option-top-bottom-correction-rotation-support.test.tsx new file mode 100644 index 00000000000..b148fc7b228 --- /dev/null +++ b/react-native/src/__tests__/options/option-top-bottom-correction-rotation-support.test.tsx @@ -0,0 +1,44 @@ +import { convertOptions } from '../../theta-repository/libs'; +import type { Options } from '../../theta-repository/options'; + +describe('convertOptions', () => { + test('getOption', () => { + const pitchData = { max: 10.0, min: -15.0, stepSize: 1.0 }; + const rollData = { max: 20.0, min: -5.0, stepSize: 0.5 }; + const yawData = { max: 30.0, min: -10.0, stepSize: 2.0 }; + + const jsonOptions = { + topBottomCorrectionRotationSupport: JSON.stringify({ + pitch: pitchData, + roll: rollData, + yaw: yawData, + }), + }; + + const emptyOptions: Options = {}; + const expectedOptions: Options = { + topBottomCorrectionRotationSupport: { + pitch: { + max: pitchData.max, + min: pitchData.min, + stepSize: pitchData.stepSize, + }, + roll: { + max: rollData.max, + min: rollData.min, + stepSize: rollData.stepSize, + }, + yaw: { + max: yawData.max, + min: yawData.min, + stepSize: yawData.stepSize, + }, + }, + }; + + const options = convertOptions(emptyOptions, jsonOptions); + expect(options.topBottomCorrectionRotationSupport).toEqual( + expectedOptions.topBottomCorrectionRotationSupport + ); + }); +}); diff --git a/react-native/src/capture/continuous-capture.ts b/react-native/src/capture/continuous-capture.ts index dc329cba91d..6edae739c9b 100644 --- a/react-native/src/capture/continuous-capture.ts +++ b/react-native/src/capture/continuous-capture.ts @@ -78,10 +78,8 @@ export class ContinuousCapture { * @returns ContinuousNumberEnum */ async getContinuousNumber(): Promise { - return ( - (await getOptions([OptionNameEnum.ContinuousNumber])).continuousNumber ?? - ContinuousNumberEnum.UNSUPPORTED - ); + const options = await getOptions([OptionNameEnum.ContinuousNumber]); + return options?.continuousNumber ?? ContinuousNumberEnum.UNSUPPORTED; } } diff --git a/react-native/src/theta-repository/libs/convert-utils.ts b/react-native/src/theta-repository/libs/convert-utils.ts new file mode 100644 index 00000000000..1ab8a009bae --- /dev/null +++ b/react-native/src/theta-repository/libs/convert-utils.ts @@ -0,0 +1,27 @@ +import type { Options } from '../options'; + +export function convertOptions( + options: Options, + jsonOptions?: Record +): Options { + if (!jsonOptions) { + return options; + } + + const result = { ...options }; + + let jsonString = JSON.stringify(jsonOptions); + jsonString = jsonString + .replace(/\\"/g, '"') + .replace(/\\n/g, '') + .replace(/"{/g, '{') + .replace(/}"/g, '}'); + const parsedOptions = JSON.parse(jsonString) as Options; + + if (parsedOptions.topBottomCorrectionRotationSupport) { + result.topBottomCorrectionRotationSupport = + parsedOptions.topBottomCorrectionRotationSupport; + } + + return result; +} diff --git a/react-native/src/theta-repository/libs/index.ts b/react-native/src/theta-repository/libs/index.ts index 881f73cdb1c..6dff12f6f5b 100644 --- a/react-native/src/theta-repository/libs/index.ts +++ b/react-native/src/theta-repository/libs/index.ts @@ -1 +1,2 @@ export * from './convert-video-formats'; +export * from './convert-utils'; diff --git a/react-native/src/theta-repository/options/index.ts b/react-native/src/theta-repository/options/index.ts index 48162d08cdd..39bc36eaf59 100644 --- a/react-native/src/theta-repository/options/index.ts +++ b/react-native/src/theta-repository/options/index.ts @@ -30,6 +30,7 @@ export * from './option-sleep-delay'; export * from './option-time-shift'; export * from './option-top-bottom-correction'; export * from './option-top-bottom-correction-rotation'; +export * from './option-top-bottom-correction-rotation-support'; export * from './option-video-stitching'; export * from './option-visibility-reduction'; export * from './option-white-balance-auto-strength'; diff --git a/react-native/src/theta-repository/options/option-top-bottom-correction-rotation-support.ts b/react-native/src/theta-repository/options/option-top-bottom-correction-rotation-support.ts new file mode 100644 index 00000000000..ab1bfc052bb --- /dev/null +++ b/react-native/src/theta-repository/options/option-top-bottom-correction-rotation-support.ts @@ -0,0 +1,35 @@ +/** + * Supported TopBottomCorrectionRotation + */ +export type TopBottomCorrectionRotationSupport = { + /** + * Supported pitch + */ + pitch: TopBottomCorrectionRotationValueSupport; + /** + * Supported roll + */ + roll: TopBottomCorrectionRotationValueSupport; + /** + * Supported yaw + */ + yaw: TopBottomCorrectionRotationValueSupport; +}; + +/** + * Supported value of TopBottomCorrectionRotation + */ +export type TopBottomCorrectionRotationValueSupport = { + /** + * maximum value + */ + max: number; + /** + * minimum value + */ + min: number; + /** + * Step size + */ + stepSize: number; +}; diff --git a/react-native/src/theta-repository/options/options.ts b/react-native/src/theta-repository/options/options.ts index 16148d98e27..1fc52c5679c 100644 --- a/react-native/src/theta-repository/options/options.ts +++ b/react-native/src/theta-repository/options/options.ts @@ -34,6 +34,7 @@ import type { EthernetConfig } from './option-ethernet-config'; import type { FileFormatEnum } from './option-file-format'; import type { CameraPowerEnum } from './option-camera-power'; import type { ColorTemperatureSupport } from './option-color-temperature-support'; +import type { TopBottomCorrectionRotationSupport } from './option-top-bottom-correction-rotation-support'; /** Aperture value. */ export const ApertureEnum = { @@ -436,6 +437,8 @@ export const OptionNameEnum = { TopBottomCorrection: 'TopBottomCorrection', /** topBottomCorrectionRotation */ TopBottomCorrectionRotation: 'TopBottomCorrectionRotation', + /** topBottomCorrectionRotationSupport */ + TopBottomCorrectionRotationSupport: 'TopBottomCorrectionRotationSupport', /** totalSpace */ TotalSpace: 'TotalSpace', /** shutterVolume */ @@ -627,6 +630,8 @@ export type Options = { * Enabled only for _topBottomCorrection Manual. */ topBottomCorrectionRotation?: TopBottomCorrectionRotation; + /** Supported TopBottomCorrectionRotation */ + topBottomCorrectionRotationSupport?: TopBottomCorrectionRotationSupport; /** Total storage space (byte). */ totalSpace?: number; /** User name used for digest authentication when _networkType is set to client mode. */ diff --git a/react-native/src/theta-repository/theta-repository.ts b/react-native/src/theta-repository/theta-repository.ts index 4be2b0ce1f4..d2ec104b350 100644 --- a/react-native/src/theta-repository/theta-repository.ts +++ b/react-native/src/theta-repository/theta-repository.ts @@ -33,7 +33,7 @@ import type { ThetaConfig } from './theta-config'; import type { ThetaTimeout } from './theta-timeout'; import { NotifyController } from './notify-controller'; import { EventWebSocket } from './event-websocket'; -import { convertVideoFormatsImpl } from './libs'; +import { convertOptions, convertVideoFormatsImpl } from './libs'; const ThetaClientReactNative = NativeModules.ThetaClientReactNative; /** @@ -415,8 +415,13 @@ export function deleteAllVideoFiles(): Promise { * @param {OptionNameEnum[]} optionNames List of OptionNameEnum. * @return promise of Options acquired */ -export function getOptions(optionNames: OptionNameEnum[]): Promise { - return ThetaClientReactNative.getOptions(optionNames); +export async function getOptions( + optionNames: OptionNameEnum[] +): Promise { + const response = await ThetaClientReactNative.getOptions(optionNames); + const { options, json } = response; + const result = convertOptions(options, json); + return result; } /** diff --git a/react-native/verification-tool/src/screen/options-screen/options-screen.tsx b/react-native/verification-tool/src/screen/options-screen/options-screen.tsx index ae4e19e1f8f..5a56b790e62 100644 --- a/react-native/verification-tool/src/screen/options-screen/options-screen.tsx +++ b/react-native/verification-tool/src/screen/options-screen/options-screen.tsx @@ -485,6 +485,24 @@ const optionList: OptionItem[] = [ }, }, }, + { + name: 'topBottomCorrectionRotation', + value: { + optionName: OptionNameEnum.TopBottomCorrectionRotation, + editor: (options, onChange) => ( + + ), + }, + }, + { + name: 'topBottomCorrectionRotationSupport', + value: { + optionName: OptionNameEnum.TopBottomCorrectionRotationSupport, + }, + }, { name: 'videoStitching', value: { @@ -504,18 +522,6 @@ const optionList: OptionItem[] = [ }, }, }, - { - name: 'topBottomCorrectionRotation', - value: { - optionName: OptionNameEnum.TopBottomCorrectionRotation, - editor: (options, onChange) => ( - - ), - }, - }, { name: 'visibilityReduction', value: { @@ -620,6 +626,8 @@ const OptionsScreen: React.FC< selectedItem={selectedOption} placeHolder="select option" /> + +