diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b869811..cb7bb11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: uses: ./.github/actions/job-set-up - name: Run tests - run: ./gradlew :shared:cleanTestDebugUnitTest :shared:testDebugUnitTest + run: ./gradlew :composeApp:cleanTestDebugUnitTest :composeApp:testDebugUnitTest - name: Upload reports if: always() @@ -46,10 +46,10 @@ jobs: uses: ./.github/actions/job-set-up - name: Compile Android binary - run: ./gradlew :androidApp:clean :androidApp:build + run: ./gradlew :composeApp:clean :composeApp:assembleDebug - name: Run tests - run: ./gradlew :androidApp:cleanTestDebugUnitTest :shared:testDebugUnitTest + run: ./gradlew :composeApp:cleanTestDebugUnitTest :composeApp:testDebugUnitTest - name: Upload reports if: always() @@ -80,8 +80,11 @@ jobs: with: cache-disabled: true - - name: run tests - run: ./gradlew :shared:cleanIosSimulatorArm64Test :shared:iosSimulatorArm64Test + - name: Compile iOS binary + run: ./gradlew :composeApp:clean :composeApp:iosSimulatorArm64Binaries + + - name: Run tests + run: ./gradlew :composeApp:cleanIosSimulatorArm64Test :composeApp:iosSimulatorArm64Test - name: Upload reports if: always() diff --git a/.gitignore b/.gitignore index dd9f5d8..5e6537a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,38 +1,26 @@ *.iml .gradle .idea -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml .DS_Store build/ -/captures .externalNativeBuild .cxx local.properties venv/ yarn.lock -iosApp/Podfile.lock -iosApp/Pods/* -iosApp/iosApp.xcworkspace/* -iosApp/iosApp.xcodeproj/* -!iosApp/iosApp.xcodeproj/project.pbxproj -shared/shared.podspec -androidApp/release - -# IntelliJ .idea folder -.idea/workspace.xml -.idea/misc.xml -.idea/caches -.idea/compiler.xml -.idea/artifacts -.idea/deploymentTargetDropDown.xml -.idea/androidTestResultsUserPreferences.xml -.idea/assetWizardSettings.xml -.idea/modules.xml -.idea/appInsightsSettings.xml +backup/androidApp/release +.kotlin +**/build/ +xcuserdata +!src/**/build/ +captures +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +**/xcshareddata/WorkspaceSettings.xcsettings gradle.xml + +# Store the old files in a temporary folder until refactor is complete +backup/ \ No newline at end of file diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts deleted file mode 100644 index 90223e5..0000000 --- a/androidApp/build.gradle.kts +++ /dev/null @@ -1,73 +0,0 @@ -plugins { - kotlin("android") - id("com.android.application") - id("com.google.devtools.ksp") - id("kotlin-parcelize") - id("io.gitlab.arturbosch.detekt") version libs.versions.detekt.get() -} - -android { - namespace = "org.noiseplanet.noisecapture" - compileSdk = libs.versions.android.compile.sdk.get().toInt() - defaultConfig { - applicationId = "org.noiseplanet.noisecapturekmp" - minSdk = libs.versions.android.min.sdk.get().toInt() - targetSdk = libs.versions.android.target.sdk.get().toInt() - versionCode = 1 - versionName = "1.0" - } - buildFeatures { - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() - } - buildTypes { - release { - isMinifyEnabled = true - multiDexEnabled = true - signingConfig = signingConfigs.getByName("debug") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = libs.versions.jvm.target.get() - } - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() - } - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } -} - -dependencies { - implementation(libs.androidx.core) - implementation(libs.compose.ui.ui) - implementation(libs.compose.material) - implementation(libs.compose.ui.tooling) - implementation(libs.androidx.lifecycle.runtime) - implementation(libs.androidx.activity.compose) - implementation(project(":shared")) - - testImplementation(libs.junit) - - androidTestImplementation(libs.androidx.test.junit) - androidTestImplementation(libs.androidx.test.espresso.core) - androidTestImplementation(libs.compose.ui.test.junit4) - - debugImplementation(libs.compose.ui.test.manifest) - - // Appyx - implementation(libs.appyx.navigation) - implementation(libs.appyx.components.backstack) - ksp(libs.appyx.processor) - - // Koin - implementation(libs.koin.android) -} diff --git a/androidApp/proguard-rules.pro b/androidApp/proguard-rules.pro deleted file mode 100644 index 2f9dc5a..0000000 --- a/androidApp/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle.kts. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/androidApp/src/main/kotlin/org/noiseplanet/noisecapture/starter/MainActivity.kt b/androidApp/src/main/kotlin/org/noiseplanet/noisecapture/starter/MainActivity.kt deleted file mode 100644 index 6937268..0000000 --- a/androidApp/src/main/kotlin/org/noiseplanet/noisecapture/starter/MainActivity.kt +++ /dev/null @@ -1,114 +0,0 @@ -package org.noiseplanet.noisecapture.starter - -import android.annotation.SuppressLint -import android.app.Activity -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.ServiceConnection -import android.os.Bundle -import android.os.IBinder -import androidx.activity.compose.setContent -import androidx.compose.ui.platform.LocalLifecycleOwner -import com.bumble.appyx.navigation.integration.NodeActivity -import com.bumble.appyx.navigation.integration.NodeHost -import com.bumble.appyx.navigation.platform.AndroidLifecycle -import org.koin.android.logger.AndroidLogger -import org.koin.core.context.stopKoin -import org.koin.dsl.module -import org.noiseplanet.noisecapture.AndroidAudioSource -import org.noiseplanet.noisecapture.AndroidDatabase -import org.noiseplanet.noisecapture.AndroidMeasurementService -import org.noiseplanet.noisecapture.DatabaseDriverFactory -import org.noiseplanet.noisecapture.shared.MeasurementService -import org.noiseplanet.noisecapture.shared.initKoin -import org.noiseplanet.noisecapture.shared.root.RootNode -import org.noiseplanet.noisecapture.shared.ui.theme.AppyxStarterKitTheme -import kotlin.reflect.KProperty - -class MainActivity : NodeActivity() { - - private val androidLogger = AndroidLogger() - private val foregroundServiceConnection = ForegroundServiceConnection() - - internal class ForegroundServiceConnection : ServiceConnection { - - @SuppressLint("MissingPermission") - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - println("onServiceConnected $name $service") - // This is called when the connection with the service has been - // established, giving us the service object we can use to - // interact with the service. Because we have bound to a explicit - // service that we know is running in our own process, we can - // cast its IBinder to a concrete class and directly access it. - } - - override fun onServiceDisconnected(name: ComponentName?) { - println("onServiceConnected $name") - } - } - - override fun onDestroy() { - super.onDestroy() - stopKoin() - } - - override fun onRestart() { - super.onRestart() - println("onRestart") - } - - override fun onStop() { - super.onStop() - println("OnStop") - } - - @Suppress("UnusedParameter") - private fun onStorageStateChange(property: KProperty<*>, oldValue: Boolean, newValue: Boolean) { - // bind this application context to Android Foreground service if storage is launched - // in order to avoid application shutdown by Android when moved in background - if (newValue) { - val intent = Intent(applicationContext, AndroidMeasurementService::class.java) - if (applicationContext.bindService( - intent, foregroundServiceConnection, - Context.BIND_AUTO_CREATE - ) - ) { - androidLogger.info("Bind with foreground service") - } else { - androidLogger.info("Can't bind with foreground service") - } - } else { - applicationContext.unbindService(foregroundServiceConnection) - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val koinApplication = initKoin( - additionalModules = listOf( - module { - single { applicationContext } - single { this@MainActivity } - single { - val measurementService = - MeasurementService(AndroidAudioSource(logger)) - measurementService.storageObservers.add(::onStorageStateChange) - measurementService - } - single { AndroidDatabase(applicationContext) } - } - ) - ).logger(androidLogger) - setContent { - AppyxStarterKitTheme { - NodeHost( - lifecycle = AndroidLifecycle(LocalLifecycleOwner.current.lifecycle), - integrationPoint = appyxIntegrationPoint, - ) { - RootNode(nodeContext = it, koin = koinApplication.koin) - } - } - } - } -} diff --git a/androidApp/src/main/res/mipmap-hdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78..0000000 Binary files a/androidApp/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d..0000000 Binary files a/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-mdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d6..0000000 Binary files a/androidApp/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a307..0000000 Binary files a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a695..0000000 Binary files a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/androidApp/src/main/res/values/colors.xml b/androidApp/src/main/res/values/colors.xml deleted file mode 100644 index ca1931b..0000000 --- a/androidApp/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml deleted file mode 100644 index 80f9f8c..0000000 --- a/androidApp/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - NoiseCaptureKMP - diff --git a/androidApp/src/main/res/values/themes.xml b/androidApp/src/main/res/values/themes.xml deleted file mode 100644 index 6b69f0f..0000000 --- a/androidApp/src/main/res/values/themes.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/androidApp/src/main/res/xml/backup_rules.xml b/androidApp/src/main/res/xml/backup_rules.xml deleted file mode 100644 index 148c18b..0000000 --- a/androidApp/src/main/res/xml/backup_rules.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/androidApp/src/main/res/xml/data_extraction_rules.xml b/androidApp/src/main/res/xml/data_extraction_rules.xml deleted file mode 100644 index 0c4f95c..0000000 --- a/androidApp/src/main/res/xml/data_extraction_rules.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - diff --git a/build.gradle.kts b/build.gradle.kts index 19bd7bc..00d51bb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,16 +1,13 @@ import io.gitlab.arturbosch.detekt.Detekt +import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - kotlin("android") version libs.versions.kotlin.get() apply false - kotlin("multiplatform") version libs.versions.kotlin.get() apply false - id("com.android.application") version libs.versions.agp.get() apply false - id("org.jetbrains.compose") version libs.versions.compose.plugin.get() apply false - id("com.google.devtools.ksp") version libs.versions.ksp.get() apply false - id("io.gitlab.arturbosch.detekt") version libs.versions.detekt.get() -} - -repositories { - mavenCentral() + alias(libs.plugins.androidApplication) apply false + alias(libs.plugins.androidLibrary) apply false + alias(libs.plugins.jetbrainsCompose) apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.detekt) } allprojects { @@ -21,17 +18,13 @@ allprojects { allRules = true config.setFrom("$rootDir/config/detekt.yml") source.setFrom( - "src/main/kotlin", - "src/iosMain/kotlin", - "src/androidMain/kotlin", - "src/jsMain/kotlin", - "src/commonMain/kotlin", + "composeApp" ) autoCorrect = true } tasks.withType().configureEach { - jvmTarget = libs.versions.jvm.target.get() + jvmTarget = JvmTarget.JVM_18.target reports { html.required.set(true) } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts new file mode 100644 index 0000000..2891a33 --- /dev/null +++ b/composeApp/build.gradle.kts @@ -0,0 +1,122 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.compose.compiler) +} + +kotlin { + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "composeApp" + browser { + commonWebpackConfig { + outputFileName = "composeApp.js" + devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { + static = (static ?: mutableListOf()).apply { + // Serve sources to debug inside browser + add(project.projectDir.path) + add(project.projectDir.path + "/commonMain/") + add(project.projectDir.path + "/wasmJSMain/") + } + } + } + } + binaries.executable() + } + + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + sourceSets { + all { + languageSettings { + optIn("androidx.compose.material3.ExperimentalMaterial3Api") + optIn("org.jetbrains.compose.resources.ExperimentalResourceApi") + } + } + + androidMain.dependencies { + implementation(compose.preview) + implementation(libs.androidx.activity.compose) + implementation(libs.koin.android) + } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.androidx.navigation.compose) + implementation(libs.koin.core) + implementation(libs.koin.compose) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + } + commonTest.dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + implementation(compose.components.resources) + } + } +} + +android { + namespace = "org.noiseplanet.noisecapture" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + sourceSets["main"].resources.srcDirs("src/commonMain/resources") + + defaultConfig { + applicationId = "org.noiseplanet.noisecapturekmp" + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + versionCode = 1 + versionName = "0.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + buildFeatures { + compose = true + } + dependencies { + debugImplementation(compose.uiTooling) + } +} diff --git a/androidApp/src/main/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml similarity index 72% rename from androidApp/src/main/AndroidManifest.xml rename to composeApp/src/androidMain/AndroidManifest.xml index 1ab61c7..e688bcc 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -14,26 +13,21 @@ + android:theme="@android:style/Theme.Material.Light.NoActionBar"> + android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode" + android:name=".MainActivity"> + - - diff --git a/composeApp/src/androidMain/kotlin/Platform.android.kt b/composeApp/src/androidMain/kotlin/Platform.android.kt new file mode 100644 index 0000000..f7e8b6d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/Platform.android.kt @@ -0,0 +1,20 @@ +import android.os.Build +import org.noiseplanet.noisecapture.permission.Permission + +class AndroidPlatform : Platform { + + override val name: String = "Android ${Build.VERSION.SDK_INT}" + + override val requiredPermissions: List + /** + * We have to request foreground location before asking for background location + */ + get() = listOf( + Permission.RECORD_AUDIO, + Permission.LOCATION_SERVICE_ON, + Permission.LOCATION_FOREGROUND, + Permission.LOCATION_BACKGROUND + ) +} + +actual fun getPlatform(): Platform = AndroidPlatform() diff --git a/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/MainActivity.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/MainActivity.kt new file mode 100644 index 0000000..c820973 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/MainActivity.kt @@ -0,0 +1,40 @@ +package org.noiseplanet.noisecapture + +import App +import android.app.Activity +import android.content.Context +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import org.koin.android.logger.AndroidLogger +import org.koin.core.logger.Logger +import org.koin.dsl.module + +/** + * Android app entry point + */ +class MainActivity : ComponentActivity() { + + private val androidLogger = AndroidLogger() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initKoin( + additionalModules = listOf( + module { + single { applicationContext } + single { this@MainActivity } + }, + module { + single { androidLogger } + }, + platformModule + ) + ) + + setContent { + App() + } + } +} diff --git a/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/PlatformModule.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/PlatformModule.kt new file mode 100644 index 0000000..f9c147f --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/PlatformModule.kt @@ -0,0 +1,13 @@ +package org.noiseplanet.noisecapture + +import org.koin.core.module.Module +import org.koin.dsl.module +import org.noiseplanet.noisecapture.audio.AndroidAudioSource +import org.noiseplanet.noisecapture.audio.AudioSource + +/** + * Registers koin components specific to this platform + */ +val platformModule: Module = module { + factory { AndroidAudioSource(logger = get()) } +} diff --git a/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/audio/AndroidAudioSource.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/audio/AndroidAudioSource.kt new file mode 100644 index 0000000..bb837a2 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/audio/AndroidAudioSource.kt @@ -0,0 +1,39 @@ +package org.noiseplanet.noisecapture.audio + +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.consumeAsFlow +import org.koin.core.logger.Logger + +/** + * Android audio source implementation + * + * @param logger Logger instance + */ +internal class AndroidAudioSource(private val logger: Logger) : AudioSource { + + private var audioThread: Thread? = null + private var audioRecorder: AudioRecorder? = null + + override suspend fun setup(): Flow { + val audioSamplesChannel = Channel( + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + // Create a recorder that will process raw incoming audio into audio samples + // and broadcast it through the channel. + audioRecorder = AudioRecorder(audioSamplesChannel, logger) + // Start audio recording in a background thread and return the channel as a Flow + audioThread = Thread(audioRecorder) + audioThread?.start() + return audioSamplesChannel.consumeAsFlow() + } + + override fun release() { + audioRecorder?.stopRecording() + } + + override fun getMicrophoneLocation(): AudioSource.MicrophoneLocation { + return AudioSource.MicrophoneLocation.LOCATION_UNKNOWN + } +} diff --git a/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/audio/AudioRecorder.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/audio/AudioRecorder.kt new file mode 100644 index 0000000..cdeb909 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/audio/AudioRecorder.kt @@ -0,0 +1,142 @@ +package org.noiseplanet.noisecapture.audio + +import android.annotation.SuppressLint +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.AudioRecord.ERROR +import android.media.AudioRecord.ERROR_BAD_VALUE +import android.media.MediaRecorder +import android.os.Process +import kotlinx.coroutines.channels.Channel +import org.koin.core.logger.Logger +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Processes audio coming through input microphone and broadcasts it through the given + * [audioSamplesChannel]. Should be ran in a background thread. + * + * TODO: What happens if the user revokes permission while the app is in the background? + */ +@SuppressLint("MissingPermission") +class AudioRecorder( + private val audioSamplesChannel: Channel, + private val logger: Logger, +) : Runnable { + + private companion object { + + private const val BUFFER_SIZE_TIME = 0.1 + } + + private var audioRecord: AudioRecord + private var bufferSize: Int + private var sampleRate: Int + private val isRecording = AtomicBoolean(false) + + init { + val possibleSampleRates = intArrayOf(48_000, 44_100) + val channel = AudioFormat.CHANNEL_IN_MONO + val encoding = AudioFormat.ENCODING_PCM_FLOAT + + // Try to find a suitable sample rate for this device + val (sampleRate, minBufferSize) = try { + possibleSampleRates.map { + Pair(it, AudioRecord.getMinBufferSize(it, channel, encoding)) + }.filterNot { (_, minBufferSize) -> + minBufferSize == ERROR_BAD_VALUE || minBufferSize == ERROR + }.first() + } catch (err: NoSuchElementException) { + val message = "Could not find a suitable sample rate" + logger.error(message) + logger.error(err.stackTraceToString()) + throw IllegalStateException(message, err) + } + + bufferSize = Integer.max( + minBufferSize, + (BUFFER_SIZE_TIME * sampleRate * 4).toInt() + ) + audioRecord = AudioRecord( + MediaRecorder.AudioSource.VOICE_RECOGNITION, + sampleRate, + channel, + encoding, + bufferSize + ) + this.sampleRate = sampleRate + } + + override fun run() { + isRecording.set(true) + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO) + } catch (ignore: IllegalArgumentException) { + // Ignore + } catch (ignore: SecurityException) { + // Ignore + } + try { + audioRecord.startRecording() + logger.debug("Capture microphone") + while (isRecording.get()) { + processBuffer() + } + broadcastAudioSamples(FloatArray(0), AudioSamples.ErrorCode.ABORTED) + audioRecord.stop() + } catch (e: IllegalStateException) { + logger.error("${e.localizedMessage}\n${e.stackTraceToString()}") + } + isRecording.set(false) + logger.debug("Release microphone") + } + + /** + * Stops the current audio recording + */ + fun stopRecording() { + isRecording.set(false) + } + + /** + * Processes the current audio data stored in the buffer + */ + private fun processBuffer() { + var buffer = FloatArray(bufferSize / 4) + + val read: Int = audioRecord.read( + buffer, + 0, + buffer.size, + AudioRecord.READ_BLOCKING + ) + + if (read < buffer.size) { + if (read > 0) { + buffer = buffer.copyOfRange(0, read) + broadcastAudioSamples(buffer) + } else { + broadcastAudioSamples(buffer.clone(), AudioSamples.ErrorCode.ABORTED) + isRecording.set(false) + } + } else { + broadcastAudioSamples(buffer.clone()) + } + } + + /** + * Broadcasts audio samples through the [audioSamplesChannel] + */ + private fun broadcastAudioSamples( + samples: FloatArray, + errorCode: AudioSamples.ErrorCode? = null, + ) { + audioSamplesChannel.trySend( + AudioSamples( + System.currentTimeMillis(), + samples, + sampleRate, + errorCode + ) + ) + } +} diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PlatformModule.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.android.kt similarity index 67% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PlatformModule.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.android.kt index 2872012..f762503 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PlatformModule.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.android.kt @@ -1,21 +1,20 @@ -package com.adrianwitaszak.kmmpermissions.permissions +package org.noiseplanet.noisecapture.permission import android.bluetooth.BluetoothManager import android.content.Context import android.location.LocationManager -import com.adrianwitaszak.kmmpermissions.permissions.delegate.AudioRecordPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.BluetoothPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.BluetoothServicePermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.LocationBackgroundPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.LocationForegroundPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.LocationServicePermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.PermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission import org.koin.core.module.Module import org.koin.core.qualifier.named import org.koin.dsl.module +import org.noiseplanet.noisecapture.permission.delegate.AudioRecordPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.BluetoothPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.BluetoothServicePermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.LocationBackgroundPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.LocationForegroundPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.LocationServicePermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.PermissionDelegate -internal actual fun platformModule(): Module = module { +internal actual fun platformPermissionModule(): Module = module { single(named(Permission.BLUETOOTH_SERVICE_ON.name)) { BluetoothServicePermissionDelegate( context = get(), diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/AudioRecordPermissionDelegate.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt similarity index 54% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/AudioRecordPermissionDelegate.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt index e0e65eb..c9cdcc4 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/AudioRecordPermissionDelegate.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt @@ -1,21 +1,22 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate import android.Manifest import android.app.Activity import android.content.Context -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.PermissionRequestException -import com.adrianwitaszak.kmmpermissions.permissions.util.checkPermissions -import com.adrianwitaszak.kmmpermissions.permissions.util.openAppSettingsPage -import com.adrianwitaszak.kmmpermissions.permissions.util.providePermissions +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.PermissionRequestException +import org.noiseplanet.noisecapture.permission.util.checkPermissions +import org.noiseplanet.noisecapture.permission.util.openAppSettingsPage +import org.noiseplanet.noisecapture.permission.util.providePermissions internal class AudioRecordPermissionDelegate( private val context: Context, private val activity: Lazy, ) : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { - return checkPermissions(context, audioRecordPermissions) + return activity.value.checkPermissions(audioRecordPermissions) } override suspend fun providePermission() { diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothPermissionDelegate.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothPermissionDelegate.kt similarity index 60% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothPermissionDelegate.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothPermissionDelegate.kt index 4719de4..a81fed3 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothPermissionDelegate.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothPermissionDelegate.kt @@ -1,22 +1,23 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate import android.Manifest import android.app.Activity import android.content.Context import android.os.Build -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.PermissionRequestException -import com.adrianwitaszak.kmmpermissions.permissions.util.checkPermissions -import com.adrianwitaszak.kmmpermissions.permissions.util.openAppSettingsPage -import com.adrianwitaszak.kmmpermissions.permissions.util.providePermissions +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.PermissionRequestException +import org.noiseplanet.noisecapture.permission.util.checkPermissions +import org.noiseplanet.noisecapture.permission.util.openAppSettingsPage +import org.noiseplanet.noisecapture.permission.util.providePermissions internal class BluetoothPermissionDelegate( private val context: Context, private val activity: Lazy, ) : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { - return checkPermissions(context, bluetoothPermissions) + return activity.value.checkPermissions(bluetoothPermissions) } override suspend fun providePermission() { diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothServicePermissionDelegate.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothServicePermissionDelegate.kt similarity index 68% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothServicePermissionDelegate.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothServicePermissionDelegate.kt index 0fc6716..2a23683 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothServicePermissionDelegate.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothServicePermissionDelegate.kt @@ -1,17 +1,18 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate import android.bluetooth.BluetoothAdapter import android.content.Context import android.provider.Settings -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.CannotOpenSettingsException -import com.adrianwitaszak.kmmpermissions.permissions.util.openPage +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.CannotOpenSettingsException +import org.noiseplanet.noisecapture.permission.util.openPage internal class BluetoothServicePermissionDelegate( private val context: Context, private val bluetoothAdapter: BluetoothAdapter?, ) : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { return if (bluetoothAdapter?.isEnabled == true) PermissionState.GRANTED else PermissionState.DENIED diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationBackgroundPermissionDelegate.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt similarity index 67% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationBackgroundPermissionDelegate.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt index 71a3832..3076f13 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationBackgroundPermissionDelegate.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt @@ -1,25 +1,26 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate import android.Manifest import android.app.Activity import android.content.Context import android.os.Build -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.PermissionRequestException -import com.adrianwitaszak.kmmpermissions.permissions.util.checkPermissions -import com.adrianwitaszak.kmmpermissions.permissions.util.openAppSettingsPage -import com.adrianwitaszak.kmmpermissions.permissions.util.providePermissions +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.PermissionRequestException +import org.noiseplanet.noisecapture.permission.util.checkPermissions +import org.noiseplanet.noisecapture.permission.util.openAppSettingsPage +import org.noiseplanet.noisecapture.permission.util.providePermissions internal class LocationBackgroundPermissionDelegate( private val context: Context, private val activity: Lazy, private val locationForegroundPermissionDelegate: PermissionDelegate, ) : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { return when (locationForegroundPermissionDelegate.getPermissionState()) { PermissionState.GRANTED -> - checkPermissions(context, backgroundLocationPermissions) + activity.value.checkPermissions(backgroundLocationPermissions) else -> PermissionState.NOT_DETERMINED } diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationForegroundPermissionDelegate.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationForegroundPermissionDelegate.kt similarity index 64% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationForegroundPermissionDelegate.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationForegroundPermissionDelegate.kt index 187b9a6..34f4e8c 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationForegroundPermissionDelegate.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationForegroundPermissionDelegate.kt @@ -1,22 +1,23 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate import android.Manifest import android.app.Activity import android.content.Context import android.os.Build -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.PermissionRequestException -import com.adrianwitaszak.kmmpermissions.permissions.util.checkPermissions -import com.adrianwitaszak.kmmpermissions.permissions.util.openAppSettingsPage -import com.adrianwitaszak.kmmpermissions.permissions.util.providePermissions +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.PermissionRequestException +import org.noiseplanet.noisecapture.permission.util.checkPermissions +import org.noiseplanet.noisecapture.permission.util.openAppSettingsPage +import org.noiseplanet.noisecapture.permission.util.providePermissions internal class LocationForegroundPermissionDelegate( private val context: Context, private val activity: Lazy, ) : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { - return checkPermissions(context, fineLocationPermissions) + return activity.value.checkPermissions(fineLocationPermissions) } override suspend fun providePermission() { diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationServicePermissionDelegate.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationServicePermissionDelegate.kt similarity index 54% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationServicePermissionDelegate.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationServicePermissionDelegate.kt index c724214..a80c462 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationServicePermissionDelegate.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationServicePermissionDelegate.kt @@ -1,23 +1,26 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate import android.content.Context import android.location.LocationManager import android.provider.Settings -import com.adrianwitaszak.kmmpermissions.permissions.delegate.PermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.CannotOpenSettingsException -import com.adrianwitaszak.kmmpermissions.permissions.util.openPage +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.CannotOpenSettingsException +import org.noiseplanet.noisecapture.permission.util.openPage internal class LocationServicePermissionDelegate( private val context: Context, private val locationManager: LocationManager, ) : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { val granted = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || - locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) - return if (granted) - PermissionState.GRANTED else PermissionState.DENIED + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + return if (granted) { + PermissionState.GRANTED + } else { + PermissionState.DENIED + } } override suspend fun providePermission() { diff --git a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.android.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/util/Extensions.android.kt similarity index 72% rename from permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.android.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/util/Extensions.android.kt index 6beb00e..ffe0904 100644 --- a/permissions/src/androidMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.android.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/permission/util/Extensions.android.kt @@ -1,6 +1,6 @@ @file:Suppress("TooGenericExceptionCaught") -package com.adrianwitaszak.kmmpermissions.permissions.util +package org.noiseplanet.noisecapture.permission.util import android.app.Activity import android.content.Context @@ -9,8 +9,9 @@ import android.content.pm.PackageManager import android.net.Uri import android.provider.Settings import androidx.core.app.ActivityCompat -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState +import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState internal fun Context.openPage( action: String, @@ -28,24 +29,28 @@ internal fun Context.openPage( } } -internal fun checkPermissions( - context: Context, +internal fun Activity.checkPermissions( permissions: List, ): PermissionState { permissions.ifEmpty { return PermissionState.GRANTED } // no permissions needed val status: List = permissions.map { - context.checkSelfPermission(it) + this.checkSelfPermission(it) + } + val isOneDenied: Boolean = permissions.all() { + this.shouldShowRequestPermissionRationale(it) } val isAllGranted: Boolean = status.all { it == PackageManager.PERMISSION_GRANTED } - if (isAllGranted) { - return PermissionState.GRANTED + return if (isAllGranted) { + PermissionState.GRANTED + } else if(isOneDenied) { + PermissionState.DENIED + } else { + PermissionState.NOT_DETERMINED } - - return PermissionState.DENIED } private const val REQUEST_PERMISSION_CODE = 100 diff --git a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.android.kt b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.kt similarity index 89% rename from shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.android.kt rename to composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.kt index 372ea78..63c9544 100644 --- a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.android.kt +++ b/composeApp/src/androidMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture +package org.noiseplanet.noisecapture.util import android.graphics.Bitmap import android.graphics.BitmapFactory diff --git a/androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml b/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml old mode 100644 new mode 100755 similarity index 99% rename from androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml index 7706ab9..2b068d1 --- a/androidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml @@ -27,4 +27,4 @@ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" android:strokeWidth="1" android:strokeColor="#00000000" /> - + \ No newline at end of file diff --git a/androidApp/src/main/res/drawable/ic_launcher_background.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml old mode 100644 new mode 100755 similarity index 99% rename from androidApp/src/main/res/drawable/ic_launcher_background.xml rename to composeApp/src/androidMain/res/drawable/ic_launcher_background.xml index 07d5da9..e93e11a --- a/androidApp/src/main/res/drawable/ic_launcher_background.xml +++ b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml @@ -167,4 +167,4 @@ android:pathData="M79,19L79,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> - + \ No newline at end of file diff --git a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml old mode 100644 new mode 100755 similarity index 93% rename from androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml index 6b78462..eca70cf --- a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,4 @@ - + \ No newline at end of file diff --git a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml old mode 100644 new mode 100755 similarity index 93% rename from androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml index 6b78462..eca70cf --- a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,4 @@ - + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 0000000..a571e60 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png new file mode 100755 index 0000000..61da551 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png new file mode 100755 index 0000000..c41dd28 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png new file mode 100755 index 0000000..db5080a Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png new file mode 100755 index 0000000..6dba46d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100755 index 0000000..da31a87 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 0000000..15ac681 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100755 index 0000000..b216f2d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100755 index 0000000..f25a419 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100755 index 0000000..e96783c Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml new file mode 100755 index 0000000..8456e41 --- /dev/null +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + NoiseCapture + \ No newline at end of file diff --git a/composeApp/src/androidUnitTest/kotlin/IgnoreUtil.android.kt b/composeApp/src/androidUnitTest/kotlin/IgnoreUtil.android.kt new file mode 100644 index 0000000..f5a2154 --- /dev/null +++ b/composeApp/src/androidUnitTest/kotlin/IgnoreUtil.android.kt @@ -0,0 +1,12 @@ +import org.junit.Ignore + +/** + * Alias for [kotlin.test.Ignore] + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreIos + +/** + * Doesn't do anything + */ +actual typealias IgnoreAndroid = Ignore diff --git a/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml new file mode 100755 index 0000000..c0bcfb2 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 0000000..b204311 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,29 @@ + + NoiseCapture + Back + + + Platform information + + + New measurement + Measurements history + Measurement feedback + Measurements statistics + Last measurement map + Help + About + Calibration + Settings + + + Permissions + In order to measure the sound level and place it on a map the application need to have access to sensitive sensors. Please tap on "Request" buttons. + Settings + Next + Request + + + + New measurement + diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt new file mode 100644 index 0000000..67f06ba --- /dev/null +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -0,0 +1,18 @@ +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.KoinContext +import org.noiseplanet.noisecapture.NoiseCaptureApp + +/** + * Entry point of the Compose app. + */ +@Composable +@Preview +fun App() { + KoinContext { + MaterialTheme { + NoiseCaptureApp() + } + } +} diff --git a/composeApp/src/commonMain/kotlin/Platform.kt b/composeApp/src/commonMain/kotlin/Platform.kt new file mode 100755 index 0000000..db1dc61 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/Platform.kt @@ -0,0 +1,29 @@ +import org.noiseplanet.noisecapture.permission.Permission + +/** + * Describes a platform running the app. + */ +interface Platform { + + /** + * Platform name (e.g. iOS, Android, Web, ...) + */ + val name: String + + /** + * Permissions required to run the app on this platform. + * Those can differ based on which features are made available for the app or the amount + * of control given to the user on those permissions. + */ + val requiredPermissions: List + get() = listOf( + Permission.RECORD_AUDIO, + Permission.LOCATION_BACKGROUND, + Permission.LOCATION_SERVICE_ON + ) +} + +/** + * Gets the [Platform] implementation for the current target. + */ +expect fun getPlatform(): Platform diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/Koin.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/Koin.kt new file mode 100644 index 0000000..4d40432 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/Koin.kt @@ -0,0 +1,32 @@ +package org.noiseplanet.noisecapture + +import org.koin.core.KoinApplication +import org.koin.core.context.startKoin +import org.koin.core.module.Module +import org.koin.dsl.module +import org.noiseplanet.noisecapture.measurements.MeasurementService +import org.noiseplanet.noisecapture.permission.DefaultPermissionService +import org.noiseplanet.noisecapture.permission.PermissionService +import org.noiseplanet.noisecapture.permission.defaultPermissionModule +import org.noiseplanet.noisecapture.permission.platformPermissionModule + +/** + * Create root Koin application and register modules shared between platforms + */ +fun initKoin( + additionalModules: List = emptyList(), +): KoinApplication { + return startKoin { + modules( + module { + includes(additionalModules) + + single { DefaultPermissionService() } + single { MeasurementService(audioSource = get()) } + }, + defaultPermissionModule, + platformPermissionModule() + ) + createEagerInstances() + } +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/NoiseCaptureApp.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/NoiseCaptureApp.kt new file mode 100644 index 0000000..450424d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/NoiseCaptureApp.kt @@ -0,0 +1,103 @@ +package org.noiseplanet.noisecapture + +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import org.koin.compose.koinInject +import org.noiseplanet.noisecapture.ui.AppBar +import org.noiseplanet.noisecapture.ui.NavigationRoute +import org.noiseplanet.noisecapture.ui.screens.HomeScreen +import org.noiseplanet.noisecapture.ui.screens.MeasurementScreen +import org.noiseplanet.noisecapture.ui.screens.PlatformInfoScreen +import org.noiseplanet.noisecapture.ui.screens.RequestPermissionScreen + + +/** + * Root component of the app. + * Currently handles the navigation stack, and navigation bar management. + */ +@Composable +fun NoiseCaptureApp( + navController: NavHostController = rememberNavController(), +) { + // Get current navigation back stack entry + val backStackEntry by navController.currentBackStackEntryAsState() + // Get the name of the current screen + val currentScreen = NavigationRoute.valueOf( + backStackEntry?.destination?.route ?: NavigationRoute.Home.name + ) + + Scaffold( + topBar = { + AppBar( + currentScreen = currentScreen, + canNavigateBack = navController.previousBackStackEntry != null, + navigateUp = { navController.navigateUp() } + ) + } + ) { innerPadding -> + // TODO: Configure NavHost in a separate file + // TODO: Use ease out curve for slide transitions + // TODO: Handle swipe back gestures on iOS -> encapsulate UINavigationController? + // TODO: Handle predictive back gestures on Android + NavHost( + navController = navController, + startDestination = NavigationRoute.Home.name, + enterTransition = { + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(300)) + }, + exitTransition = { + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(300)) + }, + popEnterTransition = { + slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(300)) + }, + popExitTransition = { + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(300)) + }, + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + composable(route = NavigationRoute.Home.name) { + HomeScreen( + onClick = { + // TODO: Silently check for permissions and bypass this step if they are already all granted + navController.navigate(NavigationRoute.RequestPermission.name) + }, + ) + } + composable(route = NavigationRoute.PlatformInfo.name) { + PlatformInfoScreen( + modifier = Modifier.fillMaxHeight() + ) + } + composable(route = NavigationRoute.RequestPermission.name) { + RequestPermissionScreen( + onClickNextButton = { + navController.navigate(NavigationRoute.Measurement.name) + } + ) + } + composable(route = NavigationRoute.Measurement.name) { + // TODO: Decide of a standard for screens architecture: + // - class or compose function as root? + // - Inject dependencies in constructor or via Koin factories? + // - What should be the package structure? + MeasurementScreen(measurementService = koinInject()) + .Content() + } + } + } +} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/AcousticIndicatorsProcessing.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AcousticIndicatorsProcessing.kt similarity index 70% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/AcousticIndicatorsProcessing.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AcousticIndicatorsProcessing.kt index bbfd756..d881394 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/AcousticIndicatorsProcessing.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AcousticIndicatorsProcessing.kt @@ -1,9 +1,8 @@ -package org.noiseplanet.noisecapture.shared +package org.noiseplanet.noisecapture.audio -import org.noiseplanet.noisecapture.AudioSamples -import org.noiseplanet.noisecapture.shared.signal.SpectrumChannel -import org.noiseplanet.noisecapture.shared.signal.get44100HZ -import org.noiseplanet.noisecapture.shared.signal.get48000HZ +import org.noiseplanet.noisecapture.audio.signal.SpectrumChannel +import org.noiseplanet.noisecapture.audio.signal.get44100HZ +import org.noiseplanet.noisecapture.audio.signal.get48000HZ import kotlin.math.log10 import kotlin.math.min import kotlin.math.pow @@ -20,7 +19,11 @@ const val WINDOW_TIME = 0.125 // microphone used to record the voice recognition audio source. const val ANDROID_GAIN = -(-22.35 - 90) +/** + * TODO: Document this class! + */ class AcousticIndicatorsProcessing(val sampleRate: Int, val dbGain: Double = ANDROID_GAIN) { + private var windowLength = (sampleRate * WINDOW_TIME).toInt() private var windowData = FloatArray(windowLength) private var windowDataCursor = 0 @@ -36,7 +39,6 @@ class AcousticIndicatorsProcessing(val sampleRate: Int, val dbGain: Double = AND this@AcousticIndicatorsProcessing.nominalFrequencies = this.getNominalFrequency() } - suspend fun processSamples(samples: AudioSamples): List { val acousticIndicatorsDataList = ArrayList() var samplesProcessed = 0 @@ -83,9 +85,39 @@ class AcousticIndicatorsProcessing(val sampleRate: Int, val dbGain: Double = AND } } + data class AcousticIndicatorsData( - val epoch: Long, val leq: Double, val laeq: Double, - val rms: Double, val thirdOctave: DoubleArray, - val nominalFrequencies: List -) + val epoch: Long, + val leq: Double, + val laeq: Double, + val rms: Double, + val thirdOctave: DoubleArray, + val nominalFrequencies: List, +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as AcousticIndicatorsData + if (epoch != other.epoch) return false + if (leq != other.leq) return false + if (laeq != other.laeq) return false + if (rms != other.rms) return false + if (!thirdOctave.contentEquals(other.thirdOctave)) return false + if (nominalFrequencies != other.nominalFrequencies) return false + + return true + } + + override fun hashCode(): Int { + var result = epoch.hashCode() + result = 31 * result + leq.hashCode() + result = 31 * result + laeq.hashCode() + result = 31 * result + rms.hashCode() + result = 31 * result + thirdOctave.contentHashCode() + result = 31 * result + nominalFrequencies.hashCode() + return result + } +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AudioSamples.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AudioSamples.kt new file mode 100644 index 0000000..077495a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AudioSamples.kt @@ -0,0 +1,32 @@ +package org.noiseplanet.noisecapture.audio + +data class AudioSamples( + val epoch: Long, + val samples: FloatArray, + val sampleRate: Int, + val errorCode: ErrorCode? = null, +) { + + enum class ErrorCode { + ABORTED, + DEVICE_ERROR + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as AudioSamples + + if (epoch != other.epoch) return false + if (!samples.contentEquals(other.samples)) return false + + return true + } + + override fun hashCode(): Int { + var result = epoch.hashCode() + result = 31 * result + samples.contentHashCode() + return result + } +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AudioSource.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AudioSource.kt new file mode 100644 index 0000000..f7bb829 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/AudioSource.kt @@ -0,0 +1,36 @@ +package org.noiseplanet.noisecapture.audio + +import kotlinx.coroutines.flow.Flow + +/** + * Common interface to access Audio samples from device microphone + * As each device + */ +interface AudioSource { + + enum class MicrophoneLocation { + LOCATION_UNKNOWN, + LOCATION_MAIN_BODY, + LOCATION_MAIN_BODY_MOVABLE, + LOCATION_PERIPHERAL + } + + /** + * TODO: Improve documentation and methods naming + * @param sampleRate Sample rate in Hz + * @param bufferSize Buffer size in bytes + * @return InitializeErrorCode instance + */ + suspend fun setup(): Flow + + /** + * Release device and will require to setup again before getting new samples + * Will abort samples flow + */ + fun release() + + /** + * TODO: Document this + */ + fun getMicrophoneLocation(): MicrophoneLocation +} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/BiquadFilter.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/BiquadFilter.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/BiquadFilter.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/BiquadFilter.kt index d1369f3..ee40411 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/BiquadFilter.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/BiquadFilter.kt @@ -24,7 +24,7 @@ * 14-20 Boulevard Newton Cite Descartes, Champs sur Marne F-77447 Marne la Vallee Cedex 2 FRANCE * or write to scientific.computing@ifsttar.fr */ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.log10 diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/Bluestein.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/Bluestein.kt similarity index 99% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/Bluestein.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/Bluestein.kt index 3dd2277..7cc69c6 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/Bluestein.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/Bluestein.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.PI import kotlin.math.atan diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/BluesteinFloat.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/BluesteinFloat.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/BluesteinFloat.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/BluesteinFloat.kt index 1b95c9a..ea51181 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/BluesteinFloat.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/BluesteinFloat.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.PI import kotlin.math.atan @@ -19,7 +19,8 @@ class BluesteinFloat(private val windowLength: Int) { companion object { - val PIF = PI.toFloat() + const val PIF = PI.toFloat() + operator fun Float.plus(other: Complex) = Complex(this + other.real, other.imag) operator fun Float.minus(other: Complex) = Complex(this - other.real, -other.imag) operator fun Float.times(other: Complex) = Complex( diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/DigitalFilter.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/DigitalFilter.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/DigitalFilter.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/DigitalFilter.kt index 0b95852..4d752d2 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/DigitalFilter.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/DigitalFilter.kt @@ -1,8 +1,9 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.log10 class DigitalFilter(var numerator: DoubleArray, var denominator: DoubleArray) { + var order: Int var delay1: DoubleArray var delay2: FloatArray diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/LevelDisplayWeightedDecay.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/LevelDisplayWeightedDecay.kt similarity index 92% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/LevelDisplayWeightedDecay.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/LevelDisplayWeightedDecay.kt index bdb589f..5c86a91 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/LevelDisplayWeightedDecay.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/LevelDisplayWeightedDecay.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.log10 import kotlin.math.pow diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannel.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/SpectrumChannel.kt similarity index 99% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannel.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/SpectrumChannel.kt index 2700bfe..f1fcf5b 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannel.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/SpectrumChannel.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannelConfiguration.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/SpectrumChannelConfiguration.kt similarity index 99% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannelConfiguration.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/SpectrumChannelConfiguration.kt index b22a3ff..0253204 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannelConfiguration.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/SpectrumChannelConfiguration.kt @@ -1,6 +1,6 @@ @file:Suppress("LongMethod") -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal data class SpectrumChannelConfiguration( diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/WindowAnalysis.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/WindowAnalysis.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/WindowAnalysis.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/WindowAnalysis.kt index c219472..c150aba 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/WindowAnalysis.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/WindowAnalysis.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.PI import kotlin.math.ceil @@ -149,6 +149,10 @@ class WindowAnalysis( } } + +// TODO: Move dataclasses to separate files and add documentation + + data class Window(val epoch: Long, val samples: FloatArray) { override fun equals(other: Any?): Boolean { diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/fft.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/fft.kt similarity index 98% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/fft.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/fft.kt index 4c1154f..705d0a9 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/fft.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/fft.kt @@ -1,6 +1,6 @@ @file:Suppress("LongMethod") -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.PI import kotlin.math.ceil @@ -26,6 +26,7 @@ fun isPowerOfTwo(number: Int): Boolean { return number > 0 && (number and (number - 1)) == 0 } +// TODO: Add documentation and move to utility folder along with isPowerOfTwo fun nextPowerOfTwo(number: Int) = 2.0.pow(ceil(log2(number.toDouble()))).toInt() /** diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/fftFloat.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/fftFloat.kt similarity index 99% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/fftFloat.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/fftFloat.kt index 9c7d247..d4ccda0 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/signal/fftFloat.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/audio/signal/fftFloat.kt @@ -1,6 +1,6 @@ @file:Suppress("LongMethod") -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.audio.signal import kotlin.math.PI import kotlin.math.cos diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/MeasurementService.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/measurements/MeasurementsService.kt similarity index 90% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/MeasurementService.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/measurements/MeasurementsService.kt index bffac7a..78286ab 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/MeasurementService.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/measurements/MeasurementsService.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture.shared +package org.noiseplanet.noisecapture.measurements import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -7,9 +7,11 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.launch -import org.noiseplanet.noisecapture.AudioSource -import org.noiseplanet.noisecapture.shared.signal.SpectrumData -import org.noiseplanet.noisecapture.shared.signal.WindowAnalysis +import org.noiseplanet.noisecapture.audio.AcousticIndicatorsData +import org.noiseplanet.noisecapture.audio.AcousticIndicatorsProcessing +import org.noiseplanet.noisecapture.audio.AudioSource +import org.noiseplanet.noisecapture.audio.signal.SpectrumData +import org.noiseplanet.noisecapture.audio.signal.WindowAnalysis import kotlin.properties.Delegates import kotlin.reflect.KProperty @@ -101,8 +103,8 @@ class MeasurementService(private val audioSource: AudioSource) { private fun canReleaseAudio(): Boolean { return !storageActivated && - onAcousticIndicatorsData == null && - onAcousticIndicatorsData == null + onAcousticIndicatorsData == null && + onAcousticIndicatorsData == null } private fun resetSpectrumDataObserver() { diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/model/Permission.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/Permission.kt similarity index 92% rename from permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/model/Permission.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/Permission.kt index c90718c..9accd51 100644 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/model/Permission.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/Permission.kt @@ -1,10 +1,11 @@ -package com.adrianwitaszak.kmmpermissions.permissions.model +package org.noiseplanet.noisecapture.permission /** * This enum represents the permissions used in the application. * It provides constant values for various permissions related to system services and features. */ enum class Permission { + /** * Indicates that the system setting bluetooth service is on. */ diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.kt new file mode 100644 index 0000000..69dc324 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.kt @@ -0,0 +1,24 @@ +package org.noiseplanet.noisecapture.permission + +import org.koin.core.module.Module +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.noiseplanet.noisecapture.permission.delegate.NotImplementedPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.PermissionDelegate + +/** + * Should be implemented in each platform to inject custom permission delegate implementations + */ +internal expect fun platformPermissionModule(): Module + + +internal val defaultPermissionModule = module { + + for (permission in Permission.entries) { + // Register a default delegate implementation for each permission that will be overridden + // in each platform module depending on the supported permissions + single(named(permission.name)) { + NotImplementedPermissionDelegate() + } + } +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionService.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionService.kt new file mode 100644 index 0000000..28acafa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionService.kt @@ -0,0 +1,90 @@ +package org.noiseplanet.noisecapture.permission + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.core.qualifier.named +import org.noiseplanet.noisecapture.permission.delegate.PermissionDelegate + +/** + * Manage the app permission states. Check current states and request permissions if needed. + */ +interface PermissionService : KoinComponent { + + /** + * Returns a flow instance to subscribe to the given permission state updates + * + * @param permission Target permission + * @return Flow of permission states + */ + fun getPermissionStateFlow(permission: Permission): Flow + + /** + * Checks the current state of the given permission, once + * + * @param permission Target permission + * @return Current permission state + */ + suspend fun checkPermission(permission: Permission): PermissionState + + /** + * Opens the settings page corresponding to the given permission. + * Should be used when permission has been previously denied since we can't trigger + * the permission popup again. + * + * @param permission Target permission + */ + fun openSettingsForPermission(permission: Permission) + + /** + * Triggers requesting the given permission to the user + * + * @param permission Target permission + */ + suspend fun requestPermission(permission: Permission) +} + + +/** + * Default Permission service implementation + */ +internal class DefaultPermissionService : PermissionService { + + private companion object { + + // Time spent between each permission check, in milliseconds + const val PERMISSION_CHECK_FLOW_FREQUENCY = 1_000L + } + + override fun getPermissionStateFlow(permission: Permission): Flow { + return flow { + // Get delegate for this permission + val delegate: PermissionDelegate = getPermissionDelegate(permission) + // TODO: It would be nicer to provide a platform dependant listener system + // rather than using an infinite loop, but this will do the trick for now + while (true) { + val permissionState = delegate.getPermissionState() + emit(permissionState) + delay(PERMISSION_CHECK_FLOW_FREQUENCY) + } + } + } + + override suspend fun checkPermission(permission: Permission): PermissionState { + return getPermissionDelegate(permission).getPermissionState() + } + + override suspend fun requestPermission(permission: Permission) { + getPermissionDelegate(permission).providePermission() + } + + override fun openSettingsForPermission(permission: Permission) { + getPermissionDelegate(permission).openSettingPage() + } + + private fun getPermissionDelegate(permission: Permission): PermissionDelegate { + return get(named(permission.name)) + } +} diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/model/PermissionState.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionState.kt similarity index 69% rename from permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/model/PermissionState.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionState.kt index 9fe0e8a..b8111f8 100644 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/model/PermissionState.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionState.kt @@ -1,10 +1,11 @@ -package com.adrianwitaszak.kmmpermissions.permissions.model +package org.noiseplanet.noisecapture.permission /** * Represents the state of a permission */ enum class PermissionState { + /** * Indicates that the permission has not been requested yet */ @@ -19,18 +20,12 @@ enum class PermissionState { * No permission delegate is available for this permission * It has not been implemented or it is no required on this platform */ - NO_PERMISSION_DELEGATE, + NOT_IMPLEMENTED, /** * Indicates that the permission has been requested but the user denied the permission */ DENIED; - /** - * Extension function to check if the permission is not granted - */ - fun notGranted(): Boolean { - return this != GRANTED - } + companion object } - diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/PermissionDelegate.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/PermissionDelegate.kt new file mode 100644 index 0000000..66649f9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/PermissionDelegate.kt @@ -0,0 +1,32 @@ +package org.noiseplanet.noisecapture.permission.delegate + +import org.noiseplanet.noisecapture.permission.PermissionState + + +/** + * Describes the expected behaviour of platform specific delegates. + * Each platform should provide a delegate implementation for each permission used in the app. + */ +internal interface PermissionDelegate { + + suspend fun getPermissionState(): PermissionState + suspend fun providePermission() + fun openSettingPage() +} + + +/** + * A default implementation for permissions that are not implemented on the current platform. + */ +internal class NotImplementedPermissionDelegate : PermissionDelegate { + + override suspend fun getPermissionState(): PermissionState { + return PermissionState.NOT_IMPLEMENTED + } + + override suspend fun providePermission() { + } + + override fun openSettingPage() { + } +} diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Exception.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/util/Exception.kt similarity index 82% rename from permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Exception.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/util/Exception.kt index 8541c2c..05267d9 100644 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Exception.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/permission/util/Exception.kt @@ -1,4 +1,4 @@ -package com.adrianwitaszak.kmmpermissions.permissions.util +package org.noiseplanet.noisecapture.permission.util internal class CannotOpenSettingsException(permissionName: String) : Exception("Cannot open settings for permission $permissionName.") diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/AppBar.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/AppBar.kt new file mode 100644 index 0000000..27dfac7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/AppBar.kt @@ -0,0 +1,41 @@ +package org.noiseplanet.noisecapture.ui + +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import noisecapture.composeapp.generated.resources.Res +import noisecapture.composeapp.generated.resources.back_button +import org.jetbrains.compose.resources.stringResource + +@Composable +fun AppBar( + currentScreen: NavigationRoute, + canNavigateBack: Boolean, + navigateUp: () -> Unit, + modifier: Modifier = Modifier, +) { + TopAppBar( + title = { Text(stringResource(currentScreen.title)) }, + colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ), + modifier = modifier, + navigationIcon = { + if (canNavigateBack) { + IconButton(onClick = navigateUp) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(Res.string.back_button) + ) + } + } + } + ) +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/NavigationRoute.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/NavigationRoute.kt new file mode 100644 index 0000000..7fd7754 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/NavigationRoute.kt @@ -0,0 +1,15 @@ +package org.noiseplanet.noisecapture.ui + +import noisecapture.composeapp.generated.resources.Res +import noisecapture.composeapp.generated.resources.app_name +import noisecapture.composeapp.generated.resources.measurement_title +import noisecapture.composeapp.generated.resources.platform_info_title +import noisecapture.composeapp.generated.resources.request_permission_title +import org.jetbrains.compose.resources.StringResource + +enum class NavigationRoute(val title: StringResource) { + Home(title = Res.string.app_name), + PlatformInfo(title = Res.string.platform_info_title), + RequestPermission(title = Res.string.request_permission_title), + Measurement(title = Res.string.measurement_title) +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/Greeting.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/Greeting.kt new file mode 100644 index 0000000..aaefce3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/Greeting.kt @@ -0,0 +1,12 @@ +package org.noiseplanet.noisecapture.ui.components + +import getPlatform + +class Greeting { + + private val platform = getPlatform() + + fun greet(): String { + return "Hello, ${platform.name}!" + } +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/MenuItem.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/MenuItem.kt new file mode 100644 index 0000000..b0ce577 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/MenuItem.kt @@ -0,0 +1,9 @@ +package org.noiseplanet.noisecapture.ui.components + +import androidx.compose.ui.graphics.vector.ImageVector + +data class MenuItem( + val label: String, + val imageVector: ImageVector, + val onClick: () -> Unit, +) diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/measurement/LegendElement.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/measurement/LegendElement.kt new file mode 100644 index 0000000..b0238e5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/measurement/LegendElement.kt @@ -0,0 +1,11 @@ +package org.noiseplanet.noisecapture.ui.components.measurement + +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextMeasurer + +data class LegendElement( + val text : TextLayoutResult, + val xPos : Float, + val textPos : Float, + val depth : Int +) diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/SpectrogramBitmap.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/measurement/SpectrogramBitmap.kt similarity index 65% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/SpectrogramBitmap.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/measurement/SpectrogramBitmap.kt index edfd0ca..8457ac4 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/SpectrogramBitmap.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/components/measurement/SpectrogramBitmap.kt @@ -1,163 +1,62 @@ -package org.noiseplanet.noisecapture.shared.ui +package org.noiseplanet.noisecapture.ui.components.measurement import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.IntSize -import org.noiseplanet.noisecapture.shared.FFT_SIZE -import org.noiseplanet.noisecapture.shared.signal.SpectrumData +import org.noiseplanet.noisecapture.audio.signal.SpectrumData +import org.noiseplanet.noisecapture.measurements.FFT_SIZE import kotlin.math.floor import kotlin.math.log10 import kotlin.math.max import kotlin.math.min import kotlin.math.pow - /** * Convert FFT result into spectrogram bitmap bytearray + * TODO: Cleanup and document */ class SpectrogramBitmap { companion object { val bmpHeader = intArrayOf( // All values are little-endian - 0x42, - 0x4D, // Signature 'BM' - 0xaa, - 0x00, - 0x00, - 0x00, // Size: 170 bytes - 0x00, - 0x00, // Unused - 0x00, - 0x00, // Unused - 0x8a, - 0x00, - 0x00, - 0x00, // Offset to image data - 0x7c, - 0x00, - 0x00, - 0x00, // DIB header size (124 bytes) - 0x04, - 0x00, - 0x00, - 0x00, // Width (4px) - 0x02, - 0x00, - 0x00, - 0x00, // Height (2px) - 0x01, - 0x00, // Planes (1) - 0x20, - 0x00, // Bits per pixel (32) - 0x03, - 0x00, - 0x00, - 0x00, // Format (bitfield = use bitfields | no compression) - 0x20, - 0x00, - 0x00, - 0x00, // Image raw size (32 bytes) - 0x13, - 0x0B, - 0x00, - 0x00, // Horizontal print resolution (2835 = 72dpi * 39.3701) - 0x13, - 0x0B, - 0x00, - 0x00, // Vertical print resolution (2835 = 72dpi * 39.3701) - 0x00, - 0x00, - 0x00, - 0x00, // Colors in palette (none) - 0x00, - 0x00, - 0x00, - 0x00, // Important colors (0 = all) - 0x00, - 0x00, - 0xFF, - 0x00, // R bitmask (00FF0000) - 0x00, - 0xFF, - 0x00, - 0x00, // G bitmask (0000FF00) - 0xFF, - 0x00, - 0x00, - 0x00, // B bitmask (000000FF) - 0x00, - 0x00, - 0x00, - 0xFF, // A bitmask (FF000000) - 0x42, - 0x47, - 0x52, - 0x73, // sRGB color space - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, // Unused R, G, B entries for color space - 0x00, - 0x00, - 0x00, - 0x00, // Unused Gamma X entry for color space - 0x00, - 0x00, - 0x00, - 0x00, // Unused Gamma Y entry for color space - 0x00, - 0x00, - 0x00, - 0x00, // Unused Gamma Z entry for color space - 0x00, - 0x00, - 0x00, - 0x00, // Unknown - 0x00, - 0x00, - 0x00, - 0x00, // Unknown - 0x00, - 0x00, - 0x00, - 0x00, // Unknown - 0x00, - 0x00, - 0x00, - 0x00 // Unknown + 0x42, 0x4D, // Signature 'BM' + 0xaa, 0x00, 0x00, 0x00, // Size: 170 bytes + 0x00, 0x00, // Unused + 0x00, 0x00, // Unused + 0x8a, 0x00, 0x00, 0x00, // Offset to image data + 0x7c, 0x00, 0x00, 0x00, // DIB header size (124 bytes) + 0x04, 0x00, 0x00, 0x00, // Width (4px) + 0x02, 0x00, 0x00, 0x00, // Height (2px) + 0x01, 0x00, // Planes (1) + 0x20, 0x00, // Bits per pixel (32) + 0x03, 0x00, 0x00, 0x00, // Format (bitfield = use bitfields | no compression) + 0x20, 0x00, 0x00, 0x00, // Image raw size (32 bytes) + 0x13, 0x0B, 0x00, 0x00, // Horizontal print resolution (2835 = 72dpi * 39.3701) + 0x13, 0x0B, 0x00, 0x00, // Vertical print resolution (2835 = 72dpi * 39.3701) + 0x00, 0x00, 0x00, 0x00, // Colors in palette (none) + 0x00, 0x00, 0x00, 0x00, // Important colors (0 = all) + 0x00, 0x00, 0xFF, 0x00, // R bitmask (00FF0000) + 0x00, 0xFF, 0x00, 0x00, // G bitmask (0000FF00) + 0xFF, 0x00, 0x00, 0x00, // B bitmask (000000FF) + 0x00, 0x00, 0x00, 0xFF, // A bitmask (FF000000) + 0x42, 0x47, 0x52, 0x73, // sRGB color space + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // Unused R, G, B entries for color space + 0x00, 0x00, 0x00, 0x00, // Unused Gamma X entry for color space + 0x00, 0x00, 0x00, 0x00, // Unused Gamma Y entry for color space + 0x00, 0x00, 0x00, 0x00, // Unused Gamma Z entry for color space + 0x00, 0x00, 0x00, 0x00, // Unknown + 0x00, 0x00, 0x00, 0x00, // Unknown + 0x00, 0x00, 0x00, 0x00, // Unknown + 0x00, 0x00, 0x00, 0x00 // Unknown // Image data after this ).map { it.toByte() }.toByteArray() @@ -247,8 +146,10 @@ class SpectrogramBitmap { * @si */ data class SpectrogramDataModel( - val size: IntSize, val byteArray: ByteArray, - var offset: Int = 0, val scaleMode: ScaleMode, + val size: IntSize, + val byteArray: ByteArray, + var offset: Int = 0, + val scaleMode: ScaleMode, val sampleRate: Double, ) { diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/HomeScreen.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/HomeScreen.kt new file mode 100644 index 0000000..240d38c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/HomeScreen.kt @@ -0,0 +1,94 @@ +package org.noiseplanet.noisecapture.ui.screens + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Help +import androidx.compose.material.icons.filled.CenterFocusWeak +import androidx.compose.material.icons.filled.History +import androidx.compose.material.icons.filled.HistoryEdu +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Map +import androidx.compose.material.icons.filled.Mic +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Timeline +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import noisecapture.composeapp.generated.resources.Res +import noisecapture.composeapp.generated.resources.menu_about +import noisecapture.composeapp.generated.resources.menu_calibration +import noisecapture.composeapp.generated.resources.menu_feedback +import noisecapture.composeapp.generated.resources.menu_help +import noisecapture.composeapp.generated.resources.menu_history +import noisecapture.composeapp.generated.resources.menu_map +import noisecapture.composeapp.generated.resources.menu_new_measurement +import noisecapture.composeapp.generated.resources.menu_settings +import noisecapture.composeapp.generated.resources.menu_statistics +import org.jetbrains.compose.resources.stringResource +import org.noiseplanet.noisecapture.ui.components.MenuItem + +/** + * Home screen layout. + * + * TODO: Improve UI once more clearly defined + * TODO: Figure out a clean design pattern to handle click events (delegate?, pass down navigation controller?) + */ +@Composable +fun HomeScreen( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val menuItems = arrayOf( + MenuItem( + stringResource(Res.string.menu_new_measurement), + Icons.Filled.Mic, + onClick = onClick + ), + MenuItem(stringResource(Res.string.menu_history), Icons.Filled.History) {}, + MenuItem(stringResource(Res.string.menu_feedback), Icons.Filled.HistoryEdu) {}, + MenuItem(stringResource(Res.string.menu_statistics), Icons.Filled.Timeline) {}, + MenuItem(stringResource(Res.string.menu_map), Icons.Filled.Map) {}, + MenuItem(stringResource(Res.string.menu_help), Icons.AutoMirrored.Filled.Help) {}, + MenuItem(stringResource(Res.string.menu_about), Icons.Filled.Info) {}, + MenuItem(stringResource(Res.string.menu_calibration), Icons.Filled.CenterFocusWeak) {}, + MenuItem(stringResource(Res.string.menu_settings), Icons.Filled.Settings) {}, + ) + + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 96.dp), + contentPadding = PaddingValues( + start = 24.dp, + top = 24.dp, + end = 24.dp, + bottom = 24.dp + ), + content = { + items(menuItems.size) { index -> + Button( + onClick = menuItems[index].onClick, + modifier = Modifier.aspectRatio(1f).padding(12.dp), + ) { + Icon( + imageVector = menuItems[index].imageVector, + menuItems[index].label, + modifier.fillMaxSize(), + ) + } + } + } + ) + } +} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/MeasurementScreen.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/MeasurementScreen.kt similarity index 60% rename from shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/MeasurementScreen.kt rename to composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/MeasurementScreen.kt index f896135..b49380b 100644 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/MeasurementScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/MeasurementScreen.kt @@ -1,7 +1,11 @@ -// TODO: Remove detekt suppression after Appyx refactor -@file:Suppress("detekt:all") +// +// +// TODO: Split this file!!!! +// +// +@file:Suppress("TooManyFunctions") -package org.noiseplanet.noisecapture.shared.child +package org.noiseplanet.noisecapture.ui.screens import androidx.compose.foundation.Canvas import androidx.compose.foundation.ExperimentalFoundationApi @@ -17,12 +21,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Colors -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.material.Tab -import androidx.compose.material.TabRow -import androidx.compose.material.Text +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -40,9 +44,9 @@ import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextMeasurer import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.drawText @@ -55,26 +59,23 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver -import com.bumble.appyx.navigation.lifecycle.Lifecycle -import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.LeafNode +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.eventFlow +import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import org.koin.core.logger.Logger -import org.noiseplanet.noisecapture.shared.ANDROID_GAIN -import org.noiseplanet.noisecapture.shared.FFT_HOP -import org.noiseplanet.noisecapture.shared.MeasurementService -import org.noiseplanet.noisecapture.shared.ScreenData -import org.noiseplanet.noisecapture.shared.WINDOW_TIME -import org.noiseplanet.noisecapture.shared.signal.FAST_DECAY_RATE -import org.noiseplanet.noisecapture.shared.signal.LevelDisplayWeightedDecay -import org.noiseplanet.noisecapture.shared.signal.SpectrumData -import org.noiseplanet.noisecapture.shared.ui.SpectrogramBitmap -import org.noiseplanet.noisecapture.shared.ui.SpectrogramBitmap.Companion.toComposeColor -import org.noiseplanet.noisecapture.shared.ui.asEventFlow -import org.noiseplanet.noisecapture.toImageBitmap +import org.noiseplanet.noisecapture.audio.ANDROID_GAIN +import org.noiseplanet.noisecapture.audio.WINDOW_TIME +import org.noiseplanet.noisecapture.audio.signal.FAST_DECAY_RATE +import org.noiseplanet.noisecapture.audio.signal.LevelDisplayWeightedDecay +import org.noiseplanet.noisecapture.audio.signal.SpectrumData +import org.noiseplanet.noisecapture.measurements.FFT_HOP +import org.noiseplanet.noisecapture.measurements.MeasurementService +import org.noiseplanet.noisecapture.ui.components.measurement.LegendElement +import org.noiseplanet.noisecapture.ui.components.measurement.SpectrogramBitmap +import org.noiseplanet.noisecapture.ui.components.measurement.SpectrogramBitmap.Companion.toComposeColor +import org.noiseplanet.noisecapture.util.toImageBitmap import kotlin.math.abs import kotlin.math.log10 import kotlin.math.max @@ -92,15 +93,12 @@ const val MAX_SHOWN_DBA_VALUE_SPECTRUM = 100.0 val NOISE_LEVEL_FONT_SIZE = TextUnit(50F, TextUnitType.Sp) val SPECTRUM_PLOT_SQUARE_WIDTH = 10.dp val SPECTRUM_PLOT_SQUARE_OFFSET = 1.dp -const val MIN_FREQUENCY_SPECTRUM = 100.0 -const val MAX_FREQUENCY_SPECTRUM = 16000.0 +// TODO: Refactor this screen +@Suppress("LargeClass") class MeasurementScreen( - nodeContext: NodeContext, - val backStack: BackStack, private val measurementService: MeasurementService, - private val logger: Logger, -) : LeafNode(nodeContext), DefaultPlatformLifecycleObserver { +) { private var rangedB = 40.0 private var mindB = 0.0 @@ -108,26 +106,36 @@ class MeasurementScreen( companion object { - val noiseColorRampSpl : List> = listOf( + val noiseColorRampSpl: List> = listOf( Pair(75F, "#FF0000".toComposeColor()), // >= 75 dB Pair(65F, "#FF8000".toComposeColor()), // >= 65 dB Pair(55F, "#FFFF00".toComposeColor()), // >= 55 dB Pair(45F, "#99FF00".toComposeColor()), // >= 45 dB - Pair(Float.NEGATIVE_INFINITY, "#00FF00".toComposeColor())) // < 45 dB + Pair(Float.NEGATIVE_INFINITY, "#00FF00".toComposeColor()) + ) // < 45 dB - fun timeAxisFormater(timeValue: Double) : String { + fun timeAxisFormater(timeValue: Double): String { return "+${round(timeValue).toInt()}s" } - fun noiseLevelAxisFormater(timeValue: Double) : String { + fun noiseLevelAxisFormater(timeValue: Double): String { return "${round(timeValue).toInt()} dB" } + val tickStroke = 2.dp val tickLength = 4.dp - - fun makeXLegend(textMeasurer: TextMeasurer, xValue : Double, legendWidth : Float, - xPerPixel : Double, depth: Int, formater: (x:Double)->String, ascending: Boolean) : LegendElement { + // TODO: Cleanup legend generation functions + @Suppress("LongParameterList") + fun makeXLegend( + textMeasurer: TextMeasurer, + xValue: Double, + legendWidth: Float, + xPerPixel: Double, + depth: Int, + formater: (x: Double) -> String, + ascending: Boolean, + ): LegendElement { val xPos = when { ascending -> (xValue / xPerPixel).toFloat() @@ -139,11 +147,15 @@ class MeasurementScreen( } } val textLayout = textMeasurer.measure(legendText) - val textPos = min(legendWidth-textLayout.size.width, - max(0F, xPos - textLayout.size.width / 2)) + val textPos = min( + legendWidth - textLayout.size.width, + max(0F, xPos - textLayout.size.width / 2) + ) return LegendElement(textLayout, xPos, textPos, depth) } + // TODO: Cleanup legend generation functions + @Suppress("LongParameterList") fun recursiveLegendBuild( textMeasurer: TextMeasurer, timeValue: Double, @@ -155,45 +167,105 @@ class MeasurementScreen( xRightValue: Double, feedElements: ArrayList, depth: Int, - formater: (x: Double) -> String + formater: (x: Double) -> String, ) { val legendElement = - makeXLegend(textMeasurer, timeValue, legendWidth, timePerPixel, depth, formater, xLeftValue < xRightValue) + makeXLegend( + textMeasurer, + timeValue, + legendWidth, + timePerPixel, + depth, + formater, + xLeftValue < xRightValue + ) + // Add sub axis element if the text does not overlap with neighboring texts if (legendElement.textPos > minPixel && legendElement.xPos + legendElement.text.size.width / 2 < maxPixel) { feedElements.add(legendElement) - val axisOrder = if(xLeftValue < xRightValue) 1 else -1 // left legend, + x seconds recursiveLegendBuild( - textMeasurer, xLeftValue + (timeValue-xLeftValue) / 2, - legendWidth, timePerPixel, minPixel, legendElement.textPos, xLeftValue, timeValue, - feedElements, depth + 1, formater + textMeasurer, + xLeftValue + (timeValue - xLeftValue) / 2, + legendWidth, + timePerPixel, + minPixel, + legendElement.textPos, + xLeftValue, + timeValue, + feedElements, + depth + 1, + formater ) // right legend, - x seconds recursiveLegendBuild( - textMeasurer, timeValue + (xRightValue - timeValue) / 2, - legendWidth, timePerPixel, legendElement.textPos + legendElement.text.size.width, - maxPixel, timeValue, xRightValue, feedElements, depth + 1, formater + textMeasurer, + timeValue + (xRightValue - timeValue) / 2, + legendWidth, + timePerPixel, + legendElement.textPos + legendElement.text.size.width, + maxPixel, + timeValue, + xRightValue, + feedElements, + depth + 1, + formater ) } } - fun makeXLabels(textMeasurer: TextMeasurer, leftValue : Double, rightValue : Double, xLegendWidth: Float, formater: (x:Double)->String) : ArrayList { + fun makeXLabels( + textMeasurer: TextMeasurer, + leftValue: Double, + rightValue: Double, + xLegendWidth: Float, + formater: (x: Double) -> String, + ): ArrayList { val xPerPixel = abs(leftValue - rightValue) / xLegendWidth val legendElements = ArrayList() - val leftLegend = makeXLegend(textMeasurer, leftValue, xLegendWidth, xPerPixel, -1, formater, leftValue < rightValue) - val rightLegend = makeXLegend(textMeasurer, rightValue, xLegendWidth, xPerPixel, -1, formater, leftValue < rightValue) + val leftLegend = + makeXLegend( + textMeasurer, + leftValue, + xLegendWidth, + xPerPixel, + -1, + formater, + leftValue < rightValue + ) + val rightLegend = + makeXLegend( + textMeasurer, + rightValue, + xLegendWidth, + xPerPixel, + -1, + formater, + leftValue < rightValue + ) legendElements.add(leftLegend) legendElements.add(rightLegend) - recursiveLegendBuild(textMeasurer, abs(leftValue - rightValue) / 2, xLegendWidth, xPerPixel, + // Add axis texts between left and rightmost axis texts (until it overlaps) + recursiveLegendBuild( + textMeasurer, + abs(leftValue - rightValue) / 2, + xLegendWidth, + xPerPixel, leftLegend.text.size.width.toFloat(), - rightLegend.xPos-rightLegend.text.size.width, leftValue, rightValue, legendElements, 0, formater) + rightLegend.xPos - rightLegend.text.size.width, + leftValue, + rightValue, + legendElements, + 0, + formater + ) // find depth index with maximum number of elements (to generate same intervals on legend) - val legendDepthCount = IntArray(legendElements.maxOf { it.depth }+1) { 0 } + val legendDepthCount = IntArray(legendElements.maxOf { it.depth } + 1) { 0 } legendElements.forEach { - if(it.depth >= 0) { + if (it.depth >= 0) { legendDepthCount[it.depth] += 1 } } + // remove sub-axis texts with isolated depth (should produce same intervals between axis text) legendElements.removeAll { it.depth > 0 && legendDepthCount[it.depth] != (2.0.pow(it.depth)).toInt() } @@ -210,24 +282,42 @@ class MeasurementScreen( ), ArrayList(), Size.Zero ) - var preparedSpectrogramOverlayBitmap = PlotBitmapOverlay( ImageBitmap(1, 1), Size(0F, 0F), Size(0F, 0F), Size(0F, 0F),0 ) - var preparedSpectrumOverlayBitmap = PlotBitmapOverlay( ImageBitmap(1, 1), Size(0F, 0F), Size(0F, 0F), Size(0F, 0F), 0) + var preparedSpectrogramOverlayBitmap = + PlotBitmapOverlay(ImageBitmap(1, 1), Size(0F, 0F), Size(0F, 0F), Size(0F, 0F), 0) + var preparedSpectrumOverlayBitmap = + PlotBitmapOverlay(ImageBitmap(1, 1), Size(0F, 0F), Size(0F, 0F), Size(0F, 0F), 0) @Composable - fun spectrogram(spectrumCanvasState : SpectrogramViewModel, bitmapOffset : Int) { - Canvas(modifier = Modifier.fillMaxSize() ) { - val canvasSize = IntSize(SPECTROGRAM_STRIP_WIDTH, (size.height - preparedSpectrogramOverlayBitmap.horizontalLegendSize.height).toInt()) - drawRect(color = SpectrogramBitmap.colorRamp[0], size=Size(size.width - preparedSpectrogramOverlayBitmap.verticalLegendSize.width, canvasSize.height.toFloat())) - spectrumCanvasState.spectrogramCanvasSize = Size(size.width - preparedSpectrogramOverlayBitmap.verticalLegendSize.width, size.height - - preparedSpectrogramOverlayBitmap.horizontalLegendSize.height) - if(spectrumCanvasState.currentStripData.size.height != canvasSize.height) { + fun spectrogram(spectrumCanvasState: SpectrogramViewModel, bitmapOffset: Int) { + Canvas(modifier = Modifier.fillMaxSize()) { + val canvasSize = + IntSize( + SPECTROGRAM_STRIP_WIDTH, + (size.height - preparedSpectrogramOverlayBitmap.horizontalLegendSize.height).toInt() + ) + drawRect( + color = SpectrogramBitmap.colorRamp[0], + size = Size( + size.width - preparedSpectrogramOverlayBitmap.verticalLegendSize.width, + canvasSize.height.toFloat() + ) + ) + spectrumCanvasState.spectrogramCanvasSize = Size( + size.width - preparedSpectrogramOverlayBitmap.verticalLegendSize.width, size.height + - preparedSpectrogramOverlayBitmap.horizontalLegendSize.height + ) + if (spectrumCanvasState.currentStripData.size.height != canvasSize.height) { // reset buffer on resize or first draw - println("Clear ${spectrumCanvasState.cachedStrips.size} strips ${spectrumCanvasState.currentStripData.size.height} != ${canvasSize.height}") + println( + "Clear ${spectrumCanvasState.cachedStrips.size} strips " + + "${spectrumCanvasState.currentStripData.size.height} != ${canvasSize.height}" + ) spectrumCanvasState.currentStripData = SpectrogramBitmap.createSpectrogram( - canvasSize, scaleMode, spectrumCanvasState.currentStripData.sampleRate) + canvasSize, scaleMode, spectrumCanvasState.currentStripData.sampleRate + ) spectrumCanvasState.cachedStrips.clear() } else { - if(spectrumCanvasState.currentStripData.sampleRate > 1) { + if (spectrumCanvasState.currentStripData.sampleRate > 1) { drawImage( spectrumCanvasState.currentStripData.byteArray.toImageBitmap(), topLeft = Offset( @@ -237,9 +327,9 @@ class MeasurementScreen( ) spectrumCanvasState.cachedStrips.reversed() .forEachIndexed { index, imageBitmap -> - val bitmapX = - size.width - preparedSpectrogramOverlayBitmap.verticalLegendSize.width - ((index + 1) * SPECTROGRAM_STRIP_WIDTH - + bitmapOffset).toFloat() + val bitmapX = size.width - + preparedSpectrogramOverlayBitmap.verticalLegendSize.width - + ((index + 1) * SPECTROGRAM_STRIP_WIDTH + bitmapOffset).toFloat() drawImage( imageBitmap, topLeft = Offset(bitmapX, 0F) @@ -250,28 +340,33 @@ class MeasurementScreen( } } - - - fun buildSpectrumAxisBitmap(size: Size, density: Density, settings: SpectrumSettings, - values : SpectrumPlotData, textMeasurer: TextMeasurer, - colors: Colors): PlotBitmapOverlay { + /** + * Generate bitmap of Axis (as it does not change between redraw of values) + */ + @Suppress("LongParameterList", "LongMethod") + fun buildSpectrumAxisBitmap( + size: Size, density: Density, + settings: SpectrumSettings, + textMeasurer: TextMeasurer, + colors: ColorScheme, + ): PlotBitmapOverlay { val drawScope = CanvasDrawScope() val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt()) val canvas = androidx.compose.ui.graphics.Canvas(bitmap) - val legendTexts = List(values.nominalFrequencies.size) { frequencyIndex -> - val textLayoutResult = textMeasurer.measure(buildAnnotatedString { - withStyle( - SpanStyle( - fontSize = TextUnit( - 10F, - TextUnitType.Sp - ) + val legendTexts = List(settings.nominalFrequencies.size) { frequencyIndex -> + val textLayoutResult = textMeasurer.measure(buildAnnotatedString { + withStyle( + SpanStyle( + fontSize = TextUnit( + 10F, + TextUnitType.Sp ) ) - { append(formatFrequency(values.nominalFrequencies[frequencyIndex].toInt())) } - }) - textLayoutResult - } + ) + { append(formatFrequency(settings.nominalFrequencies[frequencyIndex].toInt())) } + }) + textLayoutResult + } var horizontalLegendSize = Size(0F, 0F) var verticalLegendSize = Size(0F, 0F) @@ -281,20 +376,27 @@ class MeasurementScreen( canvas = canvas, size = size, ) { - val maxYAxisWidth = (legendTexts.maxOfOrNull { it.size.width }) ?:0 + val maxYAxisWidth = (legendTexts.maxOfOrNull { it.size.width }) ?: 0 verticalLegendSize = Size(maxYAxisWidth.toFloat(), size.height) - val barMaxWidth : Float = size.width - maxYAxisWidth + val barMaxWidth: Float = size.width - maxYAxisWidth val legendElements = makeXLabels( - textMeasurer, settings.minimumX, settings.maximumX, barMaxWidth, + textMeasurer, settings.minimumX, settings.maximumX, barMaxWidth, ::noiseLevelAxisFormater ) - val maxXAxisHeight = (legendElements.maxOfOrNull { it.text.size.height }) ?:0 + val maxXAxisHeight = (legendElements.maxOfOrNull { it.text.size.height }) ?: 0 horizontalLegendSize = Size(size.width, maxXAxisHeight.toFloat()) val chartHeight = (size.height - maxXAxisHeight - tickLength.toPx()) - legendElements.forEach {legendElement -> - val tickPos = maxYAxisWidth + max(tickStroke.toPx() / 2F, min(barMaxWidth-tickStroke.toPx(), legendElement.xPos - tickStroke.toPx() / 2F)) + legendElements.forEach { legendElement -> + val tickPos = + maxYAxisWidth + max( + tickStroke.toPx() / 2F, + min( + barMaxWidth - tickStroke.toPx(), + legendElement.xPos - tickStroke.toPx() / 2F + ) + ) drawLine( - color = colors.onPrimary, start = Offset( + color = colors.onSurfaceVariant, start = Offset( tickPos, chartHeight ), @@ -304,19 +406,46 @@ class MeasurementScreen( ), strokeWidth = tickStroke.toPx() ) - drawText(legendElement.text, topLeft = Offset(maxYAxisWidth + legendElement.textPos, chartHeight + tickLength.toPx())) + drawText( + legendElement.text, + topLeft = Offset( + maxYAxisWidth + legendElement.textPos, + chartHeight + tickLength.toPx() + ) + ) } - val barHeight = chartHeight / values.spl.size - SPECTRUM_PLOT_SQUARE_OFFSET.toPx() - values.spl.forEachIndexed { index, spl -> - val barYOffset = (barHeight + SPECTRUM_PLOT_SQUARE_OFFSET.toPx())*(values.spl.size - 1 - index) - drawText(textMeasurer, legendTexts[index].layoutInput.text, topLeft = Offset(0F, barYOffset + barHeight / 2 - legendTexts[index].size.height / 2F)) + val barHeight = chartHeight / settings.nominalFrequencies.size - SPECTRUM_PLOT_SQUARE_OFFSET.toPx() + legendTexts.forEachIndexed { index, legendText -> + val barYOffset = + (barHeight + SPECTRUM_PLOT_SQUARE_OFFSET.toPx()) * (settings.nominalFrequencies.size - 1 - index) + drawText( + textMeasurer, + legendText.layoutInput.text, + topLeft = Offset( + 0F, + barYOffset + barHeight / 2 - legendText.size.height / 2F + ) + ) } } - return PlotBitmapOverlay(bitmap, size, horizontalLegendSize, verticalLegendSize, settings.hashCode()) + return PlotBitmapOverlay( + bitmap, + size, + horizontalLegendSize, + verticalLegendSize, + settings.hashCode() + ) } - fun buildSpectrogramAxisBitmap(size: Size, density: Density, scaleMode: SpectrogramBitmap.Companion.ScaleMode, - sampleRate: Double, textMeasurer: TextMeasurer, colors: Colors): PlotBitmapOverlay { + @Suppress("LongParameterList", "LongMethod") + fun buildSpectrogramAxisBitmap( + size: Size, + density: Density, + scaleMode: SpectrogramBitmap.Companion.ScaleMode, + sampleRate: Double, + textMeasurer: TextMeasurer, + colors: ColorScheme, + ): PlotBitmapOverlay { val drawScope = CanvasDrawScope() val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt()) val canvas = androidx.compose.ui.graphics.Canvas(bitmap) @@ -325,7 +454,8 @@ class MeasurementScreen( SpectrogramBitmap.Companion.ScaleMode.SCALE_LOG -> SpectrogramBitmap.frequencyLegendPositionLog else -> SpectrogramBitmap.frequencyLegendPositionLinear } - frequencyLegendPosition = frequencyLegendPosition.filter { f -> f < sampleRate / 2 }.toIntArray() + frequencyLegendPosition = + frequencyLegendPosition.filter { f -> f < sampleRate / 2 }.toIntArray() val timeXLabelMeasure = textMeasurer.measure(REFERENCE_LEGEND_TEXT) val timeXLabelHeight = timeXLabelMeasure.size.height val maxYLabelWidth = @@ -341,11 +471,11 @@ class MeasurementScreen( canvas = canvas, size = size, ) { - val legendHeight = timeXLabelHeight+tickLength.toPx() - val legendWidth = maxYLabelWidth+tickLength.toPx() - bottomLegendSize = Size(size.width-legendWidth, legendHeight) + val legendHeight = timeXLabelHeight + tickLength.toPx() + val legendWidth = maxYLabelWidth + tickLength.toPx() + bottomLegendSize = Size(size.width - legendWidth, legendHeight) rightLegendSize = Size(legendWidth, size.height - legendHeight) - if(sampleRate > 1) { + if (sampleRate > 1) { // draw Y axe labels val fMax = sampleRate / 2 val fMin = frequencyLegendPosition[0].toDouble() @@ -361,30 +491,49 @@ class MeasurementScreen( SpectrogramBitmap.Companion.ScaleMode.SCALE_LOG -> { sheight - (log10(frequency / fMin) / ((log10(fMax / fMin) / sheight))).toInt() } + else -> (sheight - frequency / fMax * sheight).toInt() } drawLine( - color = colors.onPrimary, start = Offset( + color = colors.onSurfaceVariant, start = Offset( size.width - legendWidth, - tickHeightPos.toFloat() - tickStroke.toPx()/2 + tickHeightPos.toFloat() - tickStroke.toPx() / 2 ), end = Offset( size.width - legendWidth + tickLength.toPx(), - tickHeightPos.toFloat() - tickStroke.toPx()/2 + tickHeightPos.toFloat() - tickStroke.toPx() / 2 ), strokeWidth = tickStroke.toPx() ) - val textPos = min((size.height - textSize.size.height).toInt(), - max(0, tickHeightPos - textSize.size.height / 2)) - drawText(textMeasurer, text, topLeft = Offset(size.width - legendWidth + tickLength.toPx(), textPos.toFloat())) + val textPos = min( + (size.height - textSize.size.height).toInt(), + max(0, tickHeightPos - textSize.size.height / 2) + ) + drawText( + textMeasurer, + text, + topLeft = Offset( + size.width - legendWidth + tickLength.toPx(), + textPos.toFloat() + ) + ) } val xLegendWidth = (size.width - legendWidth) - val legendElements = makeXLabels(textMeasurer, (FFT_HOP / sampleRate)*xLegendWidth,0.0, - xLegendWidth, ::timeAxisFormater) - legendElements.forEach {legendElement -> - val tickPos = max(tickStroke.toPx() / 2F, min(xLegendWidth-tickStroke.toPx(), legendElement.xPos - tickStroke.toPx() / 2F)) + val legendElements = makeXLabels( + textMeasurer, (FFT_HOP / sampleRate) * xLegendWidth, 0.0, + xLegendWidth, ::timeAxisFormater + ) + legendElements.forEach { legendElement -> + val tickPos = + max( + tickStroke.toPx() / 2F, + min( + xLegendWidth - tickStroke.toPx(), + legendElement.xPos - tickStroke.toPx() / 2F + ) + ) drawLine( - color = colors.onPrimary, start = Offset( + color = colors.onSurfaceVariant, start = Offset( tickPos, sheight.toFloat() ), @@ -394,27 +543,39 @@ class MeasurementScreen( ), strokeWidth = tickStroke.toPx() ) - drawText(legendElement.text, topLeft = Offset(legendElement.textPos, sheight.toFloat() + tickLength.toPx())) + drawText( + legendElement.text, + topLeft = Offset( + legendElement.textPos, + sheight.toFloat() + tickLength.toPx() + ) + ) } } } - return PlotBitmapOverlay(bitmap, size, bottomLegendSize, rightLegendSize, scaleMode.hashCode()) + return PlotBitmapOverlay( + bitmap, + size, + bottomLegendSize, + rightLegendSize, + scaleMode.hashCode() + ) } @Composable fun spectrumAxis( - settings: SpectrumSettings, - values: SpectrumPlotData + settings: SpectrumSettings ) { - val colors = MaterialTheme.colors + val colors = MaterialTheme.colorScheme val textMeasurer = rememberTextMeasurer() Canvas(modifier = Modifier.fillMaxSize()) { - if (preparedSpectrumOverlayBitmap.imageSize != size || preparedSpectrumOverlayBitmap.plotSettingsHashCode != settings.hashCode()) { + if (preparedSpectrumOverlayBitmap.imageSize != size || + preparedSpectrumOverlayBitmap.plotSettingsHashCode != settings.hashCode() + ) { preparedSpectrumOverlayBitmap = buildSpectrumAxisBitmap( size, Density(density), settings, - values, textMeasurer, colors ) @@ -425,7 +586,7 @@ class MeasurementScreen( @Composable fun spectrogramAxis(scaleMode: SpectrogramBitmap.Companion.ScaleMode, sampleRate: Double) { - val colors = MaterialTheme.colors + val colors = MaterialTheme.colorScheme val textMeasurer = rememberTextMeasurer() Canvas(modifier = Modifier.fillMaxSize()) { if (preparedSpectrogramOverlayBitmap.imageSize != size) { @@ -452,7 +613,7 @@ class MeasurementScreen( } } - fun processSpectrum(spectrumCanvasState: SpectrogramViewModel, it : SpectrumData) : Int { + fun processSpectrum(spectrumCanvasState: SpectrogramViewModel, it: SpectrumData): Int { spectrumCanvasState.currentStripData.pushSpectrumToSpectrogramData( it, mindB, rangedB, dbGain @@ -480,14 +641,16 @@ class MeasurementScreen( } @Composable - fun buildNoiseLevelText(noiseLevel : Double) : AnnotatedString = buildAnnotatedString { + fun buildNoiseLevelText(noiseLevel: Double): AnnotatedString = buildAnnotatedString { val inRangeNoise = noiseLevel > MIN_SHOWN_DBA_VALUE && noiseLevel < MAX_SHOWN_DBA_VALUE val colorIndex = noiseColorRampSpl.indexOfFirst { pair -> pair.first < noiseLevel } - withStyle(style = SpanStyle( - color = if(inRangeNoise) noiseColorRampSpl[colorIndex].second else MaterialTheme.colors.onPrimary, - fontSize = NOISE_LEVEL_FONT_SIZE, - baselineShift = BaselineShift.None - )) { + withStyle( + style = SpanStyle( + color = if (inRangeNoise) noiseColorRampSpl[colorIndex].second else MaterialTheme.colorScheme.onPrimary, + fontSize = NOISE_LEVEL_FONT_SIZE, + baselineShift = BaselineShift.None + ) + ) { when { inRangeNoise -> append("${round(noiseLevel * 10) / 10}") else -> append("-") @@ -495,13 +658,14 @@ class MeasurementScreen( } } + @Suppress("LongParameterList", "LongMethod", "SpreadOperator") @Composable fun spectrumPlot( modifier: Modifier, settings: SpectrumSettings, - values: SpectrumPlotData + values: SpectrumPlotData, ) { - val surfaceColor = MaterialTheme.colors.onSurface + val surfaceColor = MaterialTheme.colorScheme.onSurface // color ramp 0F left side of spectrum // 1F right side of spectrum val spectrumColorRamp = remember(settings) { @@ -509,7 +673,7 @@ class MeasurementScreen( val pair = noiseColorRampSpl[noiseColorRampSpl.size - 1 - index] val linearIndex = max( 0.0, ((pair.first - settings.minimumX) / - (settings.maximumX - settings.minimumX)) + (settings.maximumX - settings.minimumX)) ) Pair(linearIndex.toFloat(), pair.second) }.toTypedArray() @@ -568,8 +732,8 @@ class MeasurementScreen( } @Composable - fun vueMeter(modifier: Modifier, settings: VueMeterSettings, value : Double) { - val color = MaterialTheme.colors + fun vueMeter(modifier: Modifier, settings: VueMeterSettings, value: Double) { + val color = MaterialTheme.colorScheme val textMeasurer = rememberTextMeasurer() Canvas(modifier = modifier) { // x axis labels @@ -605,7 +769,7 @@ class MeasurementScreen( cornerRadius = CornerRadius(barHeight / 2, barHeight / 2), size = Size(size.width, barHeight) ) - val valueRatio = (value-settings.minimum)/(settings.maximum-settings.minimum) + val valueRatio = (value - settings.minimum) / (settings.maximum - settings.minimum) val colorIndex = noiseColorRampSpl.indexOfFirst { pair -> pair.first < value } drawRoundRect( color = noiseColorRampSpl[colorIndex].second, @@ -616,6 +780,7 @@ class MeasurementScreen( } } + @Suppress("LongParameterList", "LongMethod") @Composable fun measurementHeader(noiseLevel: Double) { val rightRoundedSquareShape: Shape = RoundedCornerShape( @@ -624,20 +789,22 @@ class MeasurementScreen( bottomStart = 0.dp, bottomEnd = 40.dp ) - val vueMeterSettings = VueMeterSettings(20.0,120.0, - IntArray(6){v->((v+ 1)*20.0).toInt()}) + val vueMeterSettings = VueMeterSettings(20.0, 120.0, + IntArray(6) { v -> ((v + 1) * 20.0).toInt() }) Column() { Row( horizontalArrangement = Arrangement.SpaceBetween ) { Surface( Modifier.padding(top = 20.dp, bottom = 10.dp).weight(1F), - color = MaterialTheme.colors.background, + color = MaterialTheme.colorScheme.background, shape = rightRoundedSquareShape, - elevation = 10.dp + shadowElevation = 10.dp ) { - Row(modifier = Modifier.padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween) { + Row( + modifier = Modifier.padding(10.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { Text( buildAnnotatedString { withStyle( @@ -688,7 +855,12 @@ class MeasurementScreen( @OptIn(ExperimentalFoundationApi::class) @Composable - fun measurementPager(bitmapOffset: Int, sampleRate: Double, spectrumData: SpectrumPlotData, spectrumSettings: SpectrumSettings) { + fun measurementPager( + bitmapOffset: Int, + sampleRate: Double, + spectrumData: SpectrumPlotData, + spectrumSettings: SpectrumSettings, + ) { val animationScope = rememberCoroutineScope() val pagerState = rememberPagerState(pageCount = { MeasurementTabState.entries.size }) @@ -699,23 +871,31 @@ class MeasurementScreen( Tab( text = { Text(MEASUREMENT_TAB_LABEL[entry.ordinal]) }, selected = pagerState.currentPage == entry.ordinal, - onClick = { animationScope.launch {pagerState.animateScrollToPage(entry.ordinal)} } + onClick = { animationScope.launch { pagerState.animateScrollToPage(entry.ordinal) } } ) } } - HorizontalPager(state = pagerState) {page-> + HorizontalPager(state = pagerState) { page -> when (MeasurementTabState.entries[page]) { MeasurementTabState.SPECTROGRAM -> Box(Modifier.fillMaxSize()) { spectrogram(spectrumCanvasState, bitmapOffset) spectrogramAxis(scaleMode, sampleRate) } + MeasurementTabState.SPECTRUM -> Box(Modifier.fillMaxSize()) { spectrumPlot(Modifier.fillMaxSize(), spectrumSettings, spectrumData) - spectrumAxis(spectrumSettings, spectrumData) - } else -> Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {Text( - text = "Text tab ${MEASUREMENT_TAB_LABEL[page]} selected", - style = MaterialTheme.typography.body1 - )} + spectrumAxis(spectrumSettings) + } + + else -> Surface( + Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Text( + text = "Text tab ${MEASUREMENT_TAB_LABEL[page]} selected", + style = MaterialTheme.typography.bodyLarge + ) + } } } } @@ -723,13 +903,22 @@ class MeasurementScreen( @OptIn(ExperimentalFoundationApi::class) + @Suppress("LongParameterList", "LongMethod") @Composable - override fun Content(modifier: Modifier) { + fun Content( + lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, + ) { var bitmapOffset by remember { mutableStateOf(0) } var noiseLevel by remember { mutableStateOf(0.0) } - var sampleRate by remember { mutableStateOf( DEFAULT_SAMPLE_RATE ) } - var spectrumDataState by remember { mutableStateOf( SpectrumPlotData(ArrayList(0), - DoubleArray(0), DoubleArray(0))) } + var sampleRate by remember { mutableStateOf(DEFAULT_SAMPLE_RATE) } + var spectrumDataState by remember { + mutableStateOf( + SpectrumPlotData( + ArrayList(0), + DoubleArray(0), DoubleArray(0) + ) + ) + } var spectrumSettings by remember { mutableStateOf( SpectrumSettings( @@ -739,19 +928,29 @@ class MeasurementScreen( ) ) } - var indicatorCollectJob : Job? = null - var spectrumCollectJob : Job? = null - val launchMeasurementJob = fun () { - indicatorCollectJob = lifecycleScope.launch { + var indicatorCollectJob: Job? = null + var spectrumCollectJob: Job? = null + val launchMeasurementJob = fun() { + indicatorCollectJob = lifecycleOwner.lifecycleScope.launch { val levelDisplay = LevelDisplayWeightedDecay(FAST_DECAY_RATE, WINDOW_TIME) - var levelDisplayBands : Array? = null + var levelDisplayBands: Array? = null measurementService.collectAudioIndicators().collect { - if(levelDisplayBands == null) { - levelDisplayBands = Array(it.nominalFrequencies.size) {LevelDisplayWeightedDecay(FAST_DECAY_RATE, WINDOW_TIME)} + if (levelDisplayBands == null) { + levelDisplayBands = + Array(it.nominalFrequencies.size) { + LevelDisplayWeightedDecay( + FAST_DECAY_RATE, + WINDOW_TIME + ) + } } noiseLevel = levelDisplay.getWeightedValue(it.laeq) - val splWeightedArray = DoubleArray(it.nominalFrequencies.size) {index -> levelDisplayBands!![index].getWeightedValue(it.thirdOctave[index])} - spectrumDataState = SpectrumPlotData(it.nominalFrequencies, it.thirdOctave, splWeightedArray) + val splWeightedArray = + DoubleArray(it.nominalFrequencies.size) { index -> + levelDisplayBands!![index].getWeightedValue(it.thirdOctave[index]) + } + spectrumDataState = + SpectrumPlotData(it.nominalFrequencies, it.thirdOctave, splWeightedArray) spectrumSettings = SpectrumSettings( MIN_SHOWN_DBA_VALUE_SPECTRUM, MAX_SHOWN_DBA_VALUE_SPECTRUM, @@ -759,7 +958,7 @@ class MeasurementScreen( ) } } - spectrumCollectJob = lifecycleScope.launch { + spectrumCollectJob = lifecycleOwner.lifecycleScope.launch { println("Launch spectrum lifecycle") measurementService.collectSpectrumData().collect() { spectrumData -> sampleRate = spectrumData.sampleRate.toDouble() @@ -770,13 +969,14 @@ class MeasurementScreen( } } launchMeasurementJob() - lifecycleScope.launch { - lifecycle.asEventFlow().collect { event -> + lifecycleOwner.lifecycleScope.launch { + lifecycleOwner.lifecycle.eventFlow.collect { event -> if (event == Lifecycle.Event.ON_PAUSE) { indicatorCollectJob?.cancel() spectrumCollectJob?.cancel() - } else if(event == Lifecycle.Event.ON_RESUME && - (indicatorCollectJob == null || indicatorCollectJob?.isActive == false)) { + } else if (event == Lifecycle.Event.ON_RESUME && + (indicatorCollectJob == null || indicatorCollectJob?.isActive == false) + ) { launchMeasurementJob() } } @@ -784,7 +984,7 @@ class MeasurementScreen( Surface( modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colors.primary + color = MaterialTheme.colorScheme.background ) { BoxWithConstraints { if (maxWidth > maxHeight) { @@ -793,13 +993,23 @@ class MeasurementScreen( measurementHeader(noiseLevel) } Column(modifier = Modifier) { - measurementPager(bitmapOffset, sampleRate, spectrumDataState, spectrumSettings) + measurementPager( + bitmapOffset, + sampleRate, + spectrumDataState, + spectrumSettings + ) } } } else { Column(modifier = Modifier.fillMaxSize()) { measurementHeader(noiseLevel) - measurementPager(bitmapOffset, sampleRate, spectrumDataState, spectrumSettings) + measurementPager( + bitmapOffset, + sampleRate, + spectrumDataState, + spectrumSettings + ) } } } @@ -807,24 +1017,43 @@ class MeasurementScreen( } } -data class SpectrogramViewModel(var currentStripData : SpectrogramBitmap.SpectrogramDataModel, - val cachedStrips : ArrayList, - var spectrogramCanvasSize : Size) +data class SpectrogramViewModel( + var currentStripData: SpectrogramBitmap.SpectrogramDataModel, + val cachedStrips: ArrayList, + var spectrogramCanvasSize: Size, +) -data class LegendElement(val text : TextLayoutResult, val xPos : Float, - val textPos : Float, val depth : Int) +enum class MeasurementTabState { SPECTRUM, + SPECTROGRAM, + MAP +} -enum class MeasurementTabState { SPECTRUM, SPECTROGRAM, MAP} val MEASUREMENT_TAB_LABEL = listOf("Spectrum", "Spectrogram", "Map") -data class PlotBitmapOverlay(val imageBitmap: ImageBitmap, val imageSize: Size, val horizontalLegendSize: Size, val verticalLegendSize: Size, val plotSettingsHashCode : Int) -data class MeasurementStatistics(val label : String, val value : String) +data class PlotBitmapOverlay( + val imageBitmap: ImageBitmap, + val imageSize: Size, + val horizontalLegendSize: Size, + val verticalLegendSize: Size, + val plotSettingsHashCode: Int, +) + +data class MeasurementStatistics(val label: String, val value: String) + +data class SpectrumSettings( + val minimumX: Double, + val maximumX: Double, + val nominalFrequencies: List, +) -data class SpectrumSettings(val minimumX : Double, val maximumX : Double, val nominalFrequencies : List) +data class SpectrumPlotData( + val nominalFrequencies: List, + val spl: DoubleArray, + val splWeighted: DoubleArray, +) -data class SpectrumPlotData(val nominalFrequencies : List, val spl : DoubleArray, val splWeighted : DoubleArray) +data class VueMeterSettings(val minimum: Double, val maximum: Double, val xLabels: IntArray) { -data class VueMeterSettings(val minimum : Double, val maximum : Double, val xLabels : IntArray) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/PlatformInfoScreen.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/PlatformInfoScreen.kt new file mode 100644 index 0000000..ec8b818 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/PlatformInfoScreen.kt @@ -0,0 +1,35 @@ +package org.noiseplanet.noisecapture.ui.screens + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import noisecapture.composeapp.generated.resources.Res +import noisecapture.composeapp.generated.resources.compose_multiplatform +import org.jetbrains.compose.resources.painterResource +import org.noiseplanet.noisecapture.ui.components.Greeting + +/** + * Gives information about the platform the app is currently running on. + * Not aimed to be kept in the end but for now serves as a practical example of + * platform specific implementations + */ +@Composable +fun PlatformInfoScreen( + modifier: Modifier = Modifier, +) { + // A platform specific greeting message + val greeting = remember { Greeting().greet() } + + Column( + modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image(painterResource(Res.drawable.compose_multiplatform), null) + Text("Compose: $greeting") + } +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/RequestPermissionScreen.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/RequestPermissionScreen.kt new file mode 100644 index 0000000..0456ca9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/ui/screens/RequestPermissionScreen.kt @@ -0,0 +1,175 @@ +package org.noiseplanet.noisecapture.ui.screens + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.QuestionMark +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import getPlatform +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch +import noisecapture.composeapp.generated.resources.Res +import noisecapture.composeapp.generated.resources.request_permission_button_next +import noisecapture.composeapp.generated.resources.request_permission_button_request +import noisecapture.composeapp.generated.resources.request_permission_button_settings +import noisecapture.composeapp.generated.resources.request_permission_explanation +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.koinInject +import org.noiseplanet.noisecapture.permission.PermissionService +import org.noiseplanet.noisecapture.permission.PermissionState + +/** + * Presents required permissions to the user with controls to either request the + * permission if it was not yet asked, or to open the corresponding settings page + * if permission was already previously denied + * + * TODO: Use view models to provide data to the interface + * TODO: Rethink package structure to split views into smaller components + */ +@Composable +fun RequestPermissionScreen( + onClickNextButton: () -> Unit, + permissionService: PermissionService = koinInject(), + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + + Surface( + modifier = modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues( + top = 16.dp, + bottom = 64.dp, + start = 16.dp, + end = 16.dp + ), + modifier = Modifier.fillMaxSize() + ) { + item { + Text( + text = stringResource(Res.string.request_permission_explanation), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } + + val requiredPermissions = getPlatform().requiredPermissions + items(requiredPermissions) { permission -> + val permissionState: PermissionState by permissionService + .getPermissionStateFlow(permission) + .collectAsState(PermissionState.NOT_DETERMINED) + + Column( + modifier = Modifier.fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = permission.name, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f) + ) + Icon( + imageVector = when (permissionState) { + PermissionState.GRANTED -> Icons.Default.Check + PermissionState.NOT_DETERMINED -> Icons.Default.QuestionMark + else -> Icons.Default.Close + }, + tint = when (permissionState) { + PermissionState.GRANTED -> Color.Green + PermissionState.NOT_DETERMINED -> Color.Gray + else -> Color.Red + }, + contentDescription = null, + modifier = Modifier.padding(horizontal = 8.dp) + ) + Button( + onClick = { + permissionService.openSettingsForPermission(permission) + }, + ) { + Text( + text = stringResource(Res.string.request_permission_button_settings), + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + + // If permission state is not yet determined, show a button to trigger + // the permission request popup + AnimatedVisibility(permissionState == PermissionState.NOT_DETERMINED) { + Button( + onClick = { + coroutineScope.launch { + permissionService.requestPermission(permission) + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = stringResource(Res.string.request_permission_button_request), + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + } + } + item { + // True if all required permissions have been granted + val allPermissionsGranted by combine( + requiredPermissions.map { permission -> + permissionService.getPermissionStateFlow(permission) + }, + transform = { + it.all { permission -> + permission == PermissionState.GRANTED + } + } + ).collectAsState(false) + + AnimatedVisibility(allPermissionsGranted) { + // Show Next button only if all required permissions have been granted + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.fillParentMaxWidth()) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + Button(onClick = onClickNextButton) { + Text(stringResource(Res.string.request_permission_button_next)) + } + } + } + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.kt b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.kt new file mode 100644 index 0000000..4dc5941 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.kt @@ -0,0 +1,8 @@ +package org.noiseplanet.noisecapture.util + +import androidx.compose.ui.graphics.ImageBitmap + +/** + * Converts a byte array to a compose ImageBitmap using platform specific implementations + */ +expect fun ByteArray.toImageBitmap(): ImageBitmap diff --git a/shared/src/commonTest/composeResources/files/config_48000_third_octave.json b/composeApp/src/commonTest/composeResources/files/config_48000_third_octave.json similarity index 100% rename from shared/src/commonTest/composeResources/files/config_48000_third_octave.json rename to composeApp/src/commonTest/composeResources/files/config_48000_third_octave.json diff --git a/shared/src/commonTest/composeResources/files/speak_44100Hz_16bitsPCM_10s.raw b/composeApp/src/commonTest/composeResources/files/speak_44100Hz_16bitsPCM_10s.raw similarity index 100% rename from shared/src/commonTest/composeResources/files/speak_44100Hz_16bitsPCM_10s.raw rename to composeApp/src/commonTest/composeResources/files/speak_44100Hz_16bitsPCM_10s.raw diff --git a/composeApp/src/commonTest/kotlin/IgnoreUtil.kt b/composeApp/src/commonTest/kotlin/IgnoreUtil.kt new file mode 100644 index 0000000..39e5a51 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/IgnoreUtil.kt @@ -0,0 +1,13 @@ +/** + * This annotation will be implemented in iOS sources to ignore a test function or class, + * but will just be a dummy annotation for Android + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +expect annotation class IgnoreIos() + +/** + * This annotation will be implemented in Android sources to ignore a test function or class, + * but will just be a dummy annotation for iOS + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +expect annotation class IgnoreAndroid diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannelTest.kt b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/SpectrumChannelTest.kt similarity index 98% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannelTest.kt rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/SpectrumChannelTest.kt index f6fdf7b..d62cf9c 100644 --- a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/SpectrumChannelTest.kt +++ b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/SpectrumChannelTest.kt @@ -1,13 +1,23 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.signal +import IgnoreIos import kotlinx.coroutines.test.runTest -import noisecapture.shared.generated.resources.Res +import noisecapture.composeapp.generated.resources.Res import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.noiseplanet.noisecapture.audio.signal.AntiAliasing +import org.noiseplanet.noisecapture.audio.signal.Bandpass +import org.noiseplanet.noisecapture.audio.signal.GeneralConfiguration +import org.noiseplanet.noisecapture.audio.signal.Sos +import org.noiseplanet.noisecapture.audio.signal.SpectrumChannel +import org.noiseplanet.noisecapture.audio.signal.SpectrumChannelConfiguration +import org.noiseplanet.noisecapture.audio.signal.SubsamplingFilter +import org.noiseplanet.noisecapture.audio.signal.get48000HZ import kotlin.math.pow import kotlin.math.sqrt import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +import org.noiseplanet.noisecapture.audio.signal.DigitalFilterConfiguration as DigitalFilterConfiguration1 class SpectrumChannelTest { @@ -1344,7 +1354,7 @@ class SpectrumChannelTest { ), sampleRatio = 2 ), - aWeighting = DigitalFilterConfiguration( + aWeighting = DigitalFilterConfiguration1( filterDenominator = doubleArrayOf( 1.0, -4.01958210258393, @@ -1364,7 +1374,7 @@ class SpectrumChannelTest { 0.25574354103517516 ) ), - cWeighting = DigitalFilterConfiguration( + cWeighting = DigitalFilterConfiguration1( filterDenominator = doubleArrayOf( 1.0, -2.134692386318322, @@ -1432,6 +1442,14 @@ class SpectrumChannelTest { assertEquals(expectedLevel, thirdOctaves[frequencies.indexOf(4000.0)], 0.1); } + /** + * TODO: Fix compose test resources on iOS + * For the moment the build scripts only copy the main target resources to the simulator + * so accessing test resources is not possible. It seems like it could be solved by + * using cocoapods for providing the shared framework on iOS builds but I didn't manage + * to get it to work yet. + */ + @IgnoreIos @OptIn(ExperimentalResourceApi::class) @Test fun testSpeak() = runTest { @@ -1472,4 +1490,4 @@ class SpectrumChannelTest { assertEquals(expectedBC, sc.processSamplesWeightC(floatArray), 0.01) } -} \ No newline at end of file +} diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/TestFFT.kt b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/TestFFT.kt similarity index 87% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/TestFFT.kt rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/TestFFT.kt index 4883366..f5f15fa 100644 --- a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/TestFFT.kt +++ b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/TestFFT.kt @@ -1,6 +1,16 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.signal import kotlinx.coroutines.test.runTest +import org.noiseplanet.noisecapture.audio.signal.Bluestein +import org.noiseplanet.noisecapture.audio.signal.SpectrumChannel +import org.noiseplanet.noisecapture.audio.signal.fft +import org.noiseplanet.noisecapture.audio.signal.fftFloat +import org.noiseplanet.noisecapture.audio.signal.get48000HZ +import org.noiseplanet.noisecapture.audio.signal.nextPowerOfTwo +import org.noiseplanet.noisecapture.audio.signal.realFFT +import org.noiseplanet.noisecapture.audio.signal.realFFTFloat +import org.noiseplanet.noisecapture.audio.signal.realIFFT +import org.noiseplanet.noisecapture.audio.signal.realIFFTFloat import kotlin.math.PI import kotlin.math.ceil import kotlin.math.cos @@ -17,11 +27,12 @@ import kotlin.time.measureTime class TestFFT { companion object { + fun generateSinusoidalSignal( frequency: Double, sampleRate: Double, duration: Double, - coefficient: (Int) -> Double = { 1.0 } + coefficient: (Int) -> Double = { 1.0 }, ): DoubleArray { val numSamples = (duration * sampleRate).toInt() val signal = DoubleArray(numSamples, coefficient) @@ -39,7 +50,7 @@ class TestFFT { frequency: Double, sampleRate: Double, duration: Double, - coefficient: (Int) -> Float = { 1.0f } + coefficient: (Int) -> Float = { 1.0f }, ): FloatArray { val numSamples = (duration * sampleRate).toInt() val signal = FloatArray(numSamples, coefficient) @@ -67,7 +78,7 @@ class TestFFT { @Test - fun testFFT() { + fun testFFTDouble() { val values = doubleArrayOf( 0.0, 0.0, 1.0, 0.0, 2.0, 0.0, 3.0, 0.0, 4.0, 0.0, 5.0, 0.0, 6.0, 0.0, 7.0, 0.0 @@ -270,7 +281,7 @@ class TestFFT { 0.0, 0.0, 1.0, 0.0, 2.0, 0.0, 3.0, 0.0, 4.0, 0.0, 5.0, 0.0 ) val bluestein = Bluestein(input.size / 2) - val expected_chirp = doubleArrayOf( + val expectedChirp = doubleArrayOf( 0.965925826289071, -0.2588190451025107, 0.5000000000000081, @@ -318,19 +329,19 @@ class TestFFT { 0.965925826289071, -0.2588190451025107 ) - assertEquals(expected_chirp.size, bluestein.chirp.size) - expected_chirp.forEachIndexed { index, it -> - assertEquals(it, bluestein.chirp[index], 1e-8) + assertEquals(expectedChirp.size, bluestein.chirp.size) + expectedChirp.forEachIndexed { index, expected -> + assertEquals(expected, bluestein.chirp[index], 1e-8) } - val expected_fft = doubleArrayOf( + val expectedFft = doubleArrayOf( -6.0, 0.0, -6.0, 22.39230485, -6.0, 10.39230485, -6.0, 6.0, -6.0, 3.46410162, -6.0, 1.60769515, -6.0, -0.0, -6.0, -1.60769515, -6.0, -3.46410162, -6.0, -6.0, -6.0, -10.39230485, -6.0, -22.39230485 ) val got = bluestein.fft(input) - assertEquals(expected_fft.size, got.size) - expected_fft.forEachIndexed { index, it -> - assertEquals(it, got[index], 1e-8, message = "Index $index") + assertEquals(expectedFft.size, got.size) + expectedFft.forEachIndexed { index, expected -> + assertEquals(expected, got[index], 1e-8, message = "Index $index") } } @@ -359,26 +370,21 @@ class TestFFT { } println( "min ${timings.min()} max ${timings.max()}" + - " median ${ - timings.sorted() - [if (timings.size % 2 == 0) timings.size / 2 - 1 else timings.size / 2] - }" + " median ${ + timings.sorted()[if (timings.size % 2 == 0) timings.size / 2 - 1 else timings.size / 2] + }" ) println( "min ${timings2.min()} max ${timings2.max()}" + - " median ${ - timings2.sorted() - [if (timings2.size % 2 == 0) timings2.size / 2 - 1 else timings2.size / 2] - }" + " median ${ + timings2.sorted()[if (timings2.size % 2 == 0) timings2.size / 2 - 1 else timings2.size / 2] + }" ) println( "min ${timings3.min()} max ${timings3.max()}" + - " median ${ - timings3.sorted() - [if (timings3.size % 2 == 0) timings3.size / 2 - 1 else timings3.size / 2] - }" + " median ${ + timings3.sorted()[if (timings3.size % 2 == 0) timings3.size / 2 - 1 else timings3.size / 2] + }" ) } } - - diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/TestWindowAnalysis.kt b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/TestWindowAnalysis.kt similarity index 88% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/TestWindowAnalysis.kt rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/TestWindowAnalysis.kt index 95446a8..ead729c 100644 --- a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/TestWindowAnalysis.kt +++ b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/TestWindowAnalysis.kt @@ -1,9 +1,14 @@ -package org.noiseplanet.noisecapture.shared.signal +package org.noiseplanet.noisecapture.signal import kotlinx.coroutines.test.runTest -import org.noiseplanet.noisecapture.AudioSamples -import org.noiseplanet.noisecapture.shared.AcousticIndicatorsProcessing -import org.noiseplanet.noisecapture.shared.WINDOW_TIME +import org.noiseplanet.noisecapture.audio.AcousticIndicatorsProcessing +import org.noiseplanet.noisecapture.audio.AudioSamples +import org.noiseplanet.noisecapture.audio.WINDOW_TIME +import org.noiseplanet.noisecapture.audio.signal.FAST_DECAY_RATE +import org.noiseplanet.noisecapture.audio.signal.LevelDisplayWeightedDecay +import org.noiseplanet.noisecapture.audio.signal.SpectrumData +import org.noiseplanet.noisecapture.audio.signal.Window +import org.noiseplanet.noisecapture.audio.signal.WindowAnalysis import kotlin.math.PI import kotlin.math.min import kotlin.math.pow @@ -72,7 +77,7 @@ class TestWindowAnalysis { fun testOverlapWindowsSmallPush() { val arraySize = 13 val ones = FloatArray(arraySize) { if (it in 2..arraySize - 3) 1f else 0f } - //val ones = FloatArray(arraySize) {it.toFloat()} + // val ones = FloatArray(arraySize) {it.toFloat()} val windowAnalysis = WindowAnalysis(1, 5, 2) val processedWindows = ArrayList() val step = 3 @@ -109,7 +114,8 @@ class TestWindowAnalysis { TestFFT.generateSinusoidalFloatSignal( frequencyPeak, sampleRate.toDouble(), 1.0 - ) { peak.toFloat() }, sum + ) { peak.toFloat() }, + sum ).toFloatArray() } @@ -134,7 +140,6 @@ class TestWindowAnalysis { } } - @Test fun testSinusSTFFTHannWindowThirdOctave() = runTest { val sampleRate = 32768 @@ -149,14 +154,15 @@ class TestWindowAnalysis { TestFFT.generateSinusoidalFloatSignal( frequencyPeak, sampleRate.toDouble(), 1.0 - ) { peak.toFloat() }, sum + ) { peak.toFloat() }, + sum ).toFloatArray() } val bufferSize = (sampleRate * 0.1).toInt() var cursor = 0 - val windowSize = (sampleRate*0.125).toInt() - val wa = WindowAnalysis(sampleRate, windowSize, windowSize/2) + val windowSize = (sampleRate * 0.125).toInt() + val wa = WindowAnalysis(sampleRate, windowSize, windowSize / 2) val spectrumDataArray = ArrayList() while (cursor < signal.size) { val windowSize = min(bufferSize, signal.size - cursor) @@ -167,12 +173,14 @@ class TestWindowAnalysis { } spectrumDataArray.forEachIndexed { index, spectrumData -> val thirdOctaveSquare = spectrumData.thirdOctaveProcessing( - 50.0, 12000.0, + 50.0, + 12000.0, octaveWindow = SpectrumData.OctaveWindow.RECTANGULAR ).asList() val thirdOctaveFractional = spectrumData.thirdOctaveProcessing( 50.0, - 12000.0, octaveWindow = SpectrumData.OctaveWindow.FRACTIONAL + 12000.0, + octaveWindow = SpectrumData.OctaveWindow.FRACTIONAL ).asList() val indexOf1000Hz = thirdOctaveSquare.indexOfFirst { t -> t.midFrequency.toInt() == 1000 } @@ -202,16 +210,15 @@ class TestWindowAnalysis { // fast level decay should be at the rate of 34.7 dB/s val levelDisplayWeightedDecay = LevelDisplayWeightedDecay(FAST_DECAY_RATE, timeInterval) var previousValue = 0.0 - levels.forEachIndexed { i, it -> - val dbValue = levelDisplayWeightedDecay.getWeightedValue(it) - if (i * timeInterval >= cutoffTime) { + levels.forEachIndexed { index, level -> + val dbValue = levelDisplayWeightedDecay.getWeightedValue(level) + if (index * timeInterval >= cutoffTime) { assertEquals(FAST_DECAY_RATE, (dbValue - previousValue) / timeInterval, 0.01) } previousValue = dbValue } } - @Test fun testAndroidSpecNoiseLevel() = runTest { val sampleRate = 48000 @@ -225,10 +232,7 @@ class TestWindowAnalysis { val acousticIndicatorProcessing = AcousticIndicatorsProcessing(sampleRate) val processed = acousticIndicatorProcessing.processSamples( - AudioSamples( - 0, signal, - AudioSamples.ErrorCode.OK, sampleRate - ) + AudioSamples(0, signal, sampleRate) ) val averageLeq = processed.map { indicators -> indicators.leq }.average() assertEquals(expectedLevel, averageLeq, 0.01) diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/fft_test.py b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/fft_test.py similarity index 100% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/fft_test.py rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/fft_test.py diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/requirements.txt b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/requirements.txt similarity index 100% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/requirements.txt rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/requirements.txt diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/signal_reference.py b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/signal_reference.py similarity index 100% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/signal_reference.py rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/signal_reference.py diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/stft_test.py b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/stft_test.py similarity index 100% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/stft_test.py rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/stft_test.py diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/test_nonpoweroftwo.py b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/test_nonpoweroftwo.py similarity index 100% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/test_nonpoweroftwo.py rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/test_nonpoweroftwo.py diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/test_spectrum.py b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/test_spectrum.py similarity index 100% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/test_spectrum.py rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/test_spectrum.py diff --git a/shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/test_spectrum2.py b/composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/test_spectrum2.py similarity index 100% rename from shared/src/commonTest/kotlin/org/noiseplanet/noisecapture/shared/signal/ref/test_spectrum2.py rename to composeApp/src/commonTest/kotlin/org/noiseplanet/noisecapture/signal/ref/test_spectrum2.py diff --git a/composeApp/src/iosMain/kotlin/MainViewController.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt new file mode 100644 index 0000000..9095a70 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/MainViewController.kt @@ -0,0 +1,17 @@ +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.ComposeUIViewController +import org.koin.core.logger.PrintLogger +import org.noiseplanet.noisecapture.initKoin + +/** + * iOS application entry point + */ +@Composable +fun MainViewController() = ComposeUIViewController { + + // TODO: Platform specific implementation + val logger = PrintLogger() + + initKoin().logger(logger) + App() +} diff --git a/composeApp/src/iosMain/kotlin/Platform.ios.kt b/composeApp/src/iosMain/kotlin/Platform.ios.kt new file mode 100644 index 0000000..2027972 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/Platform.ios.kt @@ -0,0 +1,10 @@ +import platform.UIKit.UIDevice + +@Suppress("MatchingDeclarationName") +class IOSPlatform : Platform { + + override val name: String = + UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion +} + +actual fun getPlatform(): Platform = IOSPlatform() diff --git a/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.ios.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.ios.kt new file mode 100644 index 0000000..bdcf3ae --- /dev/null +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.ios.kt @@ -0,0 +1,35 @@ +package org.noiseplanet.noisecapture.permission + +import org.koin.core.module.Module +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.noiseplanet.noisecapture.permission.delegate.AudioRecordPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.BluetoothPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.BluetoothServicePermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.LocationBackgroundPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.LocationForegroundPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.LocationServicePermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.PermissionDelegate + +internal actual fun platformPermissionModule(): Module = module { + single(named(Permission.BLUETOOTH_SERVICE_ON.name)) { + BluetoothServicePermissionDelegate() + } + single(named(Permission.BLUETOOTH.name)) { + BluetoothPermissionDelegate() + } + single(named(Permission.LOCATION_SERVICE_ON.name)) { + LocationServicePermissionDelegate() + } + single(named(Permission.LOCATION_FOREGROUND.name)) { + LocationForegroundPermissionDelegate() + } + single(named(Permission.LOCATION_BACKGROUND.name)) { + LocationBackgroundPermissionDelegate( + locationForegroundPermissionDelegate = get(named(Permission.LOCATION_FOREGROUND.name)), + ) + } + single(named(Permission.RECORD_AUDIO.name)) { + AudioRecordPermissionDelegate() + } +} diff --git a/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt new file mode 100644 index 0000000..bc74d7c --- /dev/null +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt @@ -0,0 +1,30 @@ +package org.noiseplanet.noisecapture.permission.delegate + +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.openNSUrl +import platform.AVFAudio.AVAudioSession +import platform.AVFAudio.AVAudioSessionRecordPermissionDenied +import platform.AVFAudio.AVAudioSessionRecordPermissionGranted + +internal class AudioRecordPermissionDelegate : PermissionDelegate { + + override suspend fun getPermissionState(): PermissionState { + return when (AVAudioSession.sharedInstance().recordPermission) { + AVAudioSessionRecordPermissionGranted -> PermissionState.GRANTED + AVAudioSessionRecordPermissionDenied -> PermissionState.DENIED + else -> PermissionState.NOT_DETERMINED + } + } + + override suspend fun providePermission() { + AVAudioSession.sharedInstance().requestRecordPermission { granted -> + // TODO: Fix logger on iOS + println("Record permission granted: $granted") + } + } + + override fun openSettingPage() { + openNSUrl("App-prefs:Privacy&path=MICROPHONE") + } + +} diff --git a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothPermissionDelegate.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothPermissionDelegate.kt similarity index 79% rename from permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothPermissionDelegate.kt rename to composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothPermissionDelegate.kt index 23b71e4..a5b4e64 100644 --- a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothPermissionDelegate.kt +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothPermissionDelegate.kt @@ -1,7 +1,7 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.openAppSettingsPage +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.openNSUrl import platform.CoreBluetooth.CBCentralManager import platform.CoreBluetooth.CBManagerAuthorizationAllowedAlways import platform.CoreBluetooth.CBManagerAuthorizationDenied @@ -9,6 +9,7 @@ import platform.CoreBluetooth.CBManagerAuthorizationNotDetermined import platform.CoreBluetooth.CBManagerAuthorizationRestricted internal class BluetoothPermissionDelegate : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { return when (CBCentralManager.authorization) { CBManagerAuthorizationNotDetermined -> PermissionState.NOT_DETERMINED @@ -23,6 +24,6 @@ internal class BluetoothPermissionDelegate : PermissionDelegate { } override fun openSettingPage() { - openAppSettingsPage() + openNSUrl("App-Prefs:Privacy&path=BLUETOOTH") } } diff --git a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothServicePermissionDelegate.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothServicePermissionDelegate.kt similarity index 82% rename from permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothServicePermissionDelegate.kt rename to composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothServicePermissionDelegate.kt index 8ef5951..2965a87 100644 --- a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/BluetoothServicePermissionDelegate.kt +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/BluetoothServicePermissionDelegate.kt @@ -1,7 +1,7 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.openNSUrl +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.openNSUrl import platform.CoreBluetooth.CBCentralManager import platform.CoreBluetooth.CBCentralManagerDelegateProtocol import platform.CoreBluetooth.CBManagerAuthorizationAllowedAlways @@ -10,6 +10,7 @@ import platform.CoreBluetooth.CBManagerStatePoweredOn import platform.darwin.NSObject internal class BluetoothServicePermissionDelegate : PermissionDelegate { + private val cbCentralManager: CBCentralManager by lazy { CBCentralManager( object : NSObject(), CBCentralManagerDelegateProtocol { @@ -22,7 +23,7 @@ internal class BluetoothServicePermissionDelegate : PermissionDelegate { override suspend fun getPermissionState(): PermissionState { val hasBluetoothPermissionGranted = CBCentralManager.authorization == CBManagerAuthorizationAllowedAlways || - CBCentralManager.authorization == CBManagerAuthorizationRestricted + CBCentralManager.authorization == CBManagerAuthorizationRestricted return if (hasBluetoothPermissionGranted) { if (cbCentralManager.state() == CBManagerStatePoweredOn) { PermissionState.GRANTED diff --git a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationBackgroundPermissionDelegate.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt similarity index 79% rename from permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationBackgroundPermissionDelegate.kt rename to composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt index 1823fd4..55eb839 100644 --- a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationBackgroundPermissionDelegate.kt +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt @@ -1,7 +1,7 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.openAppSettingsPage +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.openNSUrl import platform.CoreLocation.CLLocationManager import platform.CoreLocation.kCLAuthorizationStatusAuthorizedAlways import platform.CoreLocation.kCLAuthorizationStatusDenied @@ -9,13 +9,13 @@ import platform.CoreLocation.kCLAuthorizationStatusDenied internal class LocationBackgroundPermissionDelegate( private val locationForegroundPermissionDelegate: PermissionDelegate, ) : PermissionDelegate { + override suspend fun getPermissionState(): PermissionState { val foregroundPermissionStatus = locationForegroundPermissionDelegate.getPermissionState() return when (foregroundPermissionStatus) { PermissionState.GRANTED -> checkBackgroundLocationPermission() - else - -> foregroundPermissionStatus + else -> foregroundPermissionStatus } } @@ -24,7 +24,7 @@ internal class LocationBackgroundPermissionDelegate( } override fun openSettingPage() { - openAppSettingsPage() + openNSUrl("App-Prefs:Privacy&path=LOCATION") } private fun checkBackgroundLocationPermission(): PermissionState { diff --git a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationForegroundPermissionDelegate.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationForegroundPermissionDelegate.kt similarity index 77% rename from permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationForegroundPermissionDelegate.kt rename to composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationForegroundPermissionDelegate.kt index 2569273..53f2394 100644 --- a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationForegroundPermissionDelegate.kt +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationForegroundPermissionDelegate.kt @@ -1,7 +1,7 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.openAppSettingsPage +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.openNSUrl import platform.CoreLocation.CLLocationManager import platform.CoreLocation.kCLAuthorizationStatusAuthorizedAlways import platform.CoreLocation.kCLAuthorizationStatusAuthorizedWhenInUse @@ -10,13 +10,15 @@ import platform.CoreLocation.kCLAuthorizationStatusNotDetermined import platform.CoreLocation.kCLAuthorizationStatusRestricted internal class LocationForegroundPermissionDelegate : PermissionDelegate { + private var locationManager = CLLocationManager() override suspend fun getPermissionState(): PermissionState { return when (locationManager.authorizationStatus()) { kCLAuthorizationStatusAuthorizedAlways, kCLAuthorizationStatusAuthorizedWhenInUse, - kCLAuthorizationStatusRestricted -> PermissionState.GRANTED + kCLAuthorizationStatusRestricted, + -> PermissionState.GRANTED kCLAuthorizationStatusNotDetermined -> PermissionState.NOT_DETERMINED kCLAuthorizationStatusDenied -> PermissionState.DENIED @@ -29,6 +31,6 @@ internal class LocationForegroundPermissionDelegate : PermissionDelegate { } override fun openSettingPage() { - openAppSettingsPage() + openNSUrl("App-Prefs:Privacy&path=LOCATION") } } diff --git a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationServicePermissionDelegate.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationServicePermissionDelegate.kt similarity index 55% rename from permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationServicePermissionDelegate.kt rename to composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationServicePermissionDelegate.kt index 81e51b2..f0bfc7f 100644 --- a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/LocationServicePermissionDelegate.kt +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationServicePermissionDelegate.kt @@ -1,15 +1,19 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate +package org.noiseplanet.noisecapture.permission.delegate -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.util.openNSUrl +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.openNSUrl import platform.CoreLocation.CLLocationManager internal class LocationServicePermissionDelegate : PermissionDelegate { + private val locationManager = CLLocationManager() override suspend fun getPermissionState(): PermissionState { - return if (locationManager.locationServicesEnabled()) - PermissionState.GRANTED else PermissionState.DENIED + return if (locationManager.locationServicesEnabled()) { + PermissionState.GRANTED + } else { + PermissionState.DENIED + } } override suspend fun providePermission() { diff --git a/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/util/Extensions.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/util/Extensions.kt new file mode 100644 index 0000000..b4a9c1a --- /dev/null +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/permission/util/Extensions.kt @@ -0,0 +1,20 @@ +package org.noiseplanet.noisecapture.permission.util + +import platform.Foundation.NSURL +import platform.UIKit.UIApplication +import platform.UIKit.UIApplicationOpenSettingsURLString + +internal fun openNSUrl(string: String) { + val settingsUrl: NSURL = requireNotNull(NSURL.URLWithString(string)) { + throw CannotOpenSettingsException(string) + } + if (UIApplication.sharedApplication.canOpenURL(settingsUrl)) { + UIApplication.sharedApplication.openURL(settingsUrl) + } else { + throw CannotOpenSettingsException(string) + } +} + +internal fun openAppSettingsPage() { + openNSUrl(UIApplicationOpenSettingsURLString) +} diff --git a/shared/src/iosMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.ios.kt b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.ios.kt similarity index 85% rename from shared/src/iosMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.ios.kt rename to composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.ios.kt index 5bfbe8b..5bd9960 100644 --- a/shared/src/iosMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.ios.kt +++ b/composeApp/src/iosMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.ios.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture +package org.noiseplanet.noisecapture.util import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap diff --git a/composeApp/src/iosTest/kotlin/IgnoreUtil.ios.kt b/composeApp/src/iosTest/kotlin/IgnoreUtil.ios.kt new file mode 100644 index 0000000..ff53de5 --- /dev/null +++ b/composeApp/src/iosTest/kotlin/IgnoreUtil.ios.kt @@ -0,0 +1,12 @@ +import kotlin.test.Ignore + +/** + * Alias for [kotlin.test.Ignore] + */ +actual typealias IgnoreIos = Ignore + +/** + * Doesn't do anything + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreAndroid diff --git a/composeApp/src/wasmJsMain/kotlin/Platform.wasmjs.kt b/composeApp/src/wasmJsMain/kotlin/Platform.wasmjs.kt new file mode 100644 index 0000000..9092b33 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/Platform.wasmjs.kt @@ -0,0 +1,17 @@ +import org.noiseplanet.noisecapture.permission.Permission + +class WasmJSPlatform : Platform { + + override val name: String = "Web with Kotlin/Wasm" + + override val requiredPermissions: List + // We can't control laptop settings on the web so we don't + // check if location services are on. It will be part of the + // location background permission check. + get() = listOf( + Permission.RECORD_AUDIO, + Permission.LOCATION_BACKGROUND + ) +} + +actual fun getPlatform(): Platform = WasmJSPlatform() diff --git a/composeApp/src/wasmJsMain/kotlin/main.kt b/composeApp/src/wasmJsMain/kotlin/main.kt new file mode 100644 index 0000000..839ecdb --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/main.kt @@ -0,0 +1,21 @@ +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.ComposeViewport +import kotlinx.browser.document +import org.koin.core.logger.PrintLogger +import org.noiseplanet.noisecapture.initKoin +import org.noiseplanet.noisecapture.platformModule + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + + // TODO: Add a logger implementation that uses JS console + val logger = PrintLogger() + + ComposeViewport(document.body!!) { + initKoin( + additionalModules = listOf(platformModule) + ).logger(logger) + + App() + } +} diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/PlatformModule.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/PlatformModule.kt new file mode 100644 index 0000000..108c567 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/PlatformModule.kt @@ -0,0 +1,10 @@ +package org.noiseplanet.noisecapture + +import org.koin.core.module.Module +import org.koin.dsl.module +import org.noiseplanet.noisecapture.audio.AudioSource +import org.noiseplanet.noisecapture.audio.JsAudioSource + +val platformModule: Module = module { + factory { JsAudioSource() } +} diff --git a/shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/JsAudioSource.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/audio/JsAudioSource.kt similarity index 58% rename from shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/JsAudioSource.kt rename to composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/audio/JsAudioSource.kt index 6f306cf..0872b2f 100644 --- a/shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/JsAudioSource.kt +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/audio/JsAudioSource.kt @@ -1,33 +1,46 @@ -package org.noiseplanet.noisecapture +package org.noiseplanet.noisecapture.audio +import kotlinx.browser.window import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.datetime.Clock import org.khronos.webgl.get -import web.media.streams.MediaStream -import web.navigator.navigator +import org.noiseplanet.noisecapture.interop.AudioContext +import org.noiseplanet.noisecapture.interop.AudioNode +import org.w3c.dom.mediacapture.MediaStream +import org.w3c.dom.mediacapture.MediaStreamConstraints const val SAMPLES_BUFFER_SIZE = 1024 const val AUDIO_CONSTRAINT = "{audio: {echoCancellation: false, autoGainControl: false, noiseSuppression: false}}" -class JsAudioSource : AudioSource { +/** + * TODO: Document, cleanup, use platform logger instead of println, get rid of force unwraps (!!) + */ +internal class JsAudioSource : AudioSource { - var audioContext: AudioContext? = null - var mediaStream: MediaStream? = null - var micNode: AudioNode? = null - var scriptProcessorNode: AudioNode? = null + private var audioContext: AudioContext? = null + private var mediaStream: MediaStream? = null + private var micNode: AudioNode? = null + private var scriptProcessorNode: AudioNode? = null - //var dummyGainNode : GainNode? = null - val audioSamplesChannel = Channel(onBufferOverflow = BufferOverflow.DROP_OLDEST) + private val audioSamplesChannel = Channel( + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) override suspend fun setup(): Flow { println("Launch JSAudioSource") - navigator.mediaDevices.getUserMedia( - js(AUDIO_CONSTRAINT) + window.navigator.mediaDevices.getUserMedia( + MediaStreamConstraints( + audio = object { + val audioCancellation = false + }.toJsReference() + ) ).then(onFulfilled = { mediaStream -> + println("Got it") + this.mediaStream = mediaStream audioContext = AudioContext() println("AudioContext ready $audioContext.") @@ -40,30 +53,32 @@ class JsAudioSource : AudioSource { val samplesBuffer = FloatArray(jsBuffer.length) { i -> jsBuffer[i] } audioSamplesChannel.trySend( AudioSamples( - Clock.System.now().toEpochMilliseconds(), samplesBuffer, - AudioSamples.ErrorCode.OK, buffer.sampleRate.toInt() + Clock.System.now().toEpochMilliseconds(), + samplesBuffer, + buffer.sampleRate.toInt() ) ) } micNode!!.connect(scriptProcessorNode) scriptProcessorNode.connect(audioContext!!.destination) - micNode!!.connect(scriptProcessorNode); - AudioSource.InitializeErrorCode.INITIALIZE_OK - }, onRejected = { jsError -> - println("Error ${this::class} $jsError \n${jsError.stackTraceToString()}") - AudioSource.InitializeErrorCode.INITIALIZE_NO_MICROPHONE - } - ) + micNode!!.connect(scriptProcessorNode) + mediaStream + }, onRejected = { error -> + println("Error! $error") + error + }) return audioSamplesChannel.consumeAsFlow() } override fun release() { micNode?.disconnect() scriptProcessorNode?.disconnect() - mediaStream?.getTracks()?.forEach { track -> track.stop() } + try { - audioContext?.close()?.catch { + audioContext?.close()?.catch { error -> // ignore + println(error) + error } } catch (ignore: Exception) { // Ignore diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Audio.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Audio.kt new file mode 100644 index 0000000..cdac590 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Audio.kt @@ -0,0 +1,78 @@ +package org.noiseplanet.noisecapture.interop + +import org.khronos.webgl.Float32Array +import org.w3c.dom.mediacapture.MediaStream +import kotlin.js.Promise + +/** + * AudioContext Kotlin interop. + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext) + */ +external class AudioContext { + + val destination: AudioDestinationNode + + fun close(): Promise<*> + fun createMediaStreamSource(mediaStream: MediaStream): AudioNode + fun createScriptProcessor( + bufferSize: Int, + numberOfInputChannels: Int, + numberOfOutputChannels: Int, + ): ScriptProcessorNode +} + +/** + * AudioDestinationNode Kotlin interop. + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AudioDestinationNode) + */ +external class AudioDestinationNode : AudioNode { + + val maxChannelCount: Int +} + +/** + * AudioBuffer Kotlin interop. + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer) + */ +external class AudioBuffer { + + val sampleRate: Float + val length: Int + val duration: Double + val numberOfChannels: Int + + fun getChannelData(channel: Int): Float32Array +} + +/** + * AudioNode Kotlin interop. + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/AudioNode) + */ +open external class AudioNode { + + fun connect( + destination: AudioNode, + output: Int = definedExternally, + input: Int = definedExternally, + ): AudioNode + + fun disconnect() +} + +/** + * AudioProcessingEvent Kotlin interop. + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/AudioProcessingEvent) + */ +external class AudioProcessingEvent { + + val inputBuffer: AudioBuffer +} + +/** + * ScriptProcessorNode Kotlin interop. + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/ScriptProcessorNode) + */ +external class ScriptProcessorNode : AudioNode { + + var onaudioprocess: (AudioProcessingEvent) -> Unit +} diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Location.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Location.kt new file mode 100644 index 0000000..bf2bd4e --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Location.kt @@ -0,0 +1,162 @@ +package org.noiseplanet.noisecapture.interop + +/** + * The GeolocationCoordinates interface represents the position and altitude of the device on Earth, + * as well as the accuracy with which these properties are calculated. The geographic position + * information is provided in terms of World Geodetic System coordinates (WGS84). + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates). + * + * @property latitude Returns a double representing the position's latitude in decimal degrees. + * @property longitude Returns a double representing the position's longitude in decimal degrees. + * @property accuracy Returns a double representing the accuracy of the latitude and longitude + * properties, expressed in meters. + * @property altitude Returns a double representing the position's altitude in meters, relative to + * nominal sea level. This value can be null if the implementation cannot provide the data. + * @property altitudeAccuracy Returns a double representing the accuracy of the altitude expressed + * in meters. This value can be null if the implementation cannot provide the data. + * @property heading Returns a double representing the direction towards which the device is facing. + * This value, specified in degrees, indicates how far off from heading true north the device is. + * 0 degrees represents true north, and the direction is determined clockwise (which means that east + * is 90 degrees and west is 270 degrees). If speed is 0, heading is NaN. If the device is unable to + * provide heading information, this value is null. + * @property speed Returns a double representing the velocity of the device in meters per second. + * This value can be null. + */ +external class GeolocationCoordinates { + + val latitude: Double + val longitude: Double + val accuracy: Double + val altitude: Double? + val altitudeAccuracy: Double? + val heading: Double? + val speed: Double? +} + +/** + * The GeolocationPosition interface represents the position of the concerned device at a given + * time. The position, represented by a GeolocationCoordinates object, comprehends the 2D position + * of the device, on a spheroid representing the Earth, but also its altitude and its speed. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPosition) + * + * @property coords The [GeolocationCoordinates] object containing the device's current location. + * @property timestamp The time at which the location was retrieved. + */ +external class GeolocationPosition { + + val coords: GeolocationCoordinates + + val timestamp: Double +} + +/** + * Represents an error that occurred while retrieving the geolocation. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError) + * + * @property code The error code. + * @property message A human-readable message describing the error. + */ +external class GeolocationPositionError { + + /** + * The error code. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError/code) + * + * The following values are supported: + * - 1: Permission denied + * - 2: Position unavailable + * - 3: Timeout + */ + val code: Int + + /** + * A human-readable message describing the error. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError/message) + */ + val message: String +} + +/** + * A utility class to describe known geolocation error codes + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError/code) + */ +internal enum class GeolocationPositionErrorCode(val code: Int) { + + PermissionDenied(1), + PositionUnavailable(2), + Timeout(3) +} + +/** + * Maps the internal error code to a more usable enum + */ +internal fun GeolocationPositionError.value(): GeolocationPositionErrorCode = + GeolocationPositionErrorCode.entries.first { it.code == code } + +/** + * GeoLocation API for accessing the browser's location data. + * + * See [Geolocation](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) + */ +external class Geolocation { + + /** + * Returns the current position of the device. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition). + * + * @param success Callback function that is called with the current position. + * @param error Callback function that is called when an error occurs. + * @param options An optional object that provides options for the request. + */ + fun getCurrentPosition( + success: (GeolocationPosition?) -> Unit, + error: (GeolocationPositionError) -> Unit, + options: JsAny, + ) + + /** + * Starts watching the position of the device. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). + * + * @param success Callback function that is called with the current position. + * @param error Callback function that is called when an error occurs. + * @param options An optional object that provides options for the request. + * @return A watch ID that can be used to clear the watch. + */ + fun watchPosition( + success: (GeolocationPosition?) -> Unit, + error: (GeolocationPositionError) -> Unit, + options: JsAny, + ): Int + + /** + * Clear an existing watch operation. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/clearWatch). + * + * @param watchId The ID of the watch operation to clear. + */ + fun clearWatch(watchId: Int) +} + +/** + * Utility function for getCurrentPosition options. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition#options) + */ +@Suppress("UnusedParameter") +fun createGeolocationOptions( + enableHighAccuracy: Boolean = false, + timeout: Double = Double.POSITIVE_INFINITY, + maximumAge: Double = 0.0, +): JsAny = js( + code = "({enableHighAccuracy: enableHighAccuracy, timeout: timeout, maximumAge: maximumAge})" +) diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Navigator.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Navigator.kt new file mode 100644 index 0000000..fd0c2a9 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/interop/Navigator.kt @@ -0,0 +1,20 @@ +package org.noiseplanet.noisecapture.interop + +/** + * Navigator Kotlin interop that adds geolocation support to the default + * w3c.dom implementation provided with kotlin stdlib. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) + */ +abstract external class Navigator : org.w3c.dom.Navigator { + + /** + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/geolocation) + */ + val geolocation: Geolocation? +} + +/** + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator) + */ +external val navigator: Navigator? diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.wasmjs.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.wasmjs.kt new file mode 100644 index 0000000..2ee615b --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/PermissionModule.wasmjs.kt @@ -0,0 +1,17 @@ +package org.noiseplanet.noisecapture.permission + +import org.koin.core.module.Module +import org.koin.core.qualifier.named +import org.koin.dsl.module +import org.noiseplanet.noisecapture.permission.delegate.AudioRecordPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.LocationBackgroundPermissionDelegate +import org.noiseplanet.noisecapture.permission.delegate.PermissionDelegate + +internal actual fun platformPermissionModule(): Module = module { + single(named(Permission.RECORD_AUDIO.name)) { + AudioRecordPermissionDelegate() + } + single(named(Permission.LOCATION_BACKGROUND.name)) { + LocationBackgroundPermissionDelegate() + } +} diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt new file mode 100644 index 0000000..40dfe82 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/AudioRecordPermissionDelegate.kt @@ -0,0 +1,54 @@ +package org.noiseplanet.noisecapture.permission.delegate + +import kotlinx.browser.window +import org.koin.core.logger.Level +import org.koin.mp.KoinPlatformTools +import org.noiseplanet.noisecapture.interop.AudioContext +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.checkPermission +import org.w3c.dom.mediacapture.MediaStreamConstraints + + +class AudioRecordPermissionDelegate : PermissionDelegate { + + private val logger = KoinPlatformTools.defaultLogger(Level.DEBUG) + private var permissionState = PermissionState.NOT_DETERMINED + + override suspend fun getPermissionState(): PermissionState { + if (permissionState == PermissionState.GRANTED) { + return permissionState + } + return checkPermission(Permission.RECORD_AUDIO) + } + + override suspend fun providePermission() { + val audioContext = AudioContext() + window.navigator.mediaDevices.getUserMedia( + MediaStreamConstraints( + video = false.toJsBoolean(), + audio = true.toJsBoolean() + ) + ).then { stream -> + // Try to create an audio stream, this will trigger the audio permissions popup + audioContext.createMediaStreamSource(stream) + permissionState = PermissionState.GRANTED + // Close this stream as we don't need it to stay open + audioContext.close().then { void -> + logger.debug("Closed audio context") + void + } + stream + }.catch { error -> + // If we can't get the audio stream, we consider it's because the user has + // denied microphone access. + permissionState = PermissionState.DENIED + error + } + } + + override fun openSettingPage() { + // TODO: Is there a common way to open browser settings? + // Should we just show an alert/popup inquiring users to manually grant permission? + } +} diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt new file mode 100644 index 0000000..2b444d8 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/delegate/LocationBackgroundPermissionDelegate.kt @@ -0,0 +1,41 @@ +package org.noiseplanet.noisecapture.permission.delegate + +import org.koin.core.logger.Logger +import org.koin.mp.KoinPlatformTools +import org.noiseplanet.noisecapture.interop.createGeolocationOptions +import org.noiseplanet.noisecapture.interop.navigator +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import org.noiseplanet.noisecapture.permission.util.checkPermission + +internal class LocationBackgroundPermissionDelegate( + private val logger: Logger = KoinPlatformTools.defaultLogger(), +) : PermissionDelegate { + + private var permissionSate = PermissionState.NOT_DETERMINED + + override suspend fun getPermissionState(): PermissionState { + if (permissionSate != PermissionState.NOT_DETERMINED) { + return permissionSate + } + return checkPermission(Permission.LOCATION_BACKGROUND) + } + + override suspend fun providePermission() { + navigator?.geolocation?.getCurrentPosition( + success = { pos -> + logger.debug("Geolocation ping: ${pos?.coords}") + permissionSate = PermissionState.GRANTED + }, + error = { err -> + logger.warn("Geolocation ping failed: ${err.message}") + permissionSate = PermissionState.DENIED + }, + options = createGeolocationOptions(enableHighAccuracy = true) + ) + } + + override fun openSettingPage() { + // TODO: Show popup? + } +} diff --git a/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/util/JsPermissionUtils.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/util/JsPermissionUtils.kt new file mode 100644 index 0000000..3dedce1 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/permission/util/JsPermissionUtils.kt @@ -0,0 +1,71 @@ +package org.noiseplanet.noisecapture.permission.util + +import kotlinx.coroutines.await +import org.noiseplanet.noisecapture.permission.Permission +import org.noiseplanet.noisecapture.permission.PermissionState +import kotlin.js.Promise +import kotlin.reflect.KClass + +/** + * Checks the current status of a given permission using the + * [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) + * + * @param permission Target permission + */ +internal suspend fun checkPermission(permission: Permission): PermissionState { + val result = permissionsAPIQuery(permission.jsName).await() + return PermissionState::class.fromJsState(result) +} + +/** + * Checks the status of the given permission using the permissions API. + * + * If an error is thrown (e.g. when the browser doesn't support checking the given permission), + * we default to "prompt", which is is the equivalent of [PermissionState.NOT_DETERMINED]. + * + * @param permissionName: Permission name + * @return A promise with the result of the query + */ +@JsFun( + code = """ + async (permissionName) => { + let result = await navigator.permissions.query({ name: permissionName }) + .catch((error) => { + console.warn(error) + return { state: "prompt" } + }) + return result.state + } + """ +) +private external fun permissionsAPIQuery(permissionName: JsString): Promise + +/** + * Maps the internal permission enum to values used in + * [Permission API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions) + */ +private val Permission.jsName: JsString + get() { + return when (this) { + Permission.LOCATION_BACKGROUND, Permission.LOCATION_FOREGROUND -> "geolocation" + Permission.RECORD_AUDIO -> "microphone" + Permission.BLUETOOTH -> "bluetooth" + else -> "_unsupported" + }.toJsString() + } + +/** + * Builds an internal [PermissionState] object from a JS + * [PermissionStatus](https://developer.mozilla.org/en-US/docs/Web/API/PermissionStatus) + * + * @param state Raw JS permission status + * @return [PermissionState] equivalent + */ +private fun KClass.fromJsState(state: JsString): PermissionState { + return when (state.toString()) { + "granted" -> PermissionState.GRANTED + "denied" -> PermissionState.DENIED + "prompt" -> PermissionState.NOT_DETERMINED + else -> PermissionState.NOT_IMPLEMENTED + } +} diff --git a/shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.js.kt b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.wasmjs.kt similarity index 85% rename from shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.js.kt rename to composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.wasmjs.kt index 5bfbe8b..5bd9960 100644 --- a/shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.js.kt +++ b/composeApp/src/wasmJsMain/kotlin/org/noiseplanet/noisecapture/util/ByteArrayToImageBitmap.wasmjs.kt @@ -1,4 +1,4 @@ -package org.noiseplanet.noisecapture +package org.noiseplanet.noisecapture.util import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap diff --git a/composeApp/src/wasmJsMain/resources/index.html b/composeApp/src/wasmJsMain/resources/index.html new file mode 100755 index 0000000..0be283f --- /dev/null +++ b/composeApp/src/wasmJsMain/resources/index.html @@ -0,0 +1,12 @@ + + + + + + KotlinProject + + + + + + \ No newline at end of file diff --git a/composeApp/src/wasmJsMain/resources/styles.css b/composeApp/src/wasmJsMain/resources/styles.css new file mode 100755 index 0000000..0549b10 --- /dev/null +++ b/composeApp/src/wasmJsMain/resources/styles.css @@ -0,0 +1,7 @@ +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; +} \ No newline at end of file diff --git a/composeApp/src/wasmJsTest/kotlin/IgnoreUtil.wasmjs.kt b/composeApp/src/wasmJsTest/kotlin/IgnoreUtil.wasmjs.kt new file mode 100644 index 0000000..ae4e8f1 --- /dev/null +++ b/composeApp/src/wasmJsTest/kotlin/IgnoreUtil.wasmjs.kt @@ -0,0 +1,11 @@ +/** + * Don't ignore + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreIos + +/** + * Don't ignore + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreAndroid diff --git a/config/detekt.yml b/config/detekt.yml index afe69c7..883a946 100644 --- a/config/detekt.yml +++ b/config/detekt.yml @@ -118,6 +118,8 @@ complexity: LongMethod: active: true threshold: 60 + ignoreAnnotated: + - "Composable" LongParameterList: active: true functionThreshold: 6 @@ -338,6 +340,7 @@ naming: MatchingDeclarationName: active: true mustBeFirst: true + excludes: [ '**/**.ios.kt', '**/**.android.kt', '**/**.wasmjs.kt' ] MemberNameEqualsClassName: active: true ignoreOverridden: true @@ -381,7 +384,7 @@ performance: active: true excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] SpreadOperator: - active: true + active: false excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] UnnecessaryPartOfBinaryExpression: active: false @@ -748,4 +751,4 @@ style: WildcardImport: active: true excludeImports: - - 'java.util.*' \ No newline at end of file + - 'java.util.*' diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6059dda..3d3a9aa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,59 +1,54 @@ # keep sorted please, use Edit -> Sort Lines [versions] -agp = "8.1.1" -android-compile-sdk = "34" -android-min-sdk = "23" -android-target-sdk = "32" -androidDriver = "2.0.1" -androidx-activity-compose = "1.8.2" -androidx-lifecycle = "2.7.0" -androidx-test-espresso-core = "3.5.1" -androidx-test-junit = "1.1.5" -appcompat = "1.6.1" -appyx = "2.0.1" -compose-compiler = "1.5.14" +agp = "8.2.0" +android-compileSdk = "34" +android-minSdk = "24" +android-targetSdk = "34" +androidx-activityCompose = "1.9.0" +androidx-appcompat = "1.7.0" +androidx-constraintlayout = "2.1.4" +androidx-core-ktx = "1.13.1" +androidx-espresso-core = "3.6.0" +androidx-lifecycle-viewmodel = "2.8.3" +androidx-material = "1.12.0" +androidx-navigation = "2.7.0-alpha07" +androidx-test-junit = "1.2.0" compose-plugin = "1.6.11" -compose-ui-test-junit4 = "1.6.2" -core-ktx = "1.12.0" coroutines = "1.8.0" detekt = "1.23.6" junit = "4.13.2" -jvm-target = "1.8" -koin-android = "3.5.6" -koin-core = "3.5.6" -kotlin = "1.9.24" -kotlinWrappersVersion = "1.0.0-pre.700" -kotxdatetime = "0.5.0" -ksp = "1.9.24-1.0.20" -sqliteFramework = "2.4.0" +koin = "3.6.0-Beta4" +koin-compose-multiplatform = "1.2.0-Beta4" +kotlin = "2.0.0" +kotlin-wrappers = "1.0.0-pre.775" +kotlinx-datetime = "0.5.0" [libraries] -androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } -androidx-core = { module = "androidx.core:core-ktx", version.ref = "core-ktx" } -androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } -androidx-sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqliteFramework" } -androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso-core" } -androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-junit" } -appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } -appyx-components-backstack = { module = "com.bumble.appyx:backstack", version.ref = "appyx" } -appyx-navigation = { module = "com.bumble.appyx:appyx-navigation", version.ref = "appyx" } -appyx-processor = { module = "com.bumble.appyx:appyx-processor", version.ref = "appyx" } -compose-material = { module = "androidx.compose.material:material" } -compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-ui-test-junit4" } -compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } -compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } -compose-ui-ui = { module = "androidx.compose.ui:ui" } -detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting" } -junit = { module = "junit:junit", version.ref = "junit" } -koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin-android" } -koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin-core" } -kotlin-browser = { module = "org.jetbrains.kotlin-wrappers:kotlin-browser", version.ref = "kotlinWrappersVersion" } -kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" } +androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle-viewmodel" } +androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" } +androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } +androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-compose-multiplatform" } +koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +kotlin-browser = { module = "org.jetbrains.kotlin-wrappers:kotlin-browser", version.ref = "kotlin-wrappers" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } -kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotxdatetime" } -sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "androidDriver" } -sqldelight-driver = { module = "app.cash.sqldelight:web-worker-driver", version.ref = "androidDriver" } -sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "androidDriver" } - +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } +androidLibrary = { id = "com.android.library", version.ref = "agp" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/ios/build.gradle.kts b/ios/build.gradle.kts deleted file mode 100644 index 0e0341a..0000000 --- a/ios/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - kotlin("multiplatform") - kotlin("native.cocoapods") - id("org.jetbrains.compose") - id("com.google.devtools.ksp") -} - -kotlin { - - iosX64() - iosArm64() - iosSimulatorArm64() - - cocoapods { - version = "1.0.0" - summary = "appyx-starter-kit iOS module" - homepage = "https://bumble-tech.github.io/appyx/" - ios.deploymentTarget = "16.4" - podfile = project.file("../iosApp/Podfile") - framework { - baseName = "ios" - isStatic = true - } - license = "Apache License, Version 2.0" - authors = "https://github.com/bumble-tech/" - } - - sourceSets { - val iosX64Main by getting - val iosArm64Main by getting - val iosSimulatorArm64Main by getting - val iosMain by creating { - iosX64Main.dependsOn(this) - iosArm64Main.dependsOn(this) - iosSimulatorArm64Main.dependsOn(this) - dependencies { - implementation(project(":shared")) - implementation(libs.appyx.navigation) - api(compose.runtime) - api(compose.foundation) - api(compose.material) - implementation(libs.kotlin.coroutines.core) - } - } - } -} - diff --git a/ios/ios.podspec b/ios/ios.podspec deleted file mode 100644 index 62e0728..0000000 --- a/ios/ios.podspec +++ /dev/null @@ -1,50 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'ios' - spec.version = '1.0.0' - spec.homepage = 'https://bumble-tech.github.io/appyx/' - spec.source = { :http=> ''} - spec.authors = 'https://github.com/bumble-tech/' - spec.license = 'Apache License, Version 2.0' - spec.summary = 'appyx-starter-kit iOS module' - spec.vendored_frameworks = 'build/cocoapods/framework/ios.framework' - spec.libraries = 'c++' - spec.ios.deployment_target = '16.4' - - - if !Dir.exist?('build/cocoapods/framework/ios.framework') || Dir.empty?('build/cocoapods/framework/ios.framework') - raise " - - Kotlin framework 'ios' doesn't exist yet, so a proper Xcode project can't be generated. - 'pod install' should be executed after running ':generateDummyFramework' Gradle task: - - ./gradlew :ios:generateDummyFramework - - Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" - end - - spec.pod_target_xcconfig = { - 'KOTLIN_PROJECT_PATH' => ':ios', - 'PRODUCT_MODULE_NAME' => 'ios', - } - - spec.script_phases = [ - { - :name => 'Build ios', - :execution_position => :before_compile, - :shell_path => '/bin/sh', - :script => <<-SCRIPT - if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then - echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" - exit 0 - fi - set -ev - REPO_ROOT="$PODS_TARGET_SRCROOT" - "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ - -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ - -Pkotlin.native.cocoapods.archs="$ARCHS" \ - -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" - SCRIPT - } - ] - spec.resources = ['build/compose/cocoapods/compose-resources'] -end \ No newline at end of file diff --git a/ios/src/iosMain/kotlin/main.ios.kt b/ios/src/iosMain/kotlin/main.ios.kt deleted file mode 100644 index 5ed7669..0000000 --- a/ios/src/iosMain/kotlin/main.ios.kt +++ /dev/null @@ -1,43 +0,0 @@ -import androidx.compose.foundation.background -import androidx.compose.material.Scaffold -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.window.ComposeUIViewController -import com.bumble.appyx.navigation.integration.IosNodeHost -import com.bumble.appyx.navigation.integration.MainIntegrationPoint -import org.noiseplanet.noisecapture.shared.root.RootNode -import org.noiseplanet.noisecapture.shared.ui.theme.AppyxStarterKitTheme -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.receiveAsFlow -import org.noiseplanet.noisecapture.shared.initKoin - -val backEvents: Channel = Channel() - -private val integrationPoint = MainIntegrationPoint() - -@Suppress("FunctionNaming", "Unused") -fun MainViewController() = ComposeUIViewController { - - val koinApplication = initKoin() - - AppyxStarterKitTheme { - Scaffold( - modifier = Modifier - .background(Color.Black) - ) { - IosNodeHost( - modifier = Modifier, - onBackPressedEvents = backEvents.receiveAsFlow(), - integrationPoint = remember { integrationPoint } - ) { buildContext -> - RootNode( - nodeContext = buildContext, - koin = koinApplication.koin - ) - } - } - } -}.also { uiViewController -> - integrationPoint.setViewController(uiViewController) -} diff --git a/iosApp/Podfile b/iosApp/Podfile deleted file mode 100644 index 4491def..0000000 --- a/iosApp/Podfile +++ /dev/null @@ -1,5 +0,0 @@ -target 'iosApp' do - use_frameworks! - platform :ios, '14.1' - pod 'ios', :path => '../ios' -end \ No newline at end of file diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock deleted file mode 100644 index 57aa520..0000000 --- a/iosApp/Podfile.lock +++ /dev/null @@ -1,16 +0,0 @@ -PODS: - - ios (1.0.0) - -DEPENDENCIES: - - ios (from `../ios`) - -EXTERNAL SOURCES: - ios: - :path: "../ios" - -SPEC CHECKSUMS: - ios: 989e584c71d4e679af44b91c48d4d976d44cbf4e - -PODFILE CHECKSUM: a1e557981a2880940a401c85ea35aafdd5895941 - -COCOAPODS: 1.15.2 diff --git a/iosApp/Pods/Local Podspecs/ios.podspec.json b/iosApp/Pods/Local Podspecs/ios.podspec.json deleted file mode 100644 index d6e8a25..0000000 --- a/iosApp/Pods/Local Podspecs/ios.podspec.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "ios", - "version": "1.0.0", - "homepage": "https://bumble-tech.github.io/appyx/", - "source": { - "http": "" - }, - "authors": "https://github.com/bumble-tech/", - "license": "Apache License, Version 2.0", - "summary": "appyx-starter-kit iOS module", - "vendored_frameworks": "build/cocoapods/framework/ios.framework", - "libraries": "c++", - "platforms": { - "ios": "16.4" - }, - "pod_target_xcconfig": { - "KOTLIN_PROJECT_PATH": ":ios", - "PRODUCT_MODULE_NAME": "ios" - }, - "script_phases": [ - { - "name": "Build ios", - "execution_position": "before_compile", - "shell_path": "/bin/sh", - "script": " if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"\"\n exit 0\n fi\n set -ev\n REPO_ROOT=\"$PODS_TARGET_SRCROOT\"\n \"$REPO_ROOT/../gradlew\" -p \"$REPO_ROOT\" $KOTLIN_PROJECT_PATH:syncFramework -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME -Pkotlin.native.cocoapods.archs=\"$ARCHS\" -Pkotlin.native.cocoapods.configuration=\"$CONFIGURATION\"\n" - } - ], - "resources": [ - "build/compose/cocoapods/compose-resources" - ] -} diff --git a/iosApp/Pods/Manifest.lock b/iosApp/Pods/Manifest.lock deleted file mode 100644 index 57aa520..0000000 --- a/iosApp/Pods/Manifest.lock +++ /dev/null @@ -1,16 +0,0 @@ -PODS: - - ios (1.0.0) - -DEPENDENCIES: - - ios (from `../ios`) - -EXTERNAL SOURCES: - ios: - :path: "../ios" - -SPEC CHECKSUMS: - ios: 989e584c71d4e679af44b91c48d4d976d44cbf4e - -PODFILE CHECKSUM: a1e557981a2880940a401c85ea35aafdd5895941 - -COCOAPODS: 1.15.2 diff --git a/iosApp/Pods/Pods.xcodeproj/project.pbxproj b/iosApp/Pods/Pods.xcodeproj/project.pbxproj deleted file mode 100644 index c599783..0000000 --- a/iosApp/Pods/Pods.xcodeproj/project.pbxproj +++ /dev/null @@ -1,552 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - F125396ABB1E53E8400D50443EFB33D7 /* ios */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 081812C0037B6C46CE9FA2F3269E0A14 /* Build configuration list for PBXAggregateTarget "ios" */; - buildPhases = ( - 240DB3EE365BA4FB99472EF63F00676C /* [CP-User] Build ios */, - ); - dependencies = ( - ); - name = ios; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 57137D520FA6277DDBDF620FBFB5A76F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */; }; - 7049223602FDDADB57DC30A80E16A4BE /* Pods-iosApp-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BC3BD8CAFAE0C8EB92CD04E5FC24E61 /* Pods-iosApp-dummy.m */; }; - B3EE1858975466FF99AC14F2530150C7 /* Pods-iosApp-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 70E8DFC7821955063C886C71258CBE53 /* Pods-iosApp-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - E98772C79D78640B1113371D5B268E5C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F125396ABB1E53E8400D50443EFB33D7; - remoteInfo = ios; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 0334DBCCB349C0A2403C9E7619B89C59 /* ios.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ios.framework; path = build/cocoapods/framework/ios.framework; sourceTree = ""; }; - 257390D34074D2442461A69FE6970CBD /* Pods-iosApp-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-iosApp-resources.sh"; sourceTree = ""; }; - 2BEE9D7748BD44CA254A86734145E66C /* ios.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = ios.debug.xcconfig; sourceTree = ""; }; - 4D3E6DCB9CAB65A8A05C467E2BBC1F0D /* Pods-iosApp-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-iosApp-acknowledgements.markdown"; sourceTree = ""; }; - 6A3C5EB0586A09C512019B6B6A2DE103 /* Pods-iosApp-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-Info.plist"; sourceTree = ""; }; - 70E8DFC7821955063C886C71258CBE53 /* Pods-iosApp-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-iosApp-umbrella.h"; sourceTree = ""; }; - 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - 848B8383BABDB3A9CF9B9D5B1D828E55 /* ios.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = ios.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9155659023F6A5435DDD348FDD859885 /* compose-resources */ = {isa = PBXFileReference; includeInIndex = 1; name = "compose-resources"; path = "build/compose/cocoapods/compose-resources"; sourceTree = ""; }; - 9BC3BD8CAFAE0C8EB92CD04E5FC24E61 /* Pods-iosApp-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-iosApp-dummy.m"; sourceTree = ""; }; - 9C49AEBC7AA7C80C03295804C6F07963 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.release.xcconfig"; sourceTree = ""; }; - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - B097DD7534E741D5C41838011D755842 /* Pods-iosApp */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-iosApp"; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CD127C9F4B8B4BE405BDB8044FA76219 /* ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = ios.release.xcconfig; sourceTree = ""; }; - F6DF6FB4000E345BDEE186C956C36ABF /* Pods-iosApp-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-acknowledgements.plist"; sourceTree = ""; }; - F981EE0C95E2DFD40CA16F05D2C35B8A /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; - FB978CA3A69A4DEF4DC035E9CD8D83A4 /* Pods-iosApp.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-iosApp.modulemap"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - B02B4649BE4888C7416DADF9276D25DE /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 57137D520FA6277DDBDF620FBFB5A76F /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 11C970DEAE48C6D0282DFE54684F53F1 /* Targets Support Files */ = { - isa = PBXGroup; - children = ( - 4C16E8CC03E90AF9CABF8C82B813AE97 /* Pods-iosApp */, - ); - name = "Targets Support Files"; - sourceTree = ""; - }; - 1F86AA6785DF34AFD5A71790761717DE /* Products */ = { - isa = PBXGroup; - children = ( - B097DD7534E741D5C41838011D755842 /* Pods-iosApp */, - ); - name = Products; - sourceTree = ""; - }; - 3F9609F553D6DD82C072636FFA92AAD0 /* Pod */ = { - isa = PBXGroup; - children = ( - 848B8383BABDB3A9CF9B9D5B1D828E55 /* ios.podspec */, - ); - name = Pod; - sourceTree = ""; - }; - 4C16E8CC03E90AF9CABF8C82B813AE97 /* Pods-iosApp */ = { - isa = PBXGroup; - children = ( - FB978CA3A69A4DEF4DC035E9CD8D83A4 /* Pods-iosApp.modulemap */, - 4D3E6DCB9CAB65A8A05C467E2BBC1F0D /* Pods-iosApp-acknowledgements.markdown */, - F6DF6FB4000E345BDEE186C956C36ABF /* Pods-iosApp-acknowledgements.plist */, - 9BC3BD8CAFAE0C8EB92CD04E5FC24E61 /* Pods-iosApp-dummy.m */, - 6A3C5EB0586A09C512019B6B6A2DE103 /* Pods-iosApp-Info.plist */, - 257390D34074D2442461A69FE6970CBD /* Pods-iosApp-resources.sh */, - 70E8DFC7821955063C886C71258CBE53 /* Pods-iosApp-umbrella.h */, - F981EE0C95E2DFD40CA16F05D2C35B8A /* Pods-iosApp.debug.xcconfig */, - 9C49AEBC7AA7C80C03295804C6F07963 /* Pods-iosApp.release.xcconfig */, - ); - name = "Pods-iosApp"; - path = "Target Support Files/Pods-iosApp"; - sourceTree = ""; - }; - 54C1757960E0140F160B77B4BF72096B /* Support Files */ = { - isa = PBXGroup; - children = ( - 2BEE9D7748BD44CA254A86734145E66C /* ios.debug.xcconfig */, - CD127C9F4B8B4BE405BDB8044FA76219 /* ios.release.xcconfig */, - ); - name = "Support Files"; - path = "../iosApp/Pods/Target Support Files/ios"; - sourceTree = ""; - }; - 578452D2E740E91742655AC8F1636D1F /* iOS */ = { - isa = PBXGroup; - children = ( - 73010CC983E3809BECEE5348DA1BB8C6 /* Foundation.framework */, - ); - name = iOS; - sourceTree = ""; - }; - 7A24CA55188AFCD1F4D92ABFCE85E192 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 0334DBCCB349C0A2403C9E7619B89C59 /* ios.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 865486FD52341F4C38DB28B984E2A7DB /* ios */ = { - isa = PBXGroup; - children = ( - 9155659023F6A5435DDD348FDD859885 /* compose-resources */, - 7A24CA55188AFCD1F4D92ABFCE85E192 /* Frameworks */, - 3F9609F553D6DD82C072636FFA92AAD0 /* Pod */, - 54C1757960E0140F160B77B4BF72096B /* Support Files */, - ); - name = ios; - path = ../../ios; - sourceTree = ""; - }; - A889B63F5EBDF40B8CF0F91F59B42907 /* Development Pods */ = { - isa = PBXGroup; - children = ( - 865486FD52341F4C38DB28B984E2A7DB /* ios */, - ); - name = "Development Pods"; - sourceTree = ""; - }; - CF1408CF629C7361332E53B88F7BD30C = { - isa = PBXGroup; - children = ( - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - A889B63F5EBDF40B8CF0F91F59B42907 /* Development Pods */, - D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, - 1F86AA6785DF34AFD5A71790761717DE /* Products */, - 11C970DEAE48C6D0282DFE54684F53F1 /* Targets Support Files */, - ); - sourceTree = ""; - }; - D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 578452D2E740E91742655AC8F1636D1F /* iOS */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 70C78D66E03EF8CE3890C51C595A09A7 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - B3EE1858975466FF99AC14F2530150C7 /* Pods-iosApp-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - ED39C638569286489CD697A6C8964146 /* Pods-iosApp */ = { - isa = PBXNativeTarget; - buildConfigurationList = F708685E1A73531D4216A711971DE7F7 /* Build configuration list for PBXNativeTarget "Pods-iosApp" */; - buildPhases = ( - 70C78D66E03EF8CE3890C51C595A09A7 /* Headers */, - A2A6F45F96A642035D391F4AD5B29223 /* Sources */, - B02B4649BE4888C7416DADF9276D25DE /* Frameworks */, - A146B4C29BAAB769C10FFE4771EC9BD7 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 6A2BE3A6B20CA3256CD7A231E4FE24DA /* PBXTargetDependency */, - ); - name = "Pods-iosApp"; - productName = Pods_iosApp; - productReference = B097DD7534E741D5C41838011D755842 /* Pods-iosApp */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - BFDFE7DC352907FC980B868725387E98 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1500; - LastUpgradeCheck = 1500; - }; - buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; - compatibilityVersion = "Xcode 12.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = CF1408CF629C7361332E53B88F7BD30C; - productRefGroup = 1F86AA6785DF34AFD5A71790761717DE /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - F125396ABB1E53E8400D50443EFB33D7 /* ios */, - ED39C638569286489CD697A6C8964146 /* Pods-iosApp */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - A146B4C29BAAB769C10FFE4771EC9BD7 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 240DB3EE365BA4FB99472EF63F00676C /* [CP-User] Build ios */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - name = "[CP-User] Build ios"; - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = " if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"\"\n exit 0\n fi\n set -ev\n REPO_ROOT=\"$PODS_TARGET_SRCROOT\"\n \"$REPO_ROOT/../gradlew\" -p \"$REPO_ROOT\" $KOTLIN_PROJECT_PATH:syncFramework -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME -Pkotlin.native.cocoapods.archs=\"$ARCHS\" -Pkotlin.native.cocoapods.configuration=\"$CONFIGURATION\"\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - A2A6F45F96A642035D391F4AD5B29223 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7049223602FDDADB57DC30A80E16A4BE /* Pods-iosApp-dummy.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 6A2BE3A6B20CA3256CD7A231E4FE24DA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = ios; - target = F125396ABB1E53E8400D50443EFB33D7 /* ios */; - targetProxy = E98772C79D78640B1113371D5B268E5C /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 131542B6526CC5954CF4E1E6F3328DF6 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 2BEE9D7748BD44CA254A86734145E66C /* ios.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_OBJC_WEAK = NO; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 513C9FEDD2FECF3953398377A2112CE9 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F981EE0C95E2DFD40CA16F05D2C35B8A /* Pods-iosApp.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 593F10BFFA94DAC7D6E17FB8A7F32D72 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_RELEASE=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_INSTALLED_PRODUCT = NO; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - SYMROOT = "${SRCROOT}/../build"; - }; - name = Release; - }; - 76241D61C3D3675E24353BA10C7E8961 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = CD127C9F4B8B4BE405BDB8044FA76219 /* ios.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_OBJC_WEAK = NO; - IPHONEOS_DEPLOYMENT_TARGET = 16.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 9929C83177069A0164957040B91E7042 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9C49AEBC7AA7C80C03295804C6F07963 /* Pods-iosApp.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - A0374B8CF9A7D6A45F6D116D698D1C19 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_DEBUG=1", - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_INSTALLED_PRODUCT = NO; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - SYMROOT = "${SRCROOT}/../build"; - }; - name = Debug; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 081812C0037B6C46CE9FA2F3269E0A14 /* Build configuration list for PBXAggregateTarget "ios" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 131542B6526CC5954CF4E1E6F3328DF6 /* Debug */, - 76241D61C3D3675E24353BA10C7E8961 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A0374B8CF9A7D6A45F6D116D698D1C19 /* Debug */, - 593F10BFFA94DAC7D6E17FB8A7F32D72 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F708685E1A73531D4216A711971DE7F7 /* Build configuration list for PBXNativeTarget "Pods-iosApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 513C9FEDD2FECF3953398377A2112CE9 /* Debug */, - 9929C83177069A0164957040B91E7042 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; -} diff --git a/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/Pods-iosApp.xcscheme b/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/Pods-iosApp.xcscheme deleted file mode 100644 index 0673282..0000000 --- a/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/Pods-iosApp.xcscheme +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/ios.xcscheme b/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/ios.xcscheme deleted file mode 100644 index 71bd5d4..0000000 --- a/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/ios.xcscheme +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index bb9b6d1..0000000 --- a/iosApp/Pods/Pods.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,21 +0,0 @@ - - - - - SchemeUserState - - Pods-iosApp.xcscheme - - isShown - - - ios.xcscheme - - isShown - - - - SuppressBuildableAutocreation - - - diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist deleted file mode 100644 index 19cf209..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - ${PODS_DEVELOPMENT_LANGUAGE} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.markdown b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.markdown deleted file mode 100644 index 102af75..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.markdown +++ /dev/null @@ -1,3 +0,0 @@ -# Acknowledgements -This application makes use of the following third party libraries: -Generated by CocoaPods - https://cocoapods.org diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.plist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.plist deleted file mode 100644 index 7acbad1..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.plist +++ /dev/null @@ -1,29 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-dummy.m b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-dummy.m deleted file mode 100644 index e1bcef4..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_Pods_iosApp : NSObject -@end -@implementation PodsDummy_Pods_iosApp -@end diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-input-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-input-files.xcfilelist deleted file mode 100644 index 48c2168..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-input-files.xcfilelist +++ /dev/null @@ -1,2 +0,0 @@ -${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh -${PODS_ROOT}/../../ios/build/compose/cocoapods/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-output-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-output-files.xcfilelist deleted file mode 100644 index 383ba86..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Debug-output-files.xcfilelist +++ /dev/null @@ -1 +0,0 @@ -${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-input-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-input-files.xcfilelist deleted file mode 100644 index 48c2168..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-input-files.xcfilelist +++ /dev/null @@ -1,2 +0,0 @@ -${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh -${PODS_ROOT}/../../ios/build/compose/cocoapods/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-output-files.xcfilelist b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-output-files.xcfilelist deleted file mode 100644 index 383ba86..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources-Release-output-files.xcfilelist +++ /dev/null @@ -1 +0,0 @@ -${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/compose-resources \ No newline at end of file diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh deleted file mode 100755 index 5e3a48b..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -function on_error { - echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" -} -trap 'on_error $LINENO' ERR - -if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then - # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy - # resources to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - -RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt -> "$RESOURCES_TO_COPY" - -XCASSET_FILES=() - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -case "${TARGETED_DEVICE_FAMILY:-}" in - 1,2) - TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" - ;; - 1) - TARGET_DEVICE_ARGS="--target-device iphone" - ;; - 2) - TARGET_DEVICE_ARGS="--target-device ipad" - ;; - 3) - TARGET_DEVICE_ARGS="--target-device tv" - ;; - 4) - TARGET_DEVICE_ARGS="--target-device watch" - ;; - *) - TARGET_DEVICE_ARGS="--target-device mac" - ;; -esac - -install_resource() -{ - if [[ "$1" = /* ]] ; then - RESOURCE_PATH="$1" - else - RESOURCE_PATH="${PODS_ROOT}/$1" - fi - if [[ ! -e "$RESOURCE_PATH" ]] ; then - cat << EOM -error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. -EOM - exit 1 - fi - case $RESOURCE_PATH in - *.storyboard) - echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true - ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} - ;; - *.xib) - echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true - ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} - ;; - *.framework) - echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true - mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - ;; - *.xcdatamodel) - echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true - xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" - ;; - *.xcdatamodeld) - echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true - xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" - ;; - *.xcmappingmodel) - echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true - xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" - ;; - *.xcassets) - ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" - XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") - ;; - *) - echo "$RESOURCE_PATH" || true - echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" - ;; - esac -} -if [[ "$CONFIGURATION" == "Debug" ]]; then - install_resource "${PODS_ROOT}/../../ios/build/compose/cocoapods/compose-resources" -fi -if [[ "$CONFIGURATION" == "Release" ]]; then - install_resource "${PODS_ROOT}/../../ios/build/compose/cocoapods/compose-resources" -fi - -mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then - mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" -fi -rm -f "$RESOURCES_TO_COPY" - -if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] -then - # Find all other xcassets (this unfortunately includes those of path pods and other targets). - OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d) - while read line; do - if [[ $line != "${PODS_ROOT}*" ]]; then - XCASSET_FILES+=("$line") - fi - done <<<"$OTHER_XCASSETS" - - if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then - printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - else - printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" - fi -fi diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-umbrella.h b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-umbrella.h deleted file mode 100644 index a3d6034..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-umbrella.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - - -FOUNDATION_EXPORT double Pods_iosAppVersionNumber; -FOUNDATION_EXPORT const unsigned char Pods_iosAppVersionString[]; - diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig deleted file mode 100644 index 1489405..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../ios/build/cocoapods/framework" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -framework "ios" -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.modulemap b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.modulemap deleted file mode 100644 index 1bb57b2..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Pods_iosApp { - umbrella header "Pods-iosApp-umbrella.h" - - export * - module * { export * } -} diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig deleted file mode 100644 index 1489405..0000000 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../ios/build/cocoapods/framework" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -framework "ios" -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/Pods/Target Support Files/ios/ios.debug.xcconfig b/iosApp/Pods/Target Support Files/ios/ios.debug.xcconfig deleted file mode 100644 index bf0aa9d..0000000 --- a/iosApp/Pods/Target Support Files/ios/ios.debug.xcconfig +++ /dev/null @@ -1,16 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ios -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../ios/build/cocoapods/framework" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -KOTLIN_PROJECT_PATH = :ios -OTHER_LDFLAGS = $(inherited) -l"c++" -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../ios -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -PRODUCT_MODULE_NAME = ios -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/Pods/Target Support Files/ios/ios.release.xcconfig b/iosApp/Pods/Target Support Files/ios/ios.release.xcconfig deleted file mode 100644 index bf0aa9d..0000000 --- a/iosApp/Pods/Target Support Files/ios/ios.release.xcconfig +++ /dev/null @@ -1,16 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ios -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../ios/build/cocoapods/framework" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -KOTLIN_PROJECT_PATH = :ios -OTHER_LDFLAGS = $(inherited) -l"c++" -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../ios -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -PRODUCT_MODULE_NAME = ios -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index fd127e6..7474930 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -11,28 +11,23 @@ 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; - CFDB58B53BB94DE262B13C24 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; - 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 7555FF7B242A565900829871 /* My application.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = "My application.app"; path = NoiseCapture.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF7B242A565900829871 /* KotlinProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KotlinProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; - FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - F85CB1118929364A9C6EFABC /* Frameworks */ = { + B92378962B6B1156000C7307 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CFDB58B53BB94DE262B13C24 /* Pods_iosApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -50,7 +45,6 @@ 42799AB246E5F90AF97AA0EF /* Frameworks */ = { isa = PBXGroup; children = ( - 6B1049432C0C2B312090ABF6 /* Pods_iosApp.framework */, ); name = Frameworks; sourceTree = ""; @@ -61,7 +55,6 @@ AB1DB47929225F7C00F7AF9C /* Configuration */, 7555FF7D242A565900829871 /* iosApp */, 7555FF7C242A565900829871 /* Products */, - FEFF387C0A8D172AA4D59CAE /* Pods */, 42799AB246E5F90AF97AA0EF /* Frameworks */, ); sourceTree = ""; @@ -69,7 +62,7 @@ 7555FF7C242A565900829871 /* Products */ = { isa = PBXGroup; children = ( - 7555FF7B242A565900829871 /* My application.app */, + 7555FF7B242A565900829871 /* KotlinProject.app */, ); name = Products; sourceTree = ""; @@ -94,15 +87,6 @@ path = Configuration; sourceTree = ""; }; - FEFF387C0A8D172AA4D59CAE /* Pods */ = { - isa = PBXGroup; - children = ( - 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */, - FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -110,19 +94,20 @@ isa = PBXNativeTarget; buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; buildPhases = ( - 98D614C51D2DA07C614CC46E /* [CP] Check Pods Manifest.lock */, + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */, 7555FF77242A565900829871 /* Sources */, + B92378962B6B1156000C7307 /* Frameworks */, 7555FF79242A565900829871 /* Resources */, - F85CB1118929364A9C6EFABC /* Frameworks */, - 1CCC3E4D0CAF1C50D3FD6609 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = iosApp; + packageProductDependencies = ( + ); productName = iosApp; - productReference = 7555FF7B242A565900829871 /* My application.app */; + productReference = 7555FF7B242A565900829871 /* KotlinProject.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -131,8 +116,9 @@ 7555FF73242A565900829871 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1130; - LastUpgradeCheck = 1130; + LastUpgradeCheck = 1540; ORGANIZATIONNAME = orgName; TargetAttributes = { 7555FF7A242A565900829871 = { @@ -141,7 +127,7 @@ }; }; buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -149,6 +135,8 @@ Base, ); mainGroup = 7555FF72242A565900829871; + packageReferences = ( + ); productRefGroup = 7555FF7C242A565900829871 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -171,24 +159,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1CCC3E4D0CAF1C50D3FD6609 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 98D614C51D2DA07C614CC46E /* [CP] Check Pods Manifest.lock */ = { + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -196,19 +167,15 @@ inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Compile Kotlin Framework"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -263,6 +230,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -277,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -325,6 +293,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -333,7 +302,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -345,7 +314,6 @@ }; 7555FFA6242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4FF3202A603A284706412EDC /* Pods-iosApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; @@ -353,8 +321,12 @@ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_TEAM = "${TEAM_ID}"; ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); INFOPLIST_FILE = iosApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -369,7 +341,6 @@ }; 7555FFA7242A565B00829871 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FF8CA3F5360CEAB49D74065F /* Pods-iosApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; @@ -377,8 +348,12 @@ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_TEAM = "${TEAM_ID}"; ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); INFOPLIST_FILE = iosApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -415,4 +390,4 @@ /* End XCConfigurationList section */ }; rootObject = 7555FF73242A565900829871 /* Project object */; -} +} \ No newline at end of file diff --git a/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/iosApp/iosApp.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/iosApp.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index ca1c48c..0000000 --- a/iosApp/iosApp.xcodeproj/xcuserdata/manuelmartos.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - iosApp.xcscheme_^#shared#^_ - - orderHint - 2 - - - - diff --git a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index c009e7d..0000000 --- a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/iosApp/iosApp.xcworkspace/xcuserdata/manuelmartos.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcworkspace/xcuserdata/manuelmartos.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 9b97e01..0000000 Binary files a/iosApp/iosApp.xcworkspace/xcuserdata/manuelmartos.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json old mode 100644 new mode 100755 diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json old mode 100644 new mode 100755 diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png old mode 100644 new mode 100755 index e84e02b..53fc536 Binary files a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png differ diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json old mode 100644 new mode 100755 index 73c0059..4aa7c53 --- a/iosApp/iosApp/Assets.xcassets/Contents.json +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -3,4 +3,4 @@ "author" : "xcode", "version" : 1 } -} +} \ No newline at end of file diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift old mode 100644 new mode 100755 index 4fff140..f6a0143 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -1,10 +1,10 @@ import UIKit import SwiftUI -import ios +import ComposeApp struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { - Main_iosKt.MainViewController() + MainViewControllerKt.MainViewController() } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} @@ -13,6 +13,6 @@ struct ComposeView: UIViewControllerRepresentable { struct ContentView: View { var body: some View { ComposeView() - .ignoresSafeArea(.all) + .ignoresSafeArea(.all) // Compose has own insets handler } } diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist old mode 100644 new mode 100755 index 412e378..993f9dc --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -2,6 +2,14 @@ + NSLocationAlwaysAndWhenInUseUsageDescription + TODO: Localize this + NSLocationWhenInUseUsageDescription + TODO: Localize this + NSBluetoothAlwaysUsageDescription + TODO: Localize this + NSMicrophoneUsageDescription + TODO: Localize this CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json old mode 100644 new mode 100755 diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift old mode 100644 new mode 100755 index ab9cb11..d83dca6 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -2,12 +2,9 @@ import SwiftUI @main struct iOSApp: App { - var body: some Scene { - WindowGroup { - ZStack { - Color.white.ignoresSafeArea(.all) // status bar color - ContentView() - } - } - } -} + var body: some Scene { + WindowGroup { + ContentView() + } + } +} \ No newline at end of file diff --git a/permissions/README.md b/permissions/README.md deleted file mode 100644 index 986e8b2..0000000 --- a/permissions/README.md +++ /dev/null @@ -1,6 +0,0 @@ -See - -https://github.com/charlee-dev/KMM_Permissions-Medium - -https://medium.com/@adrianwitaszak/kotlin-multiplatform-mobile-permissions-3692fd05fcab - diff --git a/permissions/build.gradle.kts b/permissions/build.gradle.kts deleted file mode 100644 index d1f451f..0000000 --- a/permissions/build.gradle.kts +++ /dev/null @@ -1,66 +0,0 @@ -plugins { - kotlin("multiplatform") - id("com.android.library") -} - -@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) -kotlin { - targetHierarchy.default() - - androidTarget { - compilations.all { - kotlinOptions.jvmTarget = libs.versions.jvm.target.get() - } - } - - listOf( - iosX64(), - iosArm64(), - iosSimulatorArm64() - ).forEach { iosTarget -> - iosTarget.binaries.framework { - baseName = "permissions" - isStatic = true - } - } - - js(IR) { - // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "permissions-common" - browser() - } - - @Suppress("UNUSED_VARIABLE") - sourceSets { - val commonMain by getting { - dependencies { - implementation(libs.kotlin.coroutines.core) - implementation(libs.koin.core) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - } - } - val androidMain by getting { - dependencies { - implementation(libs.appcompat) - implementation(libs.kotlin.coroutines.core) - } - } - val jsMain by getting { - dependencies { - implementation(libs.kotlin.browser) - } - } - } -} - -android { - namespace = "com.adrianwitaszak.kmmpermissions" - compileSdk = 34 - defaultConfig { - minSdk = libs.versions.android.min.sdk.get().toInt() - } -} diff --git a/permissions/src/androidMain/kotlin/AndroidManifest.xml b/permissions/src/androidMain/kotlin/AndroidManifest.xml deleted file mode 100644 index 5c3d365..0000000 --- a/permissions/src/androidMain/kotlin/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PermissionModule.kt b/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PermissionModule.kt deleted file mode 100644 index 9429823..0000000 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PermissionModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions - -import com.adrianwitaszak.kmmpermissions.permissions.service.PermissionsService -import com.adrianwitaszak.kmmpermissions.permissions.service.PermissionsServiceImpl -import org.koin.core.module.Module -import org.koin.dsl.module - -internal expect fun platformModule(): Module - -val permissionsModule: Module = module { - includes(platformModule()) - - single { - PermissionsServiceImpl() - } -} diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/PermissionDelegate.kt b/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/PermissionDelegate.kt deleted file mode 100644 index c3163af..0000000 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/PermissionDelegate.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate - -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState - -internal interface PermissionDelegate { - suspend fun getPermissionState(): PermissionState - suspend fun providePermission() - fun openSettingPage() -} diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/service/PermissionsService.kt b/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/service/PermissionsService.kt deleted file mode 100644 index 76e4619..0000000 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/service/PermissionsService.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions.service - -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import kotlinx.coroutines.flow.Flow - -interface PermissionsService { - - fun checkPermissionFlow(permission: Permission): Flow - suspend fun providePermission(permission: Permission) - fun openSettingPage(permission: Permission) - - companion object { - - const val PERMISSION_CHECK_FLOW_FREQUENCY = 1000L - } -} diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/service/PermissionsServiceImpl.kt b/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/service/PermissionsServiceImpl.kt deleted file mode 100644 index 5782fd6..0000000 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/service/PermissionsServiceImpl.kt +++ /dev/null @@ -1,56 +0,0 @@ -@file:Suppress("TooGenericExceptionCaught") - -package com.adrianwitaszak.kmmpermissions.permissions.service - -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.service.PermissionsService.Companion.PERMISSION_CHECK_FLOW_FREQUENCY -import com.adrianwitaszak.kmmpermissions.permissions.util.getPermissionDelegate -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import org.koin.core.component.KoinComponent -import org.koin.core.logger.Logger -import org.koin.mp.KoinPlatformTools - -internal class PermissionsServiceImpl : PermissionsService, KoinComponent { - private val logger: Logger = KoinPlatformTools.defaultLogger() - - private suspend fun checkPermission(permission: Permission): PermissionState { - return try { - return getPermissionDelegate(permission).getPermissionState() - } catch (e: Exception) { - logger.debug("Failed to check permission $permission \n${e.stackTraceToString()}") - PermissionState.NO_PERMISSION_DELEGATE - } - } - - override fun checkPermissionFlow(permission: Permission): Flow { - return flow { - while (true) { - val permissionState = checkPermission(permission) - emit(permissionState) - delay(PERMISSION_CHECK_FLOW_FREQUENCY) - } - } - } - - override suspend fun providePermission(permission: Permission) { - try { - getPermissionDelegate(permission).providePermission() - } catch (e: Exception) { - logger.error("Failed to request permission $permission") - logger.error(e.stackTraceToString()) - } - } - - override fun openSettingPage(permission: Permission) { - println("Open settings for permission $permission") - try { - getPermissionDelegate(permission).openSettingPage() - } catch (e: Exception) { - logger.error("Failed to open settings for permission $permission") - logger.error(e.stackTraceToString()) - } - } -} diff --git a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.kt b/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.kt deleted file mode 100644 index ee1642a..0000000 --- a/permissions/src/commonMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions.util - -import com.adrianwitaszak.kmmpermissions.permissions.delegate.PermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import org.koin.core.qualifier.named - -internal fun KoinComponent.getPermissionDelegate(permission: Permission): PermissionDelegate { - val permissionDelegate by inject(named(permission.name)) - return permissionDelegate -} diff --git a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PlatformModule.kt b/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PlatformModule.kt deleted file mode 100644 index 77bde21..0000000 --- a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/PlatformModule.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions - -import com.adrianwitaszak.kmmpermissions.permissions.delegate.BluetoothPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.BluetoothServicePermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.LocationBackgroundPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.LocationForegroundPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.LocationServicePermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.PermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import org.koin.core.module.Module -import org.koin.core.qualifier.named -import org.koin.dsl.module - -internal actual fun platformModule(): Module = module { - single(named(Permission.BLUETOOTH_SERVICE_ON.name)) { - BluetoothServicePermissionDelegate() - } - single(named(Permission.BLUETOOTH.name)) { - BluetoothPermissionDelegate() - } - single(named(Permission.LOCATION_SERVICE_ON.name)) { - LocationServicePermissionDelegate() - } - single(named(Permission.LOCATION_FOREGROUND.name)) { - LocationForegroundPermissionDelegate() - } - single(named(Permission.LOCATION_BACKGROUND.name)) { - LocationBackgroundPermissionDelegate( - locationForegroundPermissionDelegate = get(named(Permission.LOCATION_FOREGROUND.name)), - ) - } -} diff --git a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.kt b/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.kt deleted file mode 100644 index f237e23..0000000 --- a/permissions/src/iosMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/util/Extensions.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions.util - -import com.adrianwitaszak.kmmpermissions.permissions.service.PermissionsService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import platform.Foundation.NSURL -import platform.UIKit.UIApplication -import platform.UIKit.UIApplicationOpenSettingsURLString - -fun openNSUrl(string: String) { - val settingsUrl: NSURL = NSURL.URLWithString(string)!! - if (UIApplication.sharedApplication.canOpenURL(settingsUrl)) { - UIApplication.sharedApplication.openURL(settingsUrl) - } else throw CannotOpenSettingsException(string) -} - -internal fun openAppSettingsPage() { - openNSUrl(UIApplicationOpenSettingsURLString) -} - -internal fun CoroutineScope.observePermission( - frequency: Long = PermissionsService.PERMISSION_CHECK_FLOW_FREQUENCY, - block: suspend () -> Unit, -): Job = launch { - while (true) { - block() - delay(frequency) - } -} diff --git a/permissions/src/jsMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/AudioRecordPermissionDelegate.kt b/permissions/src/jsMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/AudioRecordPermissionDelegate.kt deleted file mode 100644 index a2ce610..0000000 --- a/permissions/src/jsMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/delegate/AudioRecordPermissionDelegate.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions.delegate - -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import js.errors.TypeError -import js.promise.await -import web.audio.AudioContext -import web.navigator.navigator - -class AudioRecordPermissionDelegate : PermissionDelegate { - private var overrideState = PermissionState.NOT_DETERMINED - - override suspend fun getPermissionState(): PermissionState { - if (overrideState == PermissionState.GRANTED) { - return overrideState - } - return try { - when ( - navigator.permissions.query(js("{ name: \"microphone\" }")) - .await().state.toString() - ) { - "granted" -> PermissionState.GRANTED - else -> PermissionState.DENIED - } - } catch (ignore: TypeError) { - PermissionState.NOT_DETERMINED - } - } - - override suspend fun providePermission() { - val audioContext = AudioContext() - navigator.mediaDevices.getUserMedia(js("{ audio: true, video: false }")).then { stream -> - audioContext.createMediaStreamSource(stream) - overrideState = PermissionState.GRANTED - } - } - - override fun openSettingPage() { - } -} diff --git a/permissions/src/jsMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/platformModule.kt b/permissions/src/jsMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/platformModule.kt deleted file mode 100644 index 092fb73..0000000 --- a/permissions/src/jsMain/kotlin/com/adrianwitaszak/kmmpermissions/permissions/platformModule.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.adrianwitaszak.kmmpermissions.permissions - -import com.adrianwitaszak.kmmpermissions.permissions.delegate.AudioRecordPermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.delegate.PermissionDelegate -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import org.koin.core.module.Module -import org.koin.core.qualifier.named -import org.koin.dsl.module - -internal actual fun platformModule(): Module = module { - single(named(Permission.RECORD_AUDIO.name)) { - AudioRecordPermissionDelegate() - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 6c8c976..927dd2b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ -pluginManagement { +/*pluginManagement { repositories { google() gradlePluginPortal() @@ -25,13 +25,40 @@ dependencyResolutionManagement { maven("https://repo.kotlin.link") } } - -rootProject.name = "NoiseCapture" - -enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") - include(":androidApp") include(":webApp") include(":ios") include(":shared") -include(":permissions") \ No newline at end of file +include(":permissions")*/ + +rootProject.name = "NoiseCapture" +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +pluginManagement { + repositories { + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositories { + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } + mavenCentral() + } +} + +include(":composeApp") \ No newline at end of file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts deleted file mode 100644 index 5825319..0000000 --- a/shared/build.gradle.kts +++ /dev/null @@ -1,126 +0,0 @@ -plugins { - kotlin("multiplatform") - id("org.jetbrains.compose") - id("com.android.library") - id("com.google.devtools.ksp") - id("kotlin-parcelize") - id("app.cash.sqldelight") version "2.0.1" -} - -tasks.withType { - this.testLogging { - this.showStandardStreams = true - } -} - -kotlin { - androidTarget { - compilations.all { - kotlinOptions.jvmTarget = libs.versions.jvm.target.get() - } - } - - js(IR) { - // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "noisecapture-common" - browser() - } - - listOf( - iosX64(), iosArm64(), iosSimulatorArm64() - ).forEach { iosTarget -> - iosTarget.binaries.framework { - baseName = "shared" - isStatic = true - } - } - - sourceSets { - - val commonMain by getting { - dependencies { - implementation(projects.permissions) - implementation(libs.kotlinx.datetime) - api(compose.runtime) - api(compose.foundation) - implementation(libs.appyx.navigation) - api(libs.appyx.components.backstack) - @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation( - compose.components.resources - ) - implementation(libs.koin.core) - } - } - - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(libs.kotlinx.coroutines.test) - @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) - implementation(compose.uiTest) - - @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation( - compose.components.resources - ) - } - } - val iosX64Main by getting - val iosArm64Main by getting - val iosSimulatorArm64Main by getting - - val iosMain by creating { - dependsOn(commonMain) - iosX64Main.dependsOn(this) - iosArm64Main.dependsOn(this) - iosSimulatorArm64Main.dependsOn(this) - dependencies { - implementation(libs.sqldelight.native.driver) - } - } - val jsMain by getting() { - dependencies { - implementation(libs.kotlin.browser) - implementation(libs.sqldelight.driver) - implementation(npm("sql.js", "1.6.2")) - implementation(devNpm("copy-webpack-plugin", "9.1.0")) - } - } - val androidMain by getting() { - dependencies { - implementation(libs.sqldelight.android.driver) - implementation(libs.androidx.sqlite.framework) - } - } - } -} - -android { - namespace = "org.noiseplanet.noisecapturekmp.shared" - compileSdk = 34 - defaultConfig { - minSdk = libs.versions.android.min.sdk.get().toInt() - } - buildTypes { - getByName("release") { - isMinifyEnabled = true - multiDexEnabled = true - } - } -} - -dependencies { - add("kspCommonMainMetadata", libs.appyx.processor) - add("kspAndroid", libs.appyx.processor) - add("kspJs", libs.appyx.processor) - add("kspIosArm64", libs.appyx.processor) - add("kspIosX64", libs.appyx.processor) - add("kspIosSimulatorArm64", libs.appyx.processor) -} - -sqldelight { - databases { - create("Storage") { - packageName.set("org.noiseplanet.noisecapture") - } - } -} diff --git a/shared/src/androidMain/AndroidManifest.xml b/shared/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 5c3d365..0000000 --- a/shared/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidAudioSource.kt b/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidAudioSource.kt deleted file mode 100644 index f76e34d..0000000 --- a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidAudioSource.kt +++ /dev/null @@ -1,164 +0,0 @@ -package org.noiseplanet.noisecapture - -import android.annotation.SuppressLint -import android.media.AudioFormat -import android.media.AudioRecord -import android.media.AudioRecord.ERROR -import android.media.AudioRecord.ERROR_BAD_VALUE -import android.media.AudioRecord.ERROR_INVALID_OPERATION -import android.media.MediaRecorder -import android.os.Process -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.consumeAsFlow -import org.koin.core.logger.Logger -import java.util.concurrent.atomic.AtomicBoolean - -const val BUFFER_SIZE_TIME = 0.1 - -class AndroidAudioSource(private val logger: Logger) : AudioSource { - - private val recording = AtomicBoolean(false) - - override suspend fun setup(): Flow { - val audioSamplesChannel = - Channel(onBufferOverflow = BufferOverflow.DROP_OLDEST) - val audioThread = AudioThread(recording, audioSamplesChannel, logger) - audioThread.startAudioRecord() - return audioSamplesChannel.consumeAsFlow() - } - - fun readErrorCodeToString(errorCode: Int): String { - return when (errorCode) { - ERROR_INVALID_OPERATION -> "ERROR_INVALID_OPERATION" - ERROR_BAD_VALUE -> "ERROR_BAD_VALUE" - ERROR -> "Other Error" - else -> "Unknown error" - } - } - - override fun release() { - recording.set(false) - } - - override fun getMicrophoneLocation(): AudioSource.MicrophoneLocation { - return AudioSource.MicrophoneLocation.LOCATION_UNKNOWN - } -} - -class AudioThread( - private val recording: AtomicBoolean, - private val audioSamplesChannel: Channel, - private val logger: Logger, -) : Runnable { - - private lateinit var audioRecord: AudioRecord - private var bufferSize = -1 - private var sampleRate = -1 - private var thread: Thread? = null - - @SuppressLint("MissingPermission") - fun startAudioRecord() { - if (this.bufferSize == -1) { - val mSampleRates = intArrayOf(48000, 44100) - val channel = AudioFormat.CHANNEL_IN_MONO - val encoding = AudioFormat.ENCODING_PCM_FLOAT - for (tryRate in mSampleRates) { - this.sampleRate = tryRate - val minimalBufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, encoding) - if (minimalBufferSize == ERROR_BAD_VALUE || minimalBufferSize == ERROR) { - continue - } - this.bufferSize = - Integer.max(minimalBufferSize, (BUFFER_SIZE_TIME * sampleRate * 4).toInt()) - audioRecord = AudioRecord( - MediaRecorder.AudioSource.VOICE_RECOGNITION, - sampleRate, - channel, - encoding, - bufferSize - ) - thread = Thread(this) - thread!!.start() - return - } - } - } - - override fun run() { - recording.set(true) - try { - Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO) - } catch (ignore: IllegalArgumentException) { - // Ignore - } catch (ignore: SecurityException) { - // Ignore - } - try { - audioRecord.startRecording() - logger.debug("Capture microphone") - while (recording.get()) { - if (!processRecord()) { - break - } - } - bufferSize = -1 - audioSamplesChannel.trySend( - AudioSamples( - System.currentTimeMillis(), FloatArray(0), - AudioSamples.ErrorCode.ABORTED, sampleRate - ) - ) - audioRecord.stop() - } catch (e: IllegalStateException) { - logger.error("${e.localizedMessage}\n${e.stackTraceToString()}") - } - recording.set(false) - logger.debug("Release microphone") - } - - private fun processRecord(): Boolean { - var buffer = FloatArray(bufferSize / 4) - val read: Int = audioRecord.read( - buffer, - 0, - buffer.size, - AudioRecord.READ_BLOCKING - ) - if (read < buffer.size) { - if (read > 0) { - buffer = buffer.copyOfRange(0, read) - audioSamplesChannel.trySend( - AudioSamples( - System.currentTimeMillis(), - buffer, - AudioSamples.ErrorCode.OK, - sampleRate - ) - ) - } else { - audioSamplesChannel.trySend( - AudioSamples( - System.currentTimeMillis(), - buffer.clone(), - AudioSamples.ErrorCode.ABORTED, - sampleRate - ) - ) - recording.set(false) - return false - } - } else { - audioSamplesChannel.trySend( - AudioSamples( - System.currentTimeMillis(), - buffer.clone(), - AudioSamples.ErrorCode.OK, - sampleRate - ) - ) - } - return true - } -} diff --git a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidDatabase.kt b/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidDatabase.kt deleted file mode 100644 index ba3653c..0000000 --- a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidDatabase.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.noiseplanet.noisecapture - -import android.content.Context -import androidx.sqlite.db.SupportSQLiteDatabase -import androidx.sqlite.db.SupportSQLiteOpenHelper -import androidx.sqlite.db.SupportSQLiteOpenHelper.Callback -import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.android.AndroidSqliteDriver - -class AndroidDatabase(private val context: Context) : DatabaseDriverFactory { - - override suspend fun provideDbDriver(): SqlDriver { - val callback = object : Callback(DATABASE_VERSION) { - override fun onCreate(db: SupportSQLiteDatabase) { - } - - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { - } - } - return AndroidSqliteDriver( - FrameworkSQLiteOpenHelperFactory().create( - SupportSQLiteOpenHelper.Configuration.builder(context) - .callback(callback) - .name("Storage") - .noBackupDirectory(false) - .build() - ) - ) - } -} diff --git a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidMeasurementService.kt b/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidMeasurementService.kt deleted file mode 100644 index 4c03897..0000000 --- a/shared/src/androidMain/kotlin/org/noiseplanet/noisecapture/AndroidMeasurementService.kt +++ /dev/null @@ -1,112 +0,0 @@ -package org.noiseplanet.noisecapture; - -import android.R -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Intent -import android.graphics.Color -import android.os.Binder -import android.os.Build -import android.os.IBinder -import androidx.annotation.RequiresApi -import androidx.core.app.NotificationCompat - - -/** - * Android service that will not close when app is sent in background - */ -typealias AudioCallBack = (audioSamples: AudioSamples) -> Unit - -class AndroidMeasurementService : Service() { - - private val binder: IBinder = LocalBinder() - private var mNM: NotificationManager? = null - private var notificationInstance: Notification? = null - private var bindingIntent: Intent? = null - - private companion object { - - // Unique Identification Number for the Notification. - // We use it on Notification start, and to cancel it. - const val NOTIFICATION: Int = 1 - } - - inner class LocalBinder : Binder() { - - val service: AndroidMeasurementService - get() = this@AndroidMeasurementService - } - - override fun onCreate() { - super.onCreate() - mNM = getSystemService(NOTIFICATION_SERVICE) as NotificationManager? - } - - override fun onBind(intent: Intent): IBinder { - bindingIntent = intent - // Display a notification about us starting. We put an icon in the status bar. - showNotification(); - return binder - } - - @RequiresApi(Build.VERSION_CODES.O) - private fun createNotificationChannel(channelId: String, channelName: String): String { - return if (Build.VERSION.SDK_INT >= 26) { - val chan = NotificationChannel( - channelId, - channelName, NotificationManager.IMPORTANCE_NONE - ) - chan.lightColor = Color.BLUE - chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC - val service = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - service.createNotificationChannel(chan) - channelId - } else { - "" - } - } - - override fun onDestroy() { - println("Destroy MeasurementService") - // Cancel the persistent notification. - mNM?.cancel(NOTIFICATION) - } - - /** - * Show a notification while this service is running. - */ - private fun showNotification() { - val text = "Measurement service started" - - // The PendingIntent to launch our activity if the user selects this notification - val contentIntent = PendingIntent.getActivity( - this, 0, - bindingIntent, PendingIntent.FLAG_IMMUTABLE - ) - if (notificationInstance == null) { - - var channelId = "" - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - if (Build.VERSION.SDK_INT >= 26) { - channelId = createNotificationChannel("noisecapture", "NoiseCapture measurement"); - } - // Set the info for the views that show in the notification panel. - notificationInstance = NotificationCompat.Builder(this, channelId) - // TODO: Use KMP compose resources - .setSmallIcon(R.drawable.stat_notify_voicemail) // the status icon - .setTicker(text) // the status text - .setWhen(System.currentTimeMillis()) // the time stamp - .setContentTitle("Measurement") // the label of the entry - .setContentText(text) // the contents of the entry - .setContentIntent(contentIntent) // The intent to send when the entry is clicked - .build() - } - - // Send the notification. - mNM!!.notify(NOTIFICATION, notificationInstance) - } -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/AudioSource.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/AudioSource.kt deleted file mode 100644 index dad4faa..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/AudioSource.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.noiseplanet.noisecapture - -import kotlinx.coroutines.flow.Flow - -/** - * Common interface to access Audio samples from device microphone - * As each device - */ -interface AudioSource { - - enum class MicrophoneLocation { - LOCATION_UNKNOWN, - LOCATION_MAIN_BODY, - LOCATION_MAIN_BODY_MOVABLE, - LOCATION_PERIPHERAL - } - - enum class InitializeErrorCode { - INITIALIZE_OK, - INITIALIZE_WRONG_BUFFER_SIZE, - INITIALIZE_SAMPLE_RATE_NOT_SUPPORTED, - INITIALIZE_ALREADY_INITIALIZED, - INITIALIZE_NO_MICROPHONE - } - - /** - * @param sampleRate Sample rate in Hz - * @param bufferSize Buffer size in bytes - * @return InitializeErrorCode instance - */ - suspend fun setup(): Flow - - /** - * Release device and will require to setup again before getting new samples - * Will abort samples flow - */ - fun release() - - fun getMicrophoneLocation(): MicrophoneLocation - -} - -data class AudioSamples( - val epoch: Long, - val samples: FloatArray, - val errorCode: ErrorCode, - val sampleRate: Int, -) { - - enum class ErrorCode { - OK, - ABORTED, - DEVICE_ERROR - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as AudioSamples - - if (epoch != other.epoch) return false - if (!samples.contentEquals(other.samples)) return false - - return true - } - - override fun hashCode(): Int { - var result = epoch.hashCode() - result = 31 * result + samples.contentHashCode() - return result - } -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/DatabaseDriverFactory.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/DatabaseDriverFactory.kt deleted file mode 100644 index fc95c42..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/DatabaseDriverFactory.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.noiseplanet.noisecapture - -import app.cash.sqldelight.db.SqlDriver - -const val DATABASE_VERSION = 1 - -interface DatabaseDriverFactory { - - suspend fun provideDbDriver(): SqlDriver -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.common.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.common.kt deleted file mode 100644 index 51ff2d8..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/ImageBitmap.common.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.noiseplanet.noisecapture - -import androidx.compose.ui.graphics.ImageBitmap - -expect fun ByteArray.toImageBitmap(): ImageBitmap diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/Koin.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/Koin.kt deleted file mode 100644 index 27d254e..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/Koin.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.noiseplanet.noisecapture.shared - -import com.adrianwitaszak.kmmpermissions.permissions.permissionsModule -import org.koin.core.KoinApplication -import org.koin.core.context.startKoin -import org.koin.core.module.Module -import org.koin.dsl.koinApplication -import org.koin.dsl.module - -fun initKoin( - additionalModules: List = emptyList(), -): KoinApplication { - val koinApplication = koinApplication { - modules( - listOf( - module { - includes(additionalModules) - }, - permissionsModule, - ) - ) - createEagerInstances() - } - return startKoin(koinApplication) -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ScreenData.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ScreenData.kt deleted file mode 100644 index 2b3b97e..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ScreenData.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.noiseplanet.noisecapture.shared - -import com.bumble.appyx.utils.multiplatform.Parcelable -import com.bumble.appyx.utils.multiplatform.Parcelize - -sealed class ScreenData(val title: String) : Parcelable { - - @Parcelize - data object HomeTarget : ScreenData("Home") - - @Parcelize - data object PermissionTarget : ScreenData("Permissions") - - @Parcelize - data object MeasurementTarget : ScreenData("Measurement") -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/HomeScreen.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/HomeScreen.kt deleted file mode 100644 index e8e3a48..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/HomeScreen.kt +++ /dev/null @@ -1,122 +0,0 @@ -package org.noiseplanet.noisecapture.shared.child - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.material.Button -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.unit.dp -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.components.backstack.operation.push -import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.LeafNode -import org.noiseplanet.noisecapture.shared.ScreenData -import org.noiseplanet.noisecapture.shared.ui.theme.CenterFocusWeak -import org.noiseplanet.noisecapture.shared.ui.theme.Help -import org.noiseplanet.noisecapture.shared.ui.theme.Info -import org.noiseplanet.noisecapture.shared.ui.theme.Map -import org.noiseplanet.noisecapture.shared.ui.theme.Mic -import org.noiseplanet.noisecapture.shared.ui.theme.Settings -import org.noiseplanet.noisecapture.shared.ui.theme.ShowChart -import org.noiseplanet.noisecapture.shared.ui.theme.clinicalNotes -import org.noiseplanet.noisecapture.shared.ui.theme.overview - -class HomeScreen( - nodeContext: NodeContext, - private val backStack: BackStack, -) : LeafNode(nodeContext) { - - @Composable - private fun MenuItems(): Array { - return arrayOf( - MenuItem( - "Test label", - Mic, - onClick = { backStack.push(ScreenData.PermissionTarget) } - ), - MenuItem( - "Measurement History", - overview(), - onClick = {} - ), - MenuItem( - "Measurement feedback", - clinicalNotes(), - onClick = {} - ), - MenuItem( - "Measurement statistics", - ShowChart, - onClick = {} - ), - MenuItem( - "Last measurement map", - Map, - onClick = {} - ), - MenuItem( - "Help", - Help, - onClick = {} - ), - MenuItem( - "About", - Info, - onClick = {} - ), - MenuItem( - "Calibration", - CenterFocusWeak, - onClick = {} - ), - MenuItem( - "Settings", - Settings, - onClick = {} - ) - ) - } - - @Composable - override fun Content(modifier: Modifier) { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colors.background - ) { - val menuItems = MenuItems() - LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = 96.dp), - contentPadding = PaddingValues( - start = 24.dp, - top = 24.dp, - end = 24.dp, - bottom = 24.dp - ), - content = { - items(menuItems.size) { index -> - Button( - onClick = menuItems[index].onClick, - modifier = Modifier.aspectRatio(1f).padding(12.dp) - ) { - Icon( - imageVector = menuItems[index].imageVector, - menuItems[index].label, - modifier.fillMaxSize() - ) - } - } - } - ) - } - } -} - -data class MenuItem(val label: String, val imageVector: ImageVector, val onClick: () -> Unit) diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/NavigationScreen.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/NavigationScreen.kt deleted file mode 100644 index 70ce2d4..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/NavigationScreen.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.noiseplanet.noisecapture.shared.child - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.components.backstack.operation.pop -import org.noiseplanet.noisecapture.shared.ScreenData - -@Composable -fun NavigationScreen( - backStack: BackStack, - body: @Composable (PaddingValues) -> Unit, -) { - val canBackPress by backStack.canHandleBackPress().collectAsState(false) - val currentTitle by backStack.model.output.collectAsState() - - Scaffold( - topBar = { - TopAppBar( - title = { Text(text = currentTitle.currentTargetState.active.interactionTarget.title) }, - navigationIcon = { - IconButton(onClick = { backStack.pop() }, enabled = canBackPress) { - Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null) - } - } - ) - }, - content = body - ) -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/PermissionScreen.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/PermissionScreen.kt deleted file mode 100644 index bd41805..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/child/PermissionScreen.kt +++ /dev/null @@ -1,200 +0,0 @@ -// TODO: Remove detekt suppression after Appyx refactor -@file:Suppress("detekt:all") - -package org.noiseplanet.noisecapture.shared.child - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.Button -import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import com.adrianwitaszak.kmmpermissions.permissions.model.Permission -import com.adrianwitaszak.kmmpermissions.permissions.model.PermissionState -import com.adrianwitaszak.kmmpermissions.permissions.service.PermissionsService -import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.LeafNode -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch -import org.noiseplanet.noisecapture.shared.ui.theme.Check -import org.noiseplanet.noisecapture.shared.ui.theme.Close -import org.noiseplanet.noisecapture.shared.ui.theme.QuestionMark - -class PermissionScreen( - nodeContext: NodeContext, - private val permissionsService: PermissionsService, - private val onNextClick: () -> Unit, -) : LeafNode(nodeContext) { - - @Composable - override fun Content(modifier: Modifier) { - val scope = rememberCoroutineScope() - - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colors.background - ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(4.dp), - contentPadding = PaddingValues( - top = 16.dp, - bottom = 64.dp, - start = 16.dp, - end = 16.dp - ), - modifier = Modifier.fillMaxSize() - ) { - item { - Column { - Text( - text = "Permissions", - style = MaterialTheme.typography.h4, - color = MaterialTheme.colors.onSurface, - ) - Divider() - Text( - text = "In order to measure the sound level and place it on a map the application need to have access to sensitive sensors. Please tap on Request buttons.", - style = MaterialTheme.typography.body1, - color = MaterialTheme.colors.onSurface, - ) - } - } - val requiredPermissions = arrayOf( - Permission.RECORD_AUDIO, - Permission.LOCATION_SERVICE_ON, - Permission.LOCATION_FOREGROUND, - Permission.LOCATION_BACKGROUND - ) - item { - nextPanelAfterGranted( - requiredPermissions, - onNextClick = onNextClick, - permissionsService - ) - } - items(requiredPermissions) { permission -> - val permissionState by permissionsService.checkPermissionFlow(permission) - .collectAsState(PermissionState.NOT_DETERMINED) - if (permissionState != PermissionState.NO_PERMISSION_DELEGATE) { - permissionItem( - permissionName = permission.name, - permissionState = permissionState, - onRequestClick = { - scope.launch { - permissionsService.providePermission(permission) - } - }, - onOpenSettingsClick = { - permissionsService.openSettingPage(permission) - }, - ) - } - } - } - } - } -} - -@Composable -internal fun nextPanelAfterGranted( - permissions: Array, onNextClick: () -> Unit, - permissionsService: PermissionsService, -) { - val permissionsState by combine(permissions.map { p -> permissionsService.checkPermissionFlow(p) }, - transform = { it.all { p -> p != PermissionState.DENIED } }) - .collectAsState(false) - Column(modifier = Modifier.fillMaxSize()) { - Spacer(modifier = Modifier.fillMaxSize()) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 20.dp), - horizontalArrangement = Arrangement.End, - ) { - Button(onClick = onNextClick, enabled = permissionsState) { - Text("Next") - } - } - } -} - -@Suppress("FunctionName") -@Composable -internal fun permissionItem( - permissionName: String, - permissionState: PermissionState, - onRequestClick: () -> Unit, - onOpenSettingsClick: () -> Unit, -) { - val colorState by animateColorAsState( - when (permissionState) { - PermissionState.GRANTED -> Color.Green - PermissionState.NOT_DETERMINED -> Color.Gray - else -> Color.Red - } - ) - - Column( - modifier = Modifier.fillMaxWidth() - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - text = permissionName, - color = MaterialTheme.colors.onSurface, - modifier = Modifier.weight(1f) - ) - Icon( - imageVector = when (permissionState) { - PermissionState.GRANTED -> Check - PermissionState.NOT_DETERMINED -> QuestionMark - else -> Close - }, - tint = colorState, - contentDescription = null, - modifier = Modifier.padding(horizontal = 8.dp) - ) - Button( - onClick = onOpenSettingsClick, - ) { - Text( - text = "Settings", - color = MaterialTheme.colors.onPrimary, - ) - } - } - AnimatedVisibility(permissionState.notGranted()) { - Button( - onClick = onRequestClick, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = "Request", - color = MaterialTheme.colors.onPrimary, - ) - } - } - } -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/root/RootNode.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/root/RootNode.kt deleted file mode 100644 index 32760d7..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/root/RootNode.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.noiseplanet.noisecapture.shared.root - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.components.backstack.BackStackModel -import com.bumble.appyx.components.backstack.operation.push -import com.bumble.appyx.components.backstack.ui.parallax.BackStackParallax -import com.bumble.appyx.navigation.composable.AppyxNavigationContainer -import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.LeafNode -import org.koin.core.Koin -import org.koin.core.annotation.KoinInternalApi -import org.noiseplanet.noisecapture.shared.ScreenData -import org.noiseplanet.noisecapture.shared.ScreenData.HomeTarget -import org.noiseplanet.noisecapture.shared.ScreenData.PermissionTarget -import org.noiseplanet.noisecapture.shared.child.HomeScreen -import org.noiseplanet.noisecapture.shared.child.MeasurementScreen -import org.noiseplanet.noisecapture.shared.child.NavigationScreen -import org.noiseplanet.noisecapture.shared.child.PermissionScreen - -class RootNode( - nodeContext: NodeContext, - private val backStack: BackStack = BackStack( - model = BackStackModel( - initialTargets = listOf(HomeTarget), - savedStateMap = nodeContext.savedStateMap - ), - visualisation = { BackStackParallax(it) } - ), - val koin: Koin -) : Node( - nodeContext = nodeContext, - appyxComponent = backStack -) { - - @OptIn(KoinInternalApi::class) - override fun buildChildNode(navTarget: ScreenData, nodeContext: NodeContext): LeafNode = - when (navTarget) { - is PermissionTarget -> PermissionScreen( - nodeContext, - koin.get(), - ::onPermissionGrantedBeforeMeasurement - ) - - is HomeTarget -> HomeScreen( - nodeContext, - backStack - ) - - is ScreenData.MeasurementTarget -> MeasurementScreen( - nodeContext, - backStack, - koin.get(), - koin.logger - ) - } - - private fun onPermissionGrantedBeforeMeasurement() { - backStack.push(ScreenData.MeasurementTarget) - } - - @Composable - override fun Content(modifier: Modifier) { - Column( - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.fillMaxSize() - ) { - NavigationScreen(backStack) { - AppyxNavigationContainer( - appyxComponent = backStack, - modifier = Modifier.fillMaxSize() - ) - } - } - } -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/SinglePointPlatformLifeCycleObserver.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/SinglePointPlatformLifeCycleObserver.kt deleted file mode 100644 index ccaa456..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/SinglePointPlatformLifeCycleObserver.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.noiseplanet.noisecapture.shared.ui - -import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver -import com.bumble.appyx.navigation.lifecycle.Lifecycle -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow - - -fun interface SinglePointPlatformLifeCycleObserver : DefaultPlatformLifecycleObserver { - fun onEvent(event: Lifecycle.Event) - - override fun onCreate() { - onEvent(Lifecycle.Event.ON_CREATE) - } - - override fun onDestroy() { - onEvent(Lifecycle.Event.ON_DESTROY) - } - - override fun onPause() { - onEvent(Lifecycle.Event.ON_PAUSE) - } - - override fun onResume() { - onEvent(Lifecycle.Event.ON_RESUME) - } - - override fun onStart() { - onEvent(Lifecycle.Event.ON_START) - } - - override fun onStop() { - onEvent(Lifecycle.Event.ON_STOP) - } - - -} - -fun Lifecycle.asEventFlow(): Flow = - callbackFlow { - val observer = SinglePointPlatformLifeCycleObserver { event -> trySend(event) } - addObserver(observer) - awaitClose { removeObserver(observer) } - } diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Color.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Color.kt deleted file mode 100644 index fc9f739..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Color.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.noiseplanet.noisecapture.shared.ui.theme - -import androidx.compose.ui.graphics.Color - -val purple200 = Color(0xFFBB86FC) -val purple500 = Color(0xFF6200EE) -val purple700 = Color(0xFF3700B3) -val teal200 = Color(0xFF03DAC5) - -val space_cadet = Color(0xFF2B2D42) -val black_coral = Color(0xFF5C6378) -val manatee = Color(0xFF8D99AE) -val silver_sand = Color(0xFFBDC6D1) -val alice_blue = Color(0xFFEDF2F4) -val sizzling_red = Color(0xFFF05D5E) -val imperial_red = Color(0xFFEF233C) -val amaranth_red = Color(0xFFD90429) -val atomic_tangerine = Color(0xFFF0965D) diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/CustomVectors.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/CustomVectors.kt deleted file mode 100644 index 78026ab..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/CustomVectors.kt +++ /dev/null @@ -1,910 +0,0 @@ -@file:Suppress("LongMethod") - -package org.noiseplanet.noisecapture.shared.ui.theme - -import androidx.compose.material.icons.materialIcon -import androidx.compose.material.icons.materialPath -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.PathFillType.Companion.NonZero -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.StrokeCap.Companion.Butt -import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.path -import androidx.compose.ui.unit.dp - -@Composable -fun clinicalNotes(): ImageVector { - return remember { - ImageVector.Builder( - name = "clinical_notes", - defaultWidth = 40.0.dp, - defaultHeight = 40.0.dp, - viewportWidth = 40.0f, - viewportHeight = 40.0f - ).apply { - path( - fill = SolidColor(Color.Black), - fillAlpha = 1f, - stroke = null, - strokeAlpha = 1f, - strokeLineWidth = 1.0f, - strokeLineCap = Butt, - strokeLineJoin = Miter, - strokeLineMiter = 1f, - pathFillType = NonZero - ) { - moveTo(28.125f, 25.958f) - quadToRelative(-2.042f, 0f, -3.479f, -1.437f) - quadToRelative(-1.438f, -1.438f, -1.438f, -3.479f) - quadToRelative(0f, -2.084f, 1.438f, -3.5f) - quadToRelative(1.437f, -1.417f, 3.479f, -1.417f) - quadToRelative(2.083f, 0f, 3.5f, 1.437f) - quadToRelative(1.417f, 1.438f, 1.417f, 3.48f) - quadToRelative(0f, 2.041f, -1.438f, 3.479f) - quadToRelative(-1.437f, 1.437f, -3.479f, 1.437f) - close() - moveToRelative(0f, -2.625f) - quadToRelative(1f, 0f, 1.646f, -0.645f) - quadToRelative(0.646f, -0.646f, 0.646f, -1.646f) - quadToRelative(0f, -1f, -0.667f, -1.646f) - quadToRelative(-0.667f, -0.646f, -1.625f, -0.646f) - quadToRelative(-1f, 0f, -1.646f, 0.667f) - quadToRelative(-0.646f, 0.666f, -0.646f, 1.625f) - quadToRelative(0f, 1f, 0.646f, 1.646f) - quadToRelative(0.646f, 0.645f, 1.646f, 0.645f) - close() - moveTo(19.5f, 37.625f) - quadToRelative(-0.542f, 0f, -0.917f, -0.375f) - reflectiveQuadToRelative(-0.375f, -0.917f) - verticalLineToRelative(-3.458f) - quadToRelative(0f, -0.875f, 0.396f, -1.625f) - reflectiveQuadToRelative(1.146f, -1.208f) - quadToRelative(1.208f, -0.709f, 2.062f, -1.042f) - quadToRelative(0.855f, -0.333f, 2.23f, -0.667f) - quadToRelative(0.416f, -0.041f, 0.833f, 0.021f) - quadToRelative(0.417f, 0.063f, 0.667f, 0.396f) - lineTo(28.125f, 32f) - lineToRelative(2.583f, -3.25f) - quadToRelative(0.25f, -0.333f, 0.667f, -0.396f) - quadToRelative(0.417f, -0.062f, 0.833f, -0.021f) - quadToRelative(1.375f, 0.334f, 2.209f, 0.667f) - quadToRelative(0.833f, 0.333f, 2.041f, 1.042f) - quadToRelative(0.75f, 0.458f, 1.167f, 1.208f) - quadToRelative(0.417f, 0.75f, 0.417f, 1.583f) - verticalLineToRelative(3.5f) - quadToRelative(0f, 0.542f, -0.375f, 0.917f) - reflectiveQuadToRelative(-0.917f, 0.375f) - close() - moveTo(20.833f, 35f) - horizontalLineToRelative(6.292f) - lineToRelative(-3.167f, -4.042f) - quadToRelative(-0.833f, 0.334f, -1.625f, 0.709f) - quadToRelative(-0.791f, 0.375f, -1.5f, 0.833f) - close() - moveToRelative(8.292f, 0f) - horizontalLineToRelative(6.292f) - verticalLineToRelative(-2.5f) - quadToRelative(-0.709f, -0.458f, -1.479f, -0.833f) - quadToRelative(-0.771f, -0.375f, -1.605f, -0.709f) - close() - moveToRelative(-2f, 0f) - close() - moveToRelative(2f, 0f) - close() - moveToRelative(-1f, -13.958f) - close() - moveTo(7.875f, 32.083f) - verticalLineTo(7.875f) - verticalLineToRelative(6.75f) - verticalLineToRelative(-1.167f) - verticalLineToRelative(18.625f) - close() - moveToRelative(0f, 2.625f) - quadToRelative(-1.083f, 0f, -1.854f, -0.77f) - quadToRelative(-0.771f, -0.771f, -0.771f, -1.855f) - verticalLineTo(7.875f) - quadToRelative(0f, -1.083f, 0.771f, -1.854f) - quadToRelative(0.771f, -0.771f, 1.854f, -0.771f) - horizontalLineToRelative(24.25f) - quadToRelative(1.083f, 0f, 1.854f, 0.771f) - quadToRelative(0.771f, 0.771f, 0.771f, 1.854f) - verticalLineToRelative(9.5f) - quadToRelative(-0.458f, -0.833f, -1.125f, -1.542f) - quadToRelative(-0.667f, -0.708f, -1.5f, -1.208f) - verticalLineToRelative(-6.75f) - horizontalLineTo(7.875f) - verticalLineToRelative(24.208f) - horizontalLineToRelative(7.708f) - quadToRelative(0f, 0.209f, -0.021f, 0.396f) - quadToRelative(-0.02f, 0.188f, -0.02f, 0.396f) - verticalLineToRelative(1.833f) - close() - moveToRelative(5.25f, -20.25f) - horizontalLineTo(24.75f) - quadToRelative(0.75f, -0.5f, 1.625f, -0.729f) - quadToRelative(0.875f, -0.229f, 1.833f, -0.271f) - verticalLineToRelative(-1.375f) - quadToRelative(-0.083f, -0.166f, -0.208f, -0.229f) - quadToRelative(-0.125f, -0.062f, -0.292f, -0.062f) - horizontalLineTo(13.125f) - quadToRelative(-0.542f, 0f, -0.937f, 0.396f) - quadToRelative(-0.396f, 0.395f, -0.396f, 0.937f) - reflectiveQuadToRelative(0.396f, 0.938f) - quadToRelative(0.395f, 0.395f, 0.937f, 0.395f) - close() - moveToRelative(0f, 6.834f) - horizontalLineToRelative(7.458f) - quadToRelative(-0.041f, -0.667f, 0.063f, -1.334f) - quadToRelative(0.104f, -0.666f, 0.271f, -1.291f) - horizontalLineToRelative(-7.792f) - quadToRelative(-0.542f, 0f, -0.937f, 0.395f) - quadToRelative(-0.396f, 0.396f, -0.396f, 0.938f) - quadToRelative(0f, 0.542f, 0.396f, 0.917f) - quadToRelative(0.395f, 0.375f, 0.937f, 0.375f) - close() - moveToRelative(0f, 6.875f) - horizontalLineToRelative(4.75f) - quadToRelative(0.708f, -0.542f, 1.479f, -0.917f) - quadToRelative(0.771f, -0.375f, 1.646f, -0.708f) - lineToRelative(-0.083f, -0.417f) - quadToRelative(-0.084f, -0.292f, -0.292f, -0.437f) - quadToRelative(-0.208f, -0.146f, -0.583f, -0.146f) - horizontalLineToRelative(-6.917f) - quadToRelative(-0.542f, 0f, -0.937f, 0.375f) - quadToRelative(-0.396f, 0.375f, -0.396f, 0.916f) - quadToRelative(0f, 0.584f, 0.396f, 0.959f) - quadToRelative(0.395f, 0.375f, 0.937f, 0.375f) - close() - } - }.build() - } -} - - -@Composable -fun calibrate(): ImageVector { - return remember { - ImageVector.Builder( - name = "Calibration", defaultWidth = 40.0.dp, defaultHeight = 40.0.dp, - viewportWidth = 40.0f, viewportHeight = 40.0f - ).apply { - path( - fill = SolidColor(Color.Black), - fillAlpha = 1f, - stroke = null, - strokeAlpha = 1f, - strokeLineWidth = 1.0f, - strokeLineCap = Butt, - strokeLineJoin = Miter, - strokeLineMiter = 1f, - pathFillType = NonZero - ) { - moveToRelative(19.1401f, 39.9806f) - curveToRelative(-4.6432f, -0.209f, -8.9809f, -1.9697f, -12.4464f, -5.0521f) - lineToRelative(-0.3115f, -0.277f) - lineToRelative(-1.7193f, 1.7161f) - curveToRelative(-0.9456f, 0.9439f, -1.7546f, 1.7338f, -1.7978f, 1.7553f) - curveToRelative(-0.4297f, 0.2149f, -0.9594f, -0.0332f, -1.0551f, -0.4941f) - curveToRelative(-0.0316f, -0.1523f, -0.0055f, -0.3545f, 0.0636f, -0.4925f) - curveToRelative(0.0225f, -0.0449f, 0.813f, -0.8553f, 1.7567f, -1.8008f) - lineToRelative(1.7158f, -1.7192f) - lineToRelative(-0.2127f, -0.2367f) - curveToRelative(-2.3761f, -2.6439f, -4.0082f, -5.8685f, -4.7164f, -9.3188f) - curveToRelative(-0.5561f, -2.7092f, -0.5561f, -5.4136f, 0.0f, -8.1228f) - curveToRelative(0.689f, -3.3564f, 2.2631f, -6.5233f, 4.5194f, -9.0924f) - curveToRelative(0.1374f, -0.1565f, 0.2854f, -0.3255f, 0.329f, -0.3756f) - lineToRelative(0.0792f, -0.0912f) - lineToRelative(-1.6942f, -1.6925f) - curveToRelative(-0.9318f, -0.9309f, -1.7216f, -1.7384f, -1.755f, -1.7946f) - curveToRelative(-0.0335f, -0.0562f, -0.0727f, -0.1661f, -0.0871f, -0.2442f) - curveToRelative(-0.1085f, -0.5871f, 0.5153f, -1.0426f, 1.057f, -0.7717f) - curveToRelative(0.0431f, 0.0215f, 0.8521f, 0.8115f, 1.7978f, 1.7554f) - lineToRelative(1.7193f, 1.7161f) - lineToRelative(0.3115f, -0.277f) - curveToRelative(1.1123f, -0.9894f, 2.3494f, -1.8681f, 3.6256f, -2.5752f) - curveToRelative(1.4458f, -0.8012f, 3.0927f, -1.4571f, 4.6595f, -1.8558f) - curveToRelative(3.0516f, -0.7765f, 6.0694f, -0.8452f, 9.1448f, -0.2082f) - curveToRelative(3.3895f, 0.702f, 6.554f, 2.3009f, 9.1822f, 4.6393f) - lineToRelative(0.3115f, 0.2771f) - lineToRelative(1.7193f, -1.7163f) - curveToRelative(0.9456f, -0.944f, 1.7546f, -1.7339f, 1.7978f, -1.7554f) - curveToRelative(0.4303f, -0.2145f, 0.9594f, 0.0333f, 1.0551f, 0.4942f) - curveToRelative(0.0351f, 0.1692f, 0.0011f, 0.3768f, -0.0853f, 0.5219f) - curveToRelative(-0.0335f, 0.0562f, -0.8232f, 0.8637f, -1.755f, 1.7946f) - lineToRelative(-1.6942f, 1.6925f) - lineToRelative(0.0792f, 0.0912f) - curveToRelative(0.0436f, 0.0501f, 0.1916f, 0.2192f, 0.329f, 0.3756f) - curveToRelative(2.5749f, 2.932f, 4.2322f, 6.5862f, 4.7565f, 10.4877f) - curveToRelative(0.3105f, 2.3103f, 0.2207f, 4.5676f, -0.2745f, 6.9019f) - curveToRelative(-0.3943f, 1.8587f, -1.1066f, 3.7594f, -2.0403f, 5.4443f) - curveToRelative(-0.6737f, 1.2157f, -1.5357f, 2.442f, -2.4417f, 3.4737f) - curveToRelative(-0.1374f, 0.1564f, -0.2854f, 0.3255f, -0.329f, 0.3756f) - lineToRelative(-0.0792f, 0.0912f) - lineToRelative(1.6942f, 1.6925f) - curveToRelative(0.9318f, 0.9308f, 1.7216f, 1.7384f, 1.755f, 1.7945f) - curveToRelative(0.0335f, 0.0562f, 0.0727f, 0.1661f, 0.0871f, 0.2442f) - curveToRelative(0.1085f, 0.5871f, -0.5146f, 1.0422f, -1.057f, 0.7719f) - curveToRelative(-0.0431f, -0.0215f, -0.8521f, -0.8114f, -1.7978f, -1.7554f) - lineToRelative(-1.7193f, -1.7163f) - lineToRelative(-0.3115f, 0.2771f) - curveToRelative(-1.3455f, 1.1971f, -2.7934f, 2.1777f, -4.402f, 2.9813f) - curveToRelative(-2.99f, 1.4936f, -6.4214f, 2.2214f, -9.7637f, 2.071f) - close() - moveTo(20.8247f, 38.5122f) - curveToRelative(2.1961f, -0.1082f, 4.1436f, -0.5325f, 6.1521f, -1.3406f) - curveToRelative(1.1632f, -0.468f, 2.3217f, -1.0873f, 3.4013f, -1.8181f) - curveToRelative(0.7019f, -0.4752f, 1.5093f, -1.1078f, 2.0059f, -1.5716f) - lineToRelative(0.1869f, -0.1746f) - lineToRelative(-1.61f, -1.6116f) - lineToRelative(-1.61f, -1.6116f) - lineToRelative(-0.3274f, 0.2758f) - curveToRelative(-2.2902f, 1.9287f, -5.0092f, 3.0381f, -8.0145f, 3.2698f) - curveToRelative(-0.7656f, 0.059f, -1.8892f, 0.0316f, -2.7036f, -0.0662f) - curveToRelative(-2.6973f, -0.3237f, -5.2987f, -1.4662f, -7.3724f, -3.2381f) - lineToRelative(-0.2833f, -0.2421f) - lineToRelative(-1.6042f, 1.604f) - curveToRelative(-0.8823f, 0.8822f, -1.6042f, 1.6117f, -1.6042f, 1.6212f) - curveToRelative(0.0f, 0.0252f, 0.5917f, 0.5495f, 0.9078f, 0.8043f) - curveToRelative(2.2181f, 1.7883f, 4.7947f, 3.0394f, 7.5396f, 3.6608f) - curveToRelative(1.5974f, 0.3617f, 3.3455f, 0.517f, 4.936f, 0.4387f) - close() - moveTo(8.0125f, 30.9521f) - lineTo(9.6176f, 29.3469f) - lineTo(9.513f, 29.2266f) - curveToRelative(-1.6382f, -1.884f, -2.7332f, -4.1061f, -3.2027f, -6.4989f) - curveToRelative(-0.1996f, -1.0173f, -0.2516f, -1.5818f, -0.2516f, -2.7284f) - curveToRelative(0.0f, -0.9764f, 0.0242f, -1.3409f, 0.1382f, -2.0805f) - curveToRelative(0.4147f, -2.691f, 1.5279f, -5.0902f, 3.3161f, -7.1467f) - lineToRelative(0.1046f, -0.1203f) - lineToRelative(-1.6051f, -1.6052f) - curveToRelative(-0.8828f, -0.8829f, -1.6164f, -1.6016f, -1.6301f, -1.5971f) - curveToRelative(-0.0404f, 0.013f, -0.4916f, 0.5245f, -0.7953f, 0.9014f) - curveToRelative(-0.626f, 0.777f, -1.0605f, 1.4039f, -1.5887f, 2.2923f) - curveToRelative(-0.1927f, 0.3242f, -0.3328f, 0.5848f, -0.5811f, 1.0813f) - curveToRelative(-0.8939f, 1.7878f, -1.481f, 3.6632f, -1.7557f, 5.6087f) - curveToRelative(-0.259f, 1.8341f, -0.259f, 3.498f, 0.0f, 5.3321f) - curveToRelative(0.233f, 1.6502f, 0.6754f, 3.199f, 1.3699f, 4.7964f) - curveToRelative(0.1493f, 0.3433f, 0.5893f, 1.2317f, 0.7614f, 1.5371f) - curveToRelative(0.5366f, 0.9524f, 1.0791f, 1.7569f, 1.7524f, 2.599f) - curveToRelative(0.3505f, 0.4384f, 0.8114f, 0.9593f, 0.8487f, 0.9593f) - curveToRelative(0.0074f, 0.0f, 0.7358f, -0.7223f, 1.6186f, -1.6052f) - close() - moveTo(33.6428f, 32.5382f) - curveToRelative(0.014f, -0.0101f, 0.1437f, -0.1529f, 0.2883f, -0.3173f) - curveToRelative(0.8622f, -0.9807f, 1.6079f, -2.0364f, 2.2753f, -3.2211f) - curveToRelative(0.1545f, -0.2742f, 0.5787f, -1.1236f, 0.7244f, -1.4507f) - curveToRelative(0.6934f, -1.5557f, 1.1717f, -3.2159f, 1.4073f, -4.8836f) - curveToRelative(0.259f, -1.8341f, 0.259f, -3.498f, 0.0f, -5.3321f) - curveToRelative(-0.209f, -1.4803f, -0.5828f, -2.8629f, -1.1653f, -4.3106f) - curveToRelative(-0.7605f, -1.8901f, -1.9504f, -3.8068f, -3.2859f, -5.2929f) - curveToRelative(-0.1346f, -0.1497f, -0.2559f, -0.2759f, -0.2696f, -0.2803f) - curveToRelative(-0.0137f, -0.004f, -0.7473f, 0.7143f, -1.6301f, 1.5971f) - lineToRelative(-1.6051f, 1.6052f) - lineToRelative(0.1046f, 0.1203f) - curveToRelative(0.8306f, 0.9552f, 1.4816f, 1.9328f, 2.0376f, 3.0596f) - curveToRelative(0.7446f, 1.5089f, 1.1819f, 3.0431f, 1.3711f, 4.8098f) - curveToRelative(0.0355f, 0.3318f, 0.0454f, 0.626f, 0.0454f, 1.3579f) - curveToRelative(0.0f, 0.732f, -0.0098f, 1.0261f, -0.0454f, 1.3579f) - curveToRelative(-0.1892f, 1.7667f, -0.6265f, 3.3008f, -1.3711f, 4.8098f) - curveToRelative(-0.556f, 1.1268f, -1.2071f, 2.1043f, -2.0376f, 3.0596f) - lineToRelative(-0.1046f, 0.1203f) - lineToRelative(1.6051f, 1.6052f) - curveToRelative(0.8828f, 0.8828f, 1.6107f, 1.605f, 1.6176f, 1.6048f) - curveToRelative(0.0069f, -3.0E-4f, 0.024f, -0.009f, 0.038f, -0.0187f) - close() - moveTo(20.5729f, 32.4821f) - curveToRelative(1.8905f, -0.1035f, 3.5707f, -0.5504f, 5.1464f, -1.3688f) - curveToRelative(3.5334f, -1.8353f, 5.9832f, -5.1964f, 6.6115f, -9.0709f) - curveToRelative(0.2928f, -1.8057f, 0.1953f, -3.67f, -0.2815f, -5.382f) - curveToRelative(-0.5807f, -2.0852f, -1.6725f, -3.9545f, -3.2022f, -5.4829f) - curveToRelative(-2.0376f, -2.0358f, -4.6131f, -3.2789f, -7.4519f, -3.5968f) - curveToRelative(-1.7239f, -0.193f, -3.4908f, -0.0246f, -5.1206f, 0.488f) - curveToRelative(-1.9723f, 0.6203f, -3.668f, 1.6512f, -5.1335f, 3.121f) - curveToRelative(-1.811f, 1.8162f, -3.0253f, 4.1544f, -3.4491f, 6.6418f) - curveToRelative(-0.4198f, 2.4637f, -0.1264f, 4.93f, 0.854f, 7.1778f) - curveToRelative(0.9305f, 2.1334f, 2.5213f, 4.0413f, 4.457f, 5.3452f) - curveToRelative(1.9099f, 1.2866f, 4.0312f, 1.9967f, 6.349f, 2.1254f) - curveToRelative(0.5341f, 0.0297f, 0.7123f, 0.03f, 1.221f, 0.002f) - close() - moveTo(19.788f, 27.1172f) - curveToRelative(-0.2185f, -0.0718f, -0.373f, -0.218f, -0.4785f, -0.4528f) - curveToRelative(-0.0422f, -0.0941f, -0.0448f, -0.263f, -0.0448f, -3.0024f) - verticalLineToRelative(-2.9028f) - lineToRelative(-2.9179f, -0.0125f) - lineToRelative(-2.9179f, -0.0125f) - lineToRelative(-0.1716f, -0.0873f) - curveToRelative(-0.1375f, -0.07f, -0.1903f, -0.1146f, -0.2655f, -0.2242f) - curveToRelative(-0.1856f, -0.2706f, -0.1856f, -0.5763f, 0.0f, -0.8469f) - curveToRelative(0.0752f, -0.1097f, 0.128f, -0.1543f, 0.2655f, -0.2243f) - lineToRelative(0.1716f, -0.0873f) - lineToRelative(2.9179f, -0.0125f) - lineToRelative(2.9179f, -0.0125f) - verticalLineToRelative(-2.9027f) - verticalLineToRelative(-2.9028f) - lineToRelative(0.0653f, -0.1392f) - curveToRelative(0.1342f, -0.2862f, 0.3622f, -0.4339f, 0.6698f, -0.4339f) - curveToRelative(0.3076f, 0.0f, 0.5356f, 0.1477f, 0.6698f, 0.4339f) - lineToRelative(0.0653f, 0.1392f) - verticalLineToRelative(2.9028f) - verticalLineToRelative(2.9028f) - lineToRelative(2.9179f, 0.0125f) - lineToRelative(2.9179f, 0.0125f) - lineToRelative(0.1716f, 0.0873f) - curveToRelative(0.1375f, 0.07f, 0.1903f, 0.1146f, 0.2655f, 0.2243f) - curveToRelative(0.1856f, 0.2706f, 0.1856f, 0.5763f, 0.0f, 0.8469f) - curveToRelative(-0.0752f, 0.1097f, -0.128f, 0.1543f, -0.2655f, 0.2242f) - lineToRelative(-0.1716f, 0.0873f) - lineToRelative(-2.9179f, 0.0125f) - lineToRelative(-2.9179f, 0.0125f) - verticalLineToRelative(2.9028f) - verticalLineToRelative(2.9028f) - lineToRelative(-0.0653f, 0.1392f) - curveToRelative(-0.124f, 0.2643f, -0.3475f, 0.4198f, -0.6278f, 0.4367f) - curveToRelative(-0.0867f, 0.005f, -0.1977f, -0.005f, -0.2538f, -0.0235f) - close() - moveTo(10.9795f, 9.3361f) - curveToRelative(1.9415f, -1.6351f, 4.2271f, -2.6947f, 6.7155f, -3.1132f) - curveToRelative(1.2584f, -0.2116f, 2.7022f, -0.2433f, 3.9993f, -0.0876f) - curveToRelative(2.6875f, 0.3225f, 5.2253f, 1.4317f, 7.3292f, 3.2036f) - lineToRelative(0.3274f, 0.2757f) - lineToRelative(1.6038f, -1.6035f) - curveToRelative(0.8821f, -0.8819f, 1.6038f, -1.6113f, 1.6038f, -1.6208f) - curveToRelative(0.0f, -0.0252f, -0.5917f, -0.5495f, -0.9078f, -0.8043f) - curveToRelative(-2.2181f, -1.7883f, -4.7947f, -3.0394f, -7.5396f, -3.6608f) - curveToRelative(-1.5499f, -0.3509f, -3.2732f, -0.5095f, -4.8061f, -0.4424f) - curveToRelative(-2.2755f, 0.0996f, -4.2253f, 0.5169f, -6.2821f, 1.3444f) - curveToRelative(-1.8852f, 0.7585f, -3.7647f, 1.9232f, -5.2639f, 3.2622f) - curveToRelative(-0.1747f, 0.1561f, -0.3177f, 0.2914f, -0.3177f, 0.3008f) - curveToRelative(0.0f, 0.0152f, 3.1928f, 3.219f, 3.2079f, 3.219f) - curveToRelative(0.0034f, 0.0f, 0.152f, -0.1229f, 0.3303f, -0.273f) - close() - } - }.build() - } -} - - -@Composable -fun overview(): ImageVector { - return remember { - ImageVector.Builder( - name = "overview", - defaultWidth = 24.0.dp, defaultHeight = 24.0.dp, viewportWidth = 960.0f, - viewportHeight = 960.0f - ).apply { - path( - fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, - strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, - pathFillType = NonZero - ) { - moveToRelative(787.0f, 815.0f) - lineToRelative(28.0f, -28.0f) - lineToRelative(-75.0f, -75.0f) - verticalLineToRelative(-112.0f) - horizontalLineToRelative(-40.0f) - verticalLineToRelative(128.0f) - lineToRelative(87.0f, 87.0f) - close() - moveTo(200.0f, 840.0f) - quadToRelative(-33.0f, 0.0f, -56.5f, -23.5f) - reflectiveQuadTo(120.0f, 760.0f) - verticalLineToRelative(-560.0f) - quadToRelative(0.0f, -33.0f, 23.5f, -56.5f) - reflectiveQuadTo(200.0f, 120.0f) - horizontalLineToRelative(560.0f) - quadToRelative(33.0f, 0.0f, 56.5f, 23.5f) - reflectiveQuadTo(840.0f, 200.0f) - verticalLineToRelative(268.0f) - quadToRelative(-19.0f, -9.0f, -39.0f, -15.5f) - reflectiveQuadToRelative(-41.0f, -9.5f) - verticalLineToRelative(-243.0f) - lineTo(200.0f, 200.0f) - verticalLineToRelative(560.0f) - horizontalLineToRelative(242.0f) - quadToRelative(3.0f, 22.0f, 9.5f, 42.0f) - reflectiveQuadToRelative(15.5f, 38.0f) - lineTo(200.0f, 840.0f) - close() - moveTo(200.0f, 720.0f) - verticalLineToRelative(40.0f) - verticalLineToRelative(-560.0f) - verticalLineToRelative(243.0f) - verticalLineToRelative(-3.0f) - verticalLineToRelative(280.0f) - close() - moveTo(280.0f, 680.0f) - horizontalLineToRelative(163.0f) - quadToRelative(3.0f, -21.0f, 9.5f, -41.0f) - reflectiveQuadToRelative(14.5f, -39.0f) - lineTo(280.0f, 600.0f) - verticalLineToRelative(80.0f) - close() - moveTo(280.0f, 520.0f) - horizontalLineToRelative(244.0f) - quadToRelative(32.0f, -30.0f, 71.5f, -50.0f) - reflectiveQuadToRelative(84.5f, -27.0f) - verticalLineToRelative(-3.0f) - lineTo(280.0f, 440.0f) - verticalLineToRelative(80.0f) - close() - moveTo(280.0f, 360.0f) - horizontalLineToRelative(400.0f) - verticalLineToRelative(-80.0f) - lineTo(280.0f, 280.0f) - verticalLineToRelative(80.0f) - close() - moveTo(720.0f, 920.0f) - quadToRelative(-83.0f, 0.0f, -141.5f, -58.5f) - reflectiveQuadTo(520.0f, 720.0f) - quadToRelative(0.0f, -83.0f, 58.5f, -141.5f) - reflectiveQuadTo(720.0f, 520.0f) - quadToRelative(83.0f, 0.0f, 141.5f, 58.5f) - reflectiveQuadTo(920.0f, 720.0f) - quadToRelative(0.0f, 83.0f, -58.5f, 141.5f) - reflectiveQuadTo(720.0f, 920.0f) - close() - } - }.build() - } -} - - -public val ShowChart: ImageVector - get() { - if (_showChart != null) { - return _showChart!! - } - _showChart = materialIcon(name = "Outlined.ShowChart") { - materialPath { - moveTo(3.5f, 18.49f) - lineToRelative(6.0f, -6.01f) - lineToRelative(4.0f, 4.0f) - lineTo(22.0f, 6.92f) - lineToRelative(-1.41f, -1.41f) - lineToRelative(-7.09f, 7.97f) - lineToRelative(-4.0f, -4.0f) - lineTo(2.0f, 16.99f) - lineToRelative(1.5f, 1.5f) - close() - } - } - return _showChart!! - } - -private var _showChart: ImageVector? = null - - -public val Map: ImageVector - get() { - if (_map != null) { - return _map!! - } - _map = materialIcon(name = "Outlined.Map") { - materialPath { - moveTo(20.5f, 3.0f) - lineToRelative(-0.16f, 0.03f) - lineTo(15.0f, 5.1f) - lineTo(9.0f, 3.0f) - lineTo(3.36f, 4.9f) - curveToRelative(-0.21f, 0.07f, -0.36f, 0.25f, -0.36f, 0.48f) - lineTo(3.0f, 20.5f) - curveToRelative(0.0f, 0.28f, 0.22f, 0.5f, 0.5f, 0.5f) - lineToRelative(0.16f, -0.03f) - lineTo(9.0f, 18.9f) - lineToRelative(6.0f, 2.1f) - lineToRelative(5.64f, -1.9f) - curveToRelative(0.21f, -0.07f, 0.36f, -0.25f, 0.36f, -0.48f) - lineTo(21.0f, 3.5f) - curveToRelative(0.0f, -0.28f, -0.22f, -0.5f, -0.5f, -0.5f) - close() - moveTo(10.0f, 5.47f) - lineToRelative(4.0f, 1.4f) - verticalLineToRelative(11.66f) - lineToRelative(-4.0f, -1.4f) - lineTo(10.0f, 5.47f) - close() - moveTo(5.0f, 6.46f) - lineToRelative(3.0f, -1.01f) - verticalLineToRelative(11.7f) - lineToRelative(-3.0f, 1.16f) - lineTo(5.0f, 6.46f) - close() - moveTo(19.0f, 17.54f) - lineToRelative(-3.0f, 1.01f) - lineTo(16.0f, 6.86f) - lineToRelative(3.0f, -1.16f) - verticalLineToRelative(11.84f) - close() - } - } - return _map!! - } - -private var _map: ImageVector? = null - - -public val Help: ImageVector - get() { - if (_help != null) { - return _help!! - } - _help = materialIcon(name = "Outlined.Help") { - materialPath { - moveTo(12.0f, 2.0f) - curveTo(6.48f, 2.0f, 2.0f, 6.48f, 2.0f, 12.0f) - reflectiveCurveToRelative(4.48f, 10.0f, 10.0f, 10.0f) - reflectiveCurveToRelative(10.0f, -4.48f, 10.0f, -10.0f) - reflectiveCurveTo(17.52f, 2.0f, 12.0f, 2.0f) - close() - moveTo(13.0f, 19.0f) - horizontalLineToRelative(-2.0f) - verticalLineToRelative(-2.0f) - horizontalLineToRelative(2.0f) - verticalLineToRelative(2.0f) - close() - moveTo(15.07f, 11.25f) - lineToRelative(-0.9f, 0.92f) - curveTo(13.45f, 12.9f, 13.0f, 13.5f, 13.0f, 15.0f) - horizontalLineToRelative(-2.0f) - verticalLineToRelative(-0.5f) - curveToRelative(0.0f, -1.1f, 0.45f, -2.1f, 1.17f, -2.83f) - lineToRelative(1.24f, -1.26f) - curveToRelative(0.37f, -0.36f, 0.59f, -0.86f, 0.59f, -1.41f) - curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f) - reflectiveCurveToRelative(-2.0f, 0.9f, -2.0f, 2.0f) - lineTo(8.0f, 9.0f) - curveToRelative(0.0f, -2.21f, 1.79f, -4.0f, 4.0f, -4.0f) - reflectiveCurveToRelative(4.0f, 1.79f, 4.0f, 4.0f) - curveToRelative(0.0f, 0.88f, -0.36f, 1.68f, -0.93f, 2.25f) - close() - } - } - return _help!! - } - -private var _help: ImageVector? = null - - -public val Info: ImageVector - get() { - if (_info != null) { - return _info!! - } - _info = materialIcon(name = "Outlined.Info") { - materialPath { - moveTo(11.0f, 7.0f) - horizontalLineToRelative(2.0f) - verticalLineToRelative(2.0f) - horizontalLineToRelative(-2.0f) - close() - moveTo(11.0f, 11.0f) - horizontalLineToRelative(2.0f) - verticalLineToRelative(6.0f) - horizontalLineToRelative(-2.0f) - close() - moveTo(12.0f, 2.0f) - curveTo(6.48f, 2.0f, 2.0f, 6.48f, 2.0f, 12.0f) - reflectiveCurveToRelative(4.48f, 10.0f, 10.0f, 10.0f) - reflectiveCurveToRelative(10.0f, -4.48f, 10.0f, -10.0f) - reflectiveCurveTo(17.52f, 2.0f, 12.0f, 2.0f) - close() - moveTo(12.0f, 20.0f) - curveToRelative(-4.41f, 0.0f, -8.0f, -3.59f, -8.0f, -8.0f) - reflectiveCurveToRelative(3.59f, -8.0f, 8.0f, -8.0f) - reflectiveCurveToRelative(8.0f, 3.59f, 8.0f, 8.0f) - reflectiveCurveToRelative(-3.59f, 8.0f, -8.0f, 8.0f) - close() - } - } - return _info!! - } - -private var _info: ImageVector? = null - - -public val CenterFocusWeak: ImageVector - get() { - if (_centerFocusWeak != null) { - return _centerFocusWeak!! - } - _centerFocusWeak = materialIcon(name = "Filled.CenterFocusWeak") { - materialPath { - moveTo(5.0f, 15.0f) - lineTo(3.0f, 15.0f) - verticalLineToRelative(4.0f) - curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f) - horizontalLineToRelative(4.0f) - verticalLineToRelative(-2.0f) - lineTo(5.0f, 19.0f) - verticalLineToRelative(-4.0f) - close() - moveTo(5.0f, 5.0f) - horizontalLineToRelative(4.0f) - lineTo(9.0f, 3.0f) - lineTo(5.0f, 3.0f) - curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f) - verticalLineToRelative(4.0f) - horizontalLineToRelative(2.0f) - lineTo(5.0f, 5.0f) - close() - moveTo(19.0f, 3.0f) - horizontalLineToRelative(-4.0f) - verticalLineToRelative(2.0f) - horizontalLineToRelative(4.0f) - verticalLineToRelative(4.0f) - horizontalLineToRelative(2.0f) - lineTo(21.0f, 5.0f) - curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f) - close() - moveTo(19.0f, 19.0f) - horizontalLineToRelative(-4.0f) - verticalLineToRelative(2.0f) - horizontalLineToRelative(4.0f) - curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f) - verticalLineToRelative(-4.0f) - horizontalLineToRelative(-2.0f) - verticalLineToRelative(4.0f) - close() - moveTo(12.0f, 8.0f) - curveToRelative(-2.21f, 0.0f, -4.0f, 1.79f, -4.0f, 4.0f) - reflectiveCurveToRelative(1.79f, 4.0f, 4.0f, 4.0f) - reflectiveCurveToRelative(4.0f, -1.79f, 4.0f, -4.0f) - reflectiveCurveToRelative(-1.79f, -4.0f, -4.0f, -4.0f) - close() - moveTo(12.0f, 14.0f) - curveToRelative(-1.1f, 0.0f, -2.0f, -0.9f, -2.0f, -2.0f) - reflectiveCurveToRelative(0.9f, -2.0f, 2.0f, -2.0f) - reflectiveCurveToRelative(2.0f, 0.9f, 2.0f, 2.0f) - reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) - close() - } - } - return _centerFocusWeak!! - } - -private var _centerFocusWeak: ImageVector? = null - - -public val Settings: ImageVector - get() { - if (_settings != null) { - return _settings!! - } - _settings = materialIcon(name = "Outlined.Settings") { - materialPath { - moveTo(19.43f, 12.98f) - curveToRelative(0.04f, -0.32f, 0.07f, -0.64f, 0.07f, -0.98f) - curveToRelative(0.0f, -0.34f, -0.03f, -0.66f, -0.07f, -0.98f) - lineToRelative(2.11f, -1.65f) - curveToRelative(0.19f, -0.15f, 0.24f, -0.42f, 0.12f, -0.64f) - lineToRelative(-2.0f, -3.46f) - curveToRelative(-0.09f, -0.16f, -0.26f, -0.25f, -0.44f, -0.25f) - curveToRelative(-0.06f, 0.0f, -0.12f, 0.01f, -0.17f, 0.03f) - lineToRelative(-2.49f, 1.0f) - curveToRelative(-0.52f, -0.4f, -1.08f, -0.73f, -1.69f, -0.98f) - lineToRelative(-0.38f, -2.65f) - curveTo(14.46f, 2.18f, 14.25f, 2.0f, 14.0f, 2.0f) - horizontalLineToRelative(-4.0f) - curveToRelative(-0.25f, 0.0f, -0.46f, 0.18f, -0.49f, 0.42f) - lineToRelative(-0.38f, 2.65f) - curveToRelative(-0.61f, 0.25f, -1.17f, 0.59f, -1.69f, 0.98f) - lineToRelative(-2.49f, -1.0f) - curveToRelative(-0.06f, -0.02f, -0.12f, -0.03f, -0.18f, -0.03f) - curveToRelative(-0.17f, 0.0f, -0.34f, 0.09f, -0.43f, 0.25f) - lineToRelative(-2.0f, 3.46f) - curveToRelative(-0.13f, 0.22f, -0.07f, 0.49f, 0.12f, 0.64f) - lineToRelative(2.11f, 1.65f) - curveToRelative(-0.04f, 0.32f, -0.07f, 0.65f, -0.07f, 0.98f) - curveToRelative(0.0f, 0.33f, 0.03f, 0.66f, 0.07f, 0.98f) - lineToRelative(-2.11f, 1.65f) - curveToRelative(-0.19f, 0.15f, -0.24f, 0.42f, -0.12f, 0.64f) - lineToRelative(2.0f, 3.46f) - curveToRelative(0.09f, 0.16f, 0.26f, 0.25f, 0.44f, 0.25f) - curveToRelative(0.06f, 0.0f, 0.12f, -0.01f, 0.17f, -0.03f) - lineToRelative(2.49f, -1.0f) - curveToRelative(0.52f, 0.4f, 1.08f, 0.73f, 1.69f, 0.98f) - lineToRelative(0.38f, 2.65f) - curveToRelative(0.03f, 0.24f, 0.24f, 0.42f, 0.49f, 0.42f) - horizontalLineToRelative(4.0f) - curveToRelative(0.25f, 0.0f, 0.46f, -0.18f, 0.49f, -0.42f) - lineToRelative(0.38f, -2.65f) - curveToRelative(0.61f, -0.25f, 1.17f, -0.59f, 1.69f, -0.98f) - lineToRelative(2.49f, 1.0f) - curveToRelative(0.06f, 0.02f, 0.12f, 0.03f, 0.18f, 0.03f) - curveToRelative(0.17f, 0.0f, 0.34f, -0.09f, 0.43f, -0.25f) - lineToRelative(2.0f, -3.46f) - curveToRelative(0.12f, -0.22f, 0.07f, -0.49f, -0.12f, -0.64f) - lineToRelative(-2.11f, -1.65f) - close() - moveTo(17.45f, 11.27f) - curveToRelative(0.04f, 0.31f, 0.05f, 0.52f, 0.05f, 0.73f) - curveToRelative(0.0f, 0.21f, -0.02f, 0.43f, -0.05f, 0.73f) - lineToRelative(-0.14f, 1.13f) - lineToRelative(0.89f, 0.7f) - lineToRelative(1.08f, 0.84f) - lineToRelative(-0.7f, 1.21f) - lineToRelative(-1.27f, -0.51f) - lineToRelative(-1.04f, -0.42f) - lineToRelative(-0.9f, 0.68f) - curveToRelative(-0.43f, 0.32f, -0.84f, 0.56f, -1.25f, 0.73f) - lineToRelative(-1.06f, 0.43f) - lineToRelative(-0.16f, 1.13f) - lineToRelative(-0.2f, 1.35f) - horizontalLineToRelative(-1.4f) - lineToRelative(-0.19f, -1.35f) - lineToRelative(-0.16f, -1.13f) - lineToRelative(-1.06f, -0.43f) - curveToRelative(-0.43f, -0.18f, -0.83f, -0.41f, -1.23f, -0.71f) - lineToRelative(-0.91f, -0.7f) - lineToRelative(-1.06f, 0.43f) - lineToRelative(-1.27f, 0.51f) - lineToRelative(-0.7f, -1.21f) - lineToRelative(1.08f, -0.84f) - lineToRelative(0.89f, -0.7f) - lineToRelative(-0.14f, -1.13f) - curveToRelative(-0.03f, -0.31f, -0.05f, -0.54f, -0.05f, -0.74f) - reflectiveCurveToRelative(0.02f, -0.43f, 0.05f, -0.73f) - lineToRelative(0.14f, -1.13f) - lineToRelative(-0.89f, -0.7f) - lineToRelative(-1.08f, -0.84f) - lineToRelative(0.7f, -1.21f) - lineToRelative(1.27f, 0.51f) - lineToRelative(1.04f, 0.42f) - lineToRelative(0.9f, -0.68f) - curveToRelative(0.43f, -0.32f, 0.84f, -0.56f, 1.25f, -0.73f) - lineToRelative(1.06f, -0.43f) - lineToRelative(0.16f, -1.13f) - lineToRelative(0.2f, -1.35f) - horizontalLineToRelative(1.39f) - lineToRelative(0.19f, 1.35f) - lineToRelative(0.16f, 1.13f) - lineToRelative(1.06f, 0.43f) - curveToRelative(0.43f, 0.18f, 0.83f, 0.41f, 1.23f, 0.71f) - lineToRelative(0.91f, 0.7f) - lineToRelative(1.06f, -0.43f) - lineToRelative(1.27f, -0.51f) - lineToRelative(0.7f, 1.21f) - lineToRelative(-1.07f, 0.85f) - lineToRelative(-0.89f, 0.7f) - lineToRelative(0.14f, 1.13f) - close() - moveTo(12.0f, 8.0f) - curveToRelative(-2.21f, 0.0f, -4.0f, 1.79f, -4.0f, 4.0f) - reflectiveCurveToRelative(1.79f, 4.0f, 4.0f, 4.0f) - reflectiveCurveToRelative(4.0f, -1.79f, 4.0f, -4.0f) - reflectiveCurveToRelative(-1.79f, -4.0f, -4.0f, -4.0f) - close() - moveTo(12.0f, 14.0f) - curveToRelative(-1.1f, 0.0f, -2.0f, -0.9f, -2.0f, -2.0f) - reflectiveCurveToRelative(0.9f, -2.0f, 2.0f, -2.0f) - reflectiveCurveToRelative(2.0f, 0.9f, 2.0f, 2.0f) - reflectiveCurveToRelative(-0.9f, 2.0f, -2.0f, 2.0f) - close() - } - } - return _settings!! - } - -private var _settings: ImageVector? = null - - -public val Mic: ImageVector - get() { - if (_mic != null) { - return _mic!! - } - _mic = materialIcon(name = "Outlined.Mic") { - materialPath { - moveTo(12.0f, 14.0f) - curveToRelative(1.66f, 0.0f, 3.0f, -1.34f, 3.0f, -3.0f) - verticalLineTo(5.0f) - curveToRelative(0.0f, -1.66f, -1.34f, -3.0f, -3.0f, -3.0f) - reflectiveCurveTo(9.0f, 3.34f, 9.0f, 5.0f) - verticalLineToRelative(6.0f) - curveTo(9.0f, 12.66f, 10.34f, 14.0f, 12.0f, 14.0f) - close() - } - materialPath { - moveTo(17.0f, 11.0f) - curveToRelative(0.0f, 2.76f, -2.24f, 5.0f, -5.0f, 5.0f) - reflectiveCurveToRelative(-5.0f, -2.24f, -5.0f, -5.0f) - horizontalLineTo(5.0f) - curveToRelative(0.0f, 3.53f, 2.61f, 6.43f, 6.0f, 6.92f) - verticalLineTo(21.0f) - horizontalLineToRelative(2.0f) - verticalLineToRelative(-3.08f) - curveToRelative(3.39f, -0.49f, 6.0f, -3.39f, 6.0f, -6.92f) - horizontalLineTo(17.0f) - close() - } - } - return _mic!! - } - -private var _mic: ImageVector? = null - -public val QuestionMark: ImageVector - get() { - if (_questionMark != null) { - return _questionMark!! - } - _questionMark = materialIcon(name = "Outlined.QuestionMark") { - materialPath { - moveTo(11.07f, 12.85f) - curveToRelative(0.77f, -1.39f, 2.25f, -2.21f, 3.11f, -3.44f) - curveToRelative(0.91f, -1.29f, 0.4f, -3.7f, -2.18f, -3.7f) - curveToRelative(-1.69f, 0.0f, -2.52f, 1.28f, -2.87f, 2.34f) - lineTo(6.54f, 6.96f) - curveTo(7.25f, 4.83f, 9.18f, 3.0f, 11.99f, 3.0f) - curveToRelative(2.35f, 0.0f, 3.96f, 1.07f, 4.78f, 2.41f) - curveToRelative(0.7f, 1.15f, 1.11f, 3.3f, 0.03f, 4.9f) - curveToRelative(-1.2f, 1.77f, -2.35f, 2.31f, -2.97f, 3.45f) - curveToRelative(-0.25f, 0.46f, -0.35f, 0.76f, -0.35f, 2.24f) - horizontalLineToRelative(-2.89f) - curveTo(10.58f, 15.22f, 10.46f, 13.95f, 11.07f, 12.85f) - close() - moveTo(14.0f, 20.0f) - curveToRelative(0.0f, 1.1f, -0.9f, 2.0f, -2.0f, 2.0f) - reflectiveCurveToRelative(-2.0f, -0.9f, -2.0f, -2.0f) - curveToRelative(0.0f, -1.1f, 0.9f, -2.0f, 2.0f, -2.0f) - reflectiveCurveTo(14.0f, 18.9f, 14.0f, 20.0f) - close() - } - } - return _questionMark!! - } - -private var _questionMark: ImageVector? = null - - -public val Check: ImageVector - get() { - if (_check != null) { - return _check!! - } - _check = materialIcon(name = "Filled.Check") { - materialPath { - moveTo(9.0f, 16.17f) - lineTo(4.83f, 12.0f) - lineToRelative(-1.42f, 1.41f) - lineTo(9.0f, 19.0f) - lineTo(21.0f, 7.0f) - lineToRelative(-1.41f, -1.41f) - close() - } - } - return _check!! - } - -private var _check: ImageVector? = null - -public val Close: ImageVector - get() { - if (_close != null) { - return _close!! - } - _close = materialIcon(name = "Outlined.Close") { - materialPath { - moveTo(19.0f, 6.41f) - lineTo(17.59f, 5.0f) - lineTo(12.0f, 10.59f) - lineTo(6.41f, 5.0f) - lineTo(5.0f, 6.41f) - lineTo(10.59f, 12.0f) - lineTo(5.0f, 17.59f) - lineTo(6.41f, 19.0f) - lineTo(12.0f, 13.41f) - lineTo(17.59f, 19.0f) - lineTo(19.0f, 17.59f) - lineTo(13.41f, 12.0f) - lineTo(19.0f, 6.41f) - close() - } - } - return _close!! - } - -private var _close: ImageVector? = null diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/MaterialColors.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/MaterialColors.kt deleted file mode 100644 index 5080001..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/MaterialColors.kt +++ /dev/null @@ -1,300 +0,0 @@ -package org.noiseplanet.noisecapture.shared.ui.theme - -import androidx.compose.ui.graphics.Color - -// reds -val md_red_50 = Color(0xFFFFEBEE) -val md_red_100 = Color(0xFFFFCDD2) -val md_red_200 = Color(0xFFEF9A9A) -val md_red_300 = Color(0xFFE57373) -val md_red_400 = Color(0xFFEF5350) -val md_red_500 = Color(0xFFF44336) -val md_red_600 = Color(0xFFE53935) -val md_red_700 = Color(0xFFD32F2F) -val md_red_800 = Color(0xFFC62828) -val md_red_900 = Color(0xFFB71C1C) -val md_red_A100 = Color(0xFFFF8A80) -val md_red_A200 = Color(0xFFFF5252) -val md_red_A400 = Color(0xFFFF1744) -val md_red_A700 = Color(0xFFD50000) - -// pinks -val md_pink_50 = Color(0xFFFCE4EC) -val md_pink_100 = Color(0xFFF8BBD0) -val md_pink_200 = Color(0xFFF48FB1) -val md_pink_300 = Color(0xFFF06292) -val md_pink_400 = Color(0xFFEC407A) -val md_pink_500 = Color(0xFFE91E63) -val md_pink_600 = Color(0xFFD81B60) -val md_pink_700 = Color(0xFFC2185B) -val md_pink_800 = Color(0xFFAD1457) -val md_pink_900 = Color(0xFF880E4F) -val md_pink_A100 = Color(0xFFFF80AB) -val md_pink_A200 = Color(0xFFFF4081) -val md_pink_A400 = Color(0xFFF50057) -val md_pink_A700 = Color(0xFFC51162) - -// purples -val md_purple_50 = Color(0xFFF3E5F5) -val md_purple_100 = Color(0xFFE1BEE7) -val md_purple_200 = Color(0xFFCE93D8) -val md_purple_300 = Color(0xFFBA68C8) -val md_purple_400 = Color(0xFFAB47BC) -val md_purple_500 = Color(0xFF9C27B0) -val md_purple_600 = Color(0xFF8E24AA) -val md_purple_700 = Color(0xFF7B1FA2) -val md_purple_800 = Color(0xFF6A1B9A) -val md_purple_900 = Color(0xFF4A148C) -val md_purple_A100 = Color(0xFFEA80FC) -val md_purple_A200 = Color(0xFFE040FB) -val md_purple_A400 = Color(0xFFD500F9) -val md_purple_A700 = Color(0xFFAA00FF) - -// deep purples -val md_deep_purple_50 = Color(0xFFEDE7F6) -val md_deep_purple_100 = Color(0xFFD1C4E9) -val md_deep_purple_200 = Color(0xFFB39DDB) -val md_deep_purple_300 = Color(0xFF9575CD) -val md_deep_purple_400 = Color(0xFF7E57C2) -val md_deep_purple_500 = Color(0xFF673AB7) -val md_deep_purple_600 = Color(0xFF5E35B1) -val md_deep_purple_700 = Color(0xFF512DA8) -val md_deep_purple_800 = Color(0xFF4527A0) -val md_deep_purple_900 = Color(0xFF311B92) -val md_deep_purple_A100 = Color(0xFFB388FF) -val md_deep_purple_A200 = Color(0xFF7C4DFF) -val md_deep_purple_A400 = Color(0xFF651FFF) -val md_deep_purple_A700 = Color(0xFF6200EA) - -// indigo -val md_indigo_50 = Color(0xFFE8EAF6) -val md_indigo_100 = Color(0xFFC5CAE9) -val md_indigo_200 = Color(0xFF9FA8DA) -val md_indigo_300 = Color(0xFF7986CB) -val md_indigo_400 = Color(0xFF5C6BC0) -val md_indigo_500 = Color(0xFF3F51B5) -val md_indigo_600 = Color(0xFF3949AB) -val md_indigo_700 = Color(0xFF303F9F) -val md_indigo_800 = Color(0xFF283593) -val md_indigo_900 = Color(0xFF1A237E) -val md_indigo_A100 = Color(0xFF8C9EFF) -val md_indigo_A200 = Color(0xFF536DFE) -val md_indigo_A400 = Color(0xFF3D5AFE) -val md_indigo_A700 = Color(0xFF304FFE) - -// blue -val md_blue_50 = Color(0xFFE3F2FD) -val md_blue_100 = Color(0xFFBBDEFB) -val md_blue_200 = Color(0xFF90CAF9) -val md_blue_300 = Color(0xFF64B5F6) -val md_blue_400 = Color(0xFF42A5F5) -val md_blue_500 = Color(0xFF2196F3) -val md_blue_600 = Color(0xFF1E88E5) -val md_blue_700 = Color(0xFF1976D2) -val md_blue_800 = Color(0xFF1565C0) -val md_blue_900 = Color(0xFF0D47A1) -val md_blue_A100 = Color(0xFF82B1FF) -val md_blue_A200 = Color(0xFF448AFF) -val md_blue_A400 = Color(0xFF2979FF) -val md_blue_A700 = Color(0xFF2962FF) - -// light blue -val md_light_blue_50 = Color(0xFFE1F5FE) -val md_light_blue_100 = Color(0xFFB3E5FC) -val md_light_blue_200 = Color(0xFF81D4fA) -val md_light_blue_300 = Color(0xFF4fC3F7) -val md_light_blue_400 = Color(0xFF29B6FC) -val md_light_blue_500 = Color(0xFF03A9F4) -val md_light_blue_600 = Color(0xFF039BE5) -val md_light_blue_700 = Color(0xFF0288D1) -val md_light_blue_800 = Color(0xFF0277BD) -val md_light_blue_900 = Color(0xFF01579B) -val md_light_blue_A100 = Color(0xFF80D8FF) -val md_light_blue_A200 = Color(0xFF40C4FF) -val md_light_blue_A400 = Color(0xFF00B0FF) -val md_light_blue_A700 = Color(0xFF0091EA) - -// cyan -val md_cyan_50 = Color(0xFFE0F7FA) -val md_cyan_100 = Color(0xFFB2EBF2) -val md_cyan_200 = Color(0xFF80DEEA) -val md_cyan_300 = Color(0xFF4DD0E1) -val md_cyan_400 = Color(0xFF26C6DA) -val md_cyan_500 = Color(0xFF00BCD4) -val md_cyan_600 = Color(0xFF00ACC1) -val md_cyan_700 = Color(0xFF0097A7) -val md_cyan_800 = Color(0xFF00838F) -val md_cyan_900 = Color(0xFF006064) -val md_cyan_A100 = Color(0xFF84FFFF) -val md_cyan_A200 = Color(0xFF18FFFF) -val md_cyan_A400 = Color(0xFF00E5FF) -val md_cyan_A700 = Color(0xFF00B8D4) - -// teal -val md_teal_50 = Color(0xFFE0F2F1) -val md_teal_100 = Color(0xFFB2DFDB) -val md_teal_200 = Color(0xFF80CBC4) -val md_teal_300 = Color(0xFF4DB6AC) -val md_teal_400 = Color(0xFF26A69A) -val md_teal_500 = Color(0xFF009688) -val md_teal_600 = Color(0xFF00897B) -val md_teal_700 = Color(0xFF00796B) -val md_teal_800 = Color(0xFF00695C) -val md_teal_900 = Color(0xFF004D40) -val md_teal_A100 = Color(0xFFA7FFEB) -val md_teal_A200 = Color(0xFF64FFDA) -val md_teal_A400 = Color(0xFF1DE9B6) -val md_teal_A700 = Color(0xFF00BFA5) - -// green -val md_green_50 = Color(0xFFE8F5E9) -val md_green_100 = Color(0xFFC8E6C9) -val md_green_200 = Color(0xFFA5D6A7) -val md_green_300 = Color(0xFF81C784) -val md_green_400 = Color(0xFF66BB6A) -val md_green_500 = Color(0xFF4CAF50) -val md_green_600 = Color(0xFF43A047) -val md_green_700 = Color(0xFF388E3C) -val md_green_800 = Color(0xFF2E7D32) -val md_green_900 = Color(0xFF1B5E20) -val md_green_A100 = Color(0xFFB9F6CA) -val md_green_A200 = Color(0xFF69F0AE) -val md_green_A400 = Color(0xFF00E676) -val md_green_A700 = Color(0xFF00C853) - -// light green -val md_light_green_50 = Color(0xFFF1F8E9) -val md_light_green_100 = Color(0xFFDCEDC8) -val md_light_green_200 = Color(0xFFC5E1A5) -val md_light_green_300 = Color(0xFFAED581) -val md_light_green_400 = Color(0xFF9CCC65) -val md_light_green_500 = Color(0xFF8BC34A) -val md_light_green_600 = Color(0xFF7CB342) -val md_light_green_700 = Color(0xFF689F38) -val md_light_green_800 = Color(0xFF558B2F) -val md_light_green_900 = Color(0xFF33691E) -val md_light_green_A100 = Color(0xFFCCFF90) -val md_light_green_A200 = Color(0xFFB2FF59) -val md_light_green_A400 = Color(0xFF76FF03) -val md_light_green_A700 = Color(0xFF64DD17) - -// lime -val md_lime_50 = Color(0xFFF9FBE7) -val md_lime_100 = Color(0xFFF0F4C3) -val md_lime_200 = Color(0xFFE6EE9C) -val md_lime_300 = Color(0xFFDCE775) -val md_lime_400 = Color(0xFFD4E157) -val md_lime_500 = Color(0xFFCDDC39) -val md_lime_600 = Color(0xFFC0CA33) -val md_lime_700 = Color(0xFFA4B42B) -val md_lime_800 = Color(0xFF9E9D24) -val md_lime_900 = Color(0xFF827717) -val md_lime_A100 = Color(0xFFF4FF81) -val md_lime_A200 = Color(0xFFEEFF41) -val md_lime_A400 = Color(0xFFC6FF00) -val md_lime_A700 = Color(0xFFAEEA00) - -// yellow -val md_yellow_50 = Color(0xFFFFFDE7) -val md_yellow_100 = Color(0xFFFFF9C4) -val md_yellow_200 = Color(0xFFFFF590) -val md_yellow_300 = Color(0xFFFFF176) -val md_yellow_400 = Color(0xFFFFEE58) -val md_yellow_500 = Color(0xFFFFEB3B) -val md_yellow_600 = Color(0xFFFDD835) -val md_yellow_700 = Color(0xFFFBC02D) -val md_yellow_800 = Color(0xFFF9A825) -val md_yellow_900 = Color(0xFFF57F17) -val md_yellow_A100 = Color(0xFFFFFF82) -val md_yellow_A200 = Color(0xFFFFFF00) -val md_yellow_A400 = Color(0xFFFFEA00) -val md_yellow_A700 = Color(0xFFFFD600) - -// amber -val md_amber_50 = Color(0xFFFFF8E1) -val md_amber_100 = Color(0xFFFFECB3) -val md_amber_200 = Color(0xFFFFE082) -val md_amber_300 = Color(0xFFFFD54F) -val md_amber_400 = Color(0xFFFFCA28) -val md_amber_500 = Color(0xFFFFC107) -val md_amber_600 = Color(0xFFFFB300) -val md_amber_700 = Color(0xFFFFA000) -val md_amber_800 = Color(0xFFFF8F00) -val md_amber_900 = Color(0xFFFF6F00) -val md_amber_A100 = Color(0xFFFFE57F) -val md_amber_A200 = Color(0xFFFFD740) -val md_amber_A400 = Color(0xFFFFC400) -val md_amber_A700 = Color(0xFFFFAB00) - -// orange -val md_orange_50 = Color(0xFFFFF3E0) -val md_orange_100 = Color(0xFFFFE0B2) -val md_orange_200 = Color(0xFFFFCC80) -val md_orange_300 = Color(0xFFFFB74D) -val md_orange_400 = Color(0xFFFFA726) -val md_orange_500 = Color(0xFFFF9800) -val md_orange_600 = Color(0xFFFB8C00) -val md_orange_700 = Color(0xFFF57C00) -val md_orange_800 = Color(0xFFEF6C00) -val md_orange_900 = Color(0xFFE65100) -val md_orange_A100 = Color(0xFFFFD180) -val md_orange_A200 = Color(0xFFFFAB40) -val md_orange_A400 = Color(0xFFFF9100) -val md_orange_A700 = Color(0xFFFF6D00) - -// deep orange -val md_deep_orange_50 = Color(0xFFFBE9A7) -val md_deep_orange_100 = Color(0xFFFFCCBC) -val md_deep_orange_200 = Color(0xFFFFAB91) -val md_deep_orange_300 = Color(0xFFFF8A65) -val md_deep_orange_400 = Color(0xFFFF7043) -val md_deep_orange_500 = Color(0xFFFF5722) -val md_deep_orange_600 = Color(0xFFF4511E) -val md_deep_orange_700 = Color(0xFFE64A19) -val md_deep_orange_800 = Color(0xFFD84315) -val md_deep_orange_900 = Color(0xFFBF360C) -val md_deep_orange_A100 = Color(0xFFFF9E80) -val md_deep_orange_A200 = Color(0xFFFF6E40) -val md_deep_orange_A400 = Color(0xFFFF3D00) -val md_deep_orange_A700 = Color(0xFFDD2600) - -// brown -val md_brown_50 = Color(0xFFEFEBE9) -val md_brown_100 = Color(0xFFD7CCC8) -val md_brown_200 = Color(0xFFBCAAA4) -val md_brown_300 = Color(0xFFA1887F) -val md_brown_400 = Color(0xFF8D6E63) -val md_brown_500 = Color(0xFF795548) -val md_brown_600 = Color(0xFF6D4C41) -val md_brown_700 = Color(0xFF5D4037) -val md_brown_800 = Color(0xFF4E342E) -val md_brown_900 = Color(0xFF3E2723) - -// grey -val md_grey_50 = Color(0xFFFAFAFA) -val md_grey_100 = Color(0xFFF5F5F5) -val md_grey_200 = Color(0xFFEEEEEE) -val md_grey_300 = Color(0xFFE0E0E0) -val md_grey_400 = Color(0xFFBDBDBD) -val md_grey_500 = Color(0xFF9E9E9E) -val md_grey_600 = Color(0xFF757575) -val md_grey_700 = Color(0xFF616161) -val md_grey_800 = Color(0xFF424242) -val md_grey_900 = Color(0xFF212121) -val md_black_1000 = Color(0xFF000000) -val md_white_1000 = Color(0xFFffffff) - -// blue grey -val md_blue_grey_50 = Color(0xFFECEFF1) -val md_blue_grey_100 = Color(0xFFCFD8DC) -val md_blue_grey_200 = Color(0xFFB0BBC5) -val md_blue_grey_300 = Color(0xFF90A4AE) -val md_blue_grey_400 = Color(0xFF78909C) -val md_blue_grey_500 = Color(0xFF607D8B) -val md_blue_grey_600 = Color(0xFF546E7A) -val md_blue_grey_700 = Color(0xFF455A64) -val md_blue_grey_800 = Color(0xFF37474F) -val md_blue_grey_900 = Color(0xFF263238) -val md_blue_grey_gwendall = Color(0xFFEFEFF4) -val md_blue_grey_gwendall_darker = Color(0xFFE7E6ED) -val md_blue_grey_gwendall_text = Color(0xFF645D91) diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Shape.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Shape.kt deleted file mode 100644 index 5362b37..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Shape.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.noiseplanet.noisecapture.shared.ui.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes -import androidx.compose.ui.unit.dp - -val Shapes = Shapes( - small = RoundedCornerShape(4.dp), - medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) -) diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Theme.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Theme.kt deleted file mode 100644 index cbe68db..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Theme.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.noiseplanet.noisecapture.shared.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors -import androidx.compose.runtime.Composable - -private val DarkColorPalette = darkColors( - primary = md_yellow_A700, - primaryVariant = md_yellow_A400, - secondary = md_yellow_A400, - background = md_blue_grey_900, - surface = md_blue_grey_900, - onPrimary = md_yellow_50, - onSecondary = md_yellow_50, - onBackground = md_yellow_50, - onSurface = md_yellow_50, -) - -private val LightColorPalette = lightColors( - primary = md_blue_grey_gwendall_darker, - primaryVariant = md_yellow_A400, - secondary = md_yellow_A400, - background = md_blue_grey_gwendall, - surface = md_blue_grey_gwendall, - onPrimary = md_blue_grey_gwendall_text, - onSecondary = md_blue_grey_900, - onBackground = md_blue_grey_900, - onSurface = md_blue_grey_900, -) - -@Composable -fun AppyxStarterKitTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val colors = if (darkTheme) { - DarkColorPalette - } else { - LightColorPalette - } - - MaterialTheme( - colors = colors, - typography = Typography, - shapes = Shapes, - content = content - ) -} diff --git a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Type.kt b/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Type.kt deleted file mode 100644 index 26c85f0..0000000 --- a/shared/src/commonMain/kotlin/org/noiseplanet/noisecapture/shared/ui/theme/Type.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.noiseplanet.noisecapture.shared.ui.theme - -import androidx.compose.material.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = Typography( - body1 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp - ) - /* Other default text styles to override - button = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp - ), - caption = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp - ) - */ -) diff --git a/shared/src/commonMain/kotlin/sqldelight/org/noise_planet/noisecapture/sqldelight/Storage.sq b/shared/src/commonMain/kotlin/sqldelight/org/noise_planet/noisecapture/sqldelight/Storage.sq deleted file mode 100644 index 717b746..0000000 --- a/shared/src/commonMain/kotlin/sqldelight/org/noise_planet/noisecapture/sqldelight/Storage.sq +++ /dev/null @@ -1,21 +0,0 @@ --- record definition -CREATE TABLE record(record_id INTEGER PRIMARY KEY AUTOINCREMENT, record_utc INTEGER, upload_id TEXT, leq_mean REAL, - time_length INTEGER, description TEXT, photo_uri TEXT, pleasantness INTEGER, - calibration_gain REAL DEFAULT 0,noiseparty_tag TEXT, calibration_method INTEGER DEFAULT 0, - microphone_device_id TEXT,microphone_device_settings TEXT); - -CREATE TABLE record_tag(tag_id INTEGER PRIMARY KEY AUTOINCREMENT, tag_system_name TEXT, record_id INTEGER, - FOREIGN KEY(record_id) REFERENCES record(record_id) ON DELETE CASCADE); - -CREATE TABLE leq(record_id INTEGER, leq_id INTEGER PRIMARY KEY AUTOINCREMENT, leq_utc INTEGER, - laeq REAL,lceq REAL, leq_50 REAL,leq_63 REAL,leq_80 REAL,leq_100 REAL,leq_125 REAL, - leq_160 REAL,leq_200 REAL,leq_250 REAL,leq_315 REAL,leq_400 REAL,leq_500 REAL, - leq_630 REAL,leq_800 REAL,leq_1000 REAL,leq_1250 REAL,leq_1600 REAL,leq_2000 REAL, - leq_2500 REAL,leq_3150 REAL,leq_4000 REAL,leq_5000 REAL,leq_6300 REAL,leq_8000 REAL, - leq_10000 REAL,leq_12500 REAL,leq_16000 REAL, FOREIGN KEY(record_id) - REFERENCES record(record_id) ON DELETE CASCADE); - -CREATE TABLE position(position_id INTEGER PRIMARY KEY AUTOINCREMENT, record_id INTEGER, - latitude REAL, longitude REAL, bearing REAL, altitude REAL, speed REAL, accuracy REAL, - location_utc INTEGER, FOREIGN KEY(record_id) REFERENCES record(record_id) ON DELETE CASCADE); - diff --git a/shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/Audio.kt b/shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/Audio.kt deleted file mode 100644 index b72905c..0000000 --- a/shared/src/jsMain/kotlin/org/noiseplanet/noisecapture/Audio.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.noiseplanet.noisecapture - -import js.core.Void -import org.khronos.webgl.Float32Array -import web.media.streams.MediaStream -import kotlin.js.Promise - -external class AudioContext { - val destination: AudioDestinationNode - fun close(): Promise - fun createMediaStreamSource(mediaStream: MediaStream): AudioNode - fun createScriptProcessor( - bufferSize: Int, - numberOfInputChannels: Int, - numberOfOutputChannels: Int - ): ScriptProcessorNode - -} - -external class AudioDestinationNode : - AudioNode { - /** - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AudioDestinationNode/maxChannelCount) - */ - val maxChannelCount: Int -} - -external class AudioBuffer { - val sampleRate: Float - val length: Int - val duration: Double - val numberOfChannels: Int - - fun getChannelData(channel: Int): Float32Array -} - -open external class AudioNode { - fun connect( - destination: AudioNode, output: Int = definedExternally, - input: Int = definedExternally - ): AudioNode - - fun disconnect() - -} - -external class AudioProcessingEvent { - val inputBuffer: AudioBuffer -} - -external class ScriptProcessorNode : AudioNode { - var onaudioprocess: (AudioProcessingEvent) -> Unit -} - diff --git a/shared/src/jsMain/resources/org/noiseplanet/noisecapture/raw_audio_processor.js b/shared/src/jsMain/resources/org/noiseplanet/noisecapture/raw_audio_processor.js deleted file mode 100644 index aa177f4..0000000 --- a/shared/src/jsMain/resources/org/noiseplanet/noisecapture/raw_audio_processor.js +++ /dev/null @@ -1,20 +0,0 @@ - -/** - * @class RawAudioProcessor - * @extends AudioWorkletProcessor - */ -class RawAudioProcessor extends AudioWorkletProcessor { - - constructor() { - super(); - } - - process(inputs, outputs) { - // This example only handles mono channel. - const inputChannelData = inputs[0][0]; - this.port.postMessage(inputChannelData); - return true; - } -} - -registerProcessor("raw_audio_processor", RawAudioProcessor); diff --git a/webApp/build.gradle.kts b/webApp/build.gradle.kts deleted file mode 100644 index a1a1aaf..0000000 --- a/webApp/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - kotlin("multiplatform") - id("org.jetbrains.compose") -} - -kotlin { - js(IR) { - moduleName = "NoiseCaptureWeb" - browser() - binaries.executable() - } - sourceSets { - val commonMain by getting { - dependencies { - implementation(compose.runtime) - implementation(compose.foundation) - implementation(compose.material3) - implementation(compose.ui) - implementation(project(":shared")) - implementation(libs.appyx.navigation) - implementation(libs.appyx.components.backstack) - implementation(libs.koin.core) - } - } - } -} - -compose.experimental { - web.application {} -} diff --git a/webApp/src/jsMain/kotlin/Main.kt b/webApp/src/jsMain/kotlin/Main.kt deleted file mode 100644 index b30f426..0000000 --- a/webApp/src/jsMain/kotlin/Main.kt +++ /dev/null @@ -1,94 +0,0 @@ -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.KeyEvent -import androidx.compose.ui.input.key.KeyEventType -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onKeyEvent -import androidx.compose.ui.input.key.type -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.CanvasBasedWindow -import com.bumble.appyx.navigation.integration.ScreenSize -import com.bumble.appyx.navigation.integration.WebNodeHost -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.launch -import org.jetbrains.skiko.wasm.onWasmReady -import org.koin.core.logger.PrintLogger -import org.koin.dsl.module -import org.noiseplanet.noisecapture.JsAudioSource -import org.noiseplanet.noisecapture.shared.MeasurementService -import org.noiseplanet.noisecapture.shared.initKoin -import org.noiseplanet.noisecapture.shared.root.RootNode -import org.noiseplanet.noisecapture.shared.ui.theme.AppyxStarterKitTheme - -@OptIn(ExperimentalComposeUiApi::class) -fun main() { - val events: Channel = Channel() - onWasmReady { - CanvasBasedWindow() { - AppyxStarterKitTheme { - val requester = remember { FocusRequester() } - var hasFocus by remember { mutableStateOf(false) } - - var screenSize by remember { mutableStateOf(ScreenSize(0.dp, 0.dp)) } - val eventScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.Main) } - val logger = PrintLogger() - WebNodeHost( - screenSize = screenSize, - onBackPressedEvents = events.receiveAsFlow(), - modifier = Modifier - .fillMaxSize() - .onSizeChanged { screenSize = ScreenSize(it.width.dp, it.height.dp) } - .onKeyEvent { event -> - onKeyEvent(event, events, eventScope) - } - .focusRequester(requester) - .focusable() - .onFocusChanged { hasFocus = it.hasFocus }, - ) { buildContext -> - val koinApplication = initKoin(additionalModules = listOf(module { - factory { MeasurementService(JsAudioSource()) } - })).logger(logger) - RootNode( - nodeContext = buildContext, - koin = koinApplication.koin - ) - } - if (!hasFocus) { - LaunchedEffect(Unit) { - requester.requestFocus() - } - } - } - } - } -} - -private fun onKeyEvent( - keyEvent: KeyEvent, - events: Channel, - coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main), -): Boolean = - when { - keyEvent.type == KeyEventType.KeyUp && keyEvent.key == Key.Backspace -> { - coroutineScope.launch { events.send(Unit) } - true - } - - else -> false - } diff --git a/webApp/src/jsMain/resources/index.html b/webApp/src/jsMain/resources/index.html deleted file mode 100644 index ba79a41..0000000 --- a/webApp/src/jsMain/resources/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - NoiseCapture Web App - - - - -
- -
- - - diff --git a/webApp/src/jsMain/resources/styles.css b/webApp/src/jsMain/resources/styles.css deleted file mode 100644 index 8655f2e..0000000 --- a/webApp/src/jsMain/resources/styles.css +++ /dev/null @@ -1,12 +0,0 @@ -#root { - width: 100%; - height: 100vh; -} - -body { - margin: 0; -} - -#root > .compose-web-column > div { - position: relative; -}