diff --git a/examples/src/main/AndroidManifest.xml b/examples/src/main/AndroidManifest.xml
index 0315b1cb48e..213c4d8bc36 100644
--- a/examples/src/main/AndroidManifest.xml
+++ b/examples/src/main/AndroidManifest.xml
@@ -101,6 +101,11 @@
android:screenOrientation="portrait">
+
+
+
diff --git a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt
index dcb54683038..c0132bb7ec3 100644
--- a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt
+++ b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt
@@ -33,6 +33,7 @@ import com.mapbox.navigation.examples.core.databinding.LayoutActivityMainBinding
import com.mapbox.navigation.examples.util.LocationPermissionsHelper
import com.mapbox.navigation.examples.util.LocationPermissionsHelper.Companion.areLocationPermissionsGranted
import com.mapbox.navigation.examples.util.RouteDrawingActivity
+import com.mapbox.navigation.examples.voicefeedback.MapboxVoiceFeedbackActivity
class MainActivity : AppCompatActivity(), PermissionsListener {
@@ -148,6 +149,11 @@ class MainActivity : AppCompatActivity(), PermissionsListener {
getString(R.string.title_adasis),
getString(R.string.description_adasis),
AdasisActivity::class.java
+ ),
+ SampleItem(
+ getString(R.string.title_voice_feedback),
+ getString(R.string.description_voice_feedback),
+ MapboxVoiceFeedbackActivity::class.java
)
)
}
diff --git a/examples/src/main/java/com/mapbox/navigation/examples/voicefeedback/MapboxVoiceFeedbackActivity.kt b/examples/src/main/java/com/mapbox/navigation/examples/voicefeedback/MapboxVoiceFeedbackActivity.kt
new file mode 100644
index 00000000000..ee500f7913d
--- /dev/null
+++ b/examples/src/main/java/com/mapbox/navigation/examples/voicefeedback/MapboxVoiceFeedbackActivity.kt
@@ -0,0 +1,103 @@
+package com.mapbox.navigation.examples.voicefeedback
+
+import android.Manifest
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import com.mapbox.maps.MapboxMap
+import com.mapbox.maps.plugin.locationcomponent.location
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.base.options.NavigationOptions
+import com.mapbox.navigation.core.MapboxNavigation
+import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
+import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
+import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation
+import com.mapbox.navigation.examples.core.databinding.LayoutActivityVoiceFeedbackBinding
+import com.mapbox.navigation.examples.util.Utils
+import com.mapbox.navigation.ui.base.installer.installComponents
+import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
+import com.mapbox.navigation.ui.voicefeedback.voiceFeedbackButton
+
+/**
+ * Example activity demonstrating the [libnavui-voicefeedback](https://docs.mapbox.com/android/navigation/ui/latest/libnavui-voicefeedback/overview/)
+ * SDK. This SDK provides voice feedback capabilities, allowing users to report issues,
+ * suggest improvements, or give feedback directly through voice while navigating.
+ *
+ * It utilizes [MapboxVoiceFeedbackButton] and [FeedbackAgentSession] (via the ComponentInstaller)
+ * to manage the lifecycle and UI states of the voice interaction flow.
+ */
+@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
+class MapboxVoiceFeedbackActivity : AppCompatActivity() {
+
+ private val navigationLocationProvider = NavigationLocationProvider()
+ private lateinit var binding: LayoutActivityVoiceFeedbackBinding
+ private lateinit var mapboxMap: MapboxMap
+ private val mapboxNavigation: MapboxNavigation by requireMapboxNavigation(
+ onResumedObserver = object : MapboxNavigationObserver {
+ override fun onAttached(mapboxNavigation: MapboxNavigation) {
+ }
+
+ override fun onDetached(mapboxNavigation: MapboxNavigation) {
+ }
+ },
+ onInitialize = this::initNavigation
+ )
+
+ private val requestPermissionLauncher = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions ->
+ val areGranted = permissions.values.all { it }
+ if (areGranted) {
+ mapboxNavigation.startTripSession()
+ } else {
+ Toast.makeText(
+ this,
+ "Location and Microphone permissions are required",
+ Toast.LENGTH_LONG
+ ).show()
+ finish()
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = LayoutActivityVoiceFeedbackBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ mapboxMap = binding.mapView.getMapboxMap()
+
+ requestPermissionLauncher.launch(
+ arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.RECORD_AUDIO
+ )
+ )
+ }
+
+ private fun initNavigation() {
+ if (!MapboxNavigationApp.isSetup()) {
+ MapboxNavigationApp.setup(
+ NavigationOptions.Builder(this)
+ .accessToken(Utils.getMapboxAccessToken(this))
+ .build()
+ )
+ }
+
+ MapboxNavigationApp.installComponents(this) {
+ voiceFeedbackButton(binding.voiceFeedbackButton)
+ }
+
+ binding.mapView.location.apply {
+ enabled = true
+ setLocationProvider(navigationLocationProvider)
+ }
+
+ mapboxMap.setCamera(
+ com.mapbox.maps.CameraOptions.Builder()
+ .center(com.mapbox.geojson.Point.fromLngLat(-73.98513, 40.748817)) // New York City
+ .zoom(13.0)
+ .build()
+ )
+ }
+}
diff --git a/examples/src/main/res/layout/layout_activity_voice_feedback.xml b/examples/src/main/res/layout/layout_activity_voice_feedback.xml
new file mode 100644
index 00000000000..dbacd1fc037
--- /dev/null
+++ b/examples/src/main/res/layout/layout_activity_voice_feedback.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/examples/src/main/res/values/strings.xml b/examples/src/main/res/values/strings.xml
index 79afad8bcd0..ca1df0476f2 100644
--- a/examples/src/main/res/values/strings.xml
+++ b/examples/src/main/res/values/strings.xml
@@ -39,6 +39,9 @@
Adasis Example
Demonstrates to to access Adasis functionality.
+ Voice Feedback Example
+ Demonstrates the use of voice feedback API.
+
Start Navigation
Play history
Select history
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 18d8995bd9f..fae0a6494a0 100644
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -141,6 +141,7 @@ ext {
androidStartup : "androidx.startup:startup-runtime:${version.androidStartup}",
androidXFragment : "androidx.fragment:fragment-ktx:${version.androidXFragmentVersion}",
androidXArchCoreTesting : "androidx.arch.core:core-testing:${version.androidXArchCoreVersion}",
+ androidXFragmentTesting : "androidx.fragment:fragment-testing:${version.androidXFragmentVersion}",
androidXDataStore : "androidx.datastore:datastore-preferences:${version.androidXDataStore}",
androidXWorkManager : "androidx.work:work-runtime-ktx:${version.androidXWorkManager}",
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 88366d61fe6..9e30f859f49 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -74,6 +74,7 @@ sdkNameMap["libnavui-tripprogress"] = "mobile-navigation-ui-tripprogress"
sdkNameMap["libnavui-maneuver"] = "mobile-navigation-ui-maneuver"
sdkNameMap["libnavui-resources"] = "mobile-navigation-ui-resources"
sdkNameMap["libnavui-voice"] = "mobile-navigation-ui-voice"
+sdkNameMap["libnavui-voicefeedback"] = "mobile-navigation-ui-voicefeedback"
sdkNameMap["libnavui-speedlimit"] = "mobile-navigation-ui-speedlimit"
sdkNameMap["libnavui-shield"] = "mobile-navigation-ui-shield"
sdkNameMap["libnavui-status"] = "mobile-navigation-ui-status"
diff --git a/libnavigation-android/build.gradle b/libnavigation-android/build.gradle
index 22795f76ce2..e17831df33a 100644
--- a/libnavigation-android/build.gradle
+++ b/libnavigation-android/build.gradle
@@ -34,6 +34,7 @@ dependencies {
api project(":libnavui-speedlimit")
api project(":libnavui-tripprogress")
api project(":libnavui-voice")
+ api project(":libnavui-voicefeedback")
api project(":libnavui-status")
}
diff --git a/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/FeedbackAgentSession.kt b/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/FeedbackAgentSession.kt
index ed67e1c18ef..e006f11ec98 100644
--- a/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/FeedbackAgentSession.kt
+++ b/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/FeedbackAgentSession.kt
@@ -197,7 +197,12 @@ class FeedbackAgentSession private constructor(
engine.disconnect()
}
- private companion object {
+ companion object {
private const val TAG = "FeedbackAgentSession"
+
+ @JvmStatic
+ fun getRegisteredInstance(): FeedbackAgentSession = MapboxNavigationApp
+ .getObservers(FeedbackAgentSession::class)
+ .firstOrNull() ?: Builder().build().also { MapboxNavigationApp.registerObserver(it) }
}
}
diff --git a/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/internal/audio/microphone/AudioRecordMicrophone.kt b/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/internal/audio/microphone/AudioRecordMicrophone.kt
index 8855eb6f004..fd5ef4c5fed 100644
--- a/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/internal/audio/microphone/AudioRecordMicrophone.kt
+++ b/libnavigation-voicefeedback/src/main/java/com/mapbox/navigation/voicefeedback/internal/audio/microphone/AudioRecordMicrophone.kt
@@ -185,7 +185,9 @@ internal class AudioRecordMicrophone : Microphone {
logD(TAG) { "After startRecording: ${AudioInfoRetriever.stateString(audioRecord)}" }
cont.invokeOnCancellation {
logD(TAG) { "stream cancelled, calling audioRecord.stop()" }
- audioRecord.stop()
+ if (audioRecord.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
+ audioRecord.stop()
+ }
}
while (
cont.isActive &&
diff --git a/libnavui-dropin/build.gradle b/libnavui-dropin/build.gradle
index 93bc336a334..fd85ee31756 100644
--- a/libnavui-dropin/build.gradle
+++ b/libnavui-dropin/build.gradle
@@ -48,6 +48,7 @@ dependencies {
api project(":libnavui-speedlimit")
api project(":libnavui-tripprogress")
api project(":libnavui-voice")
+ api project(":libnavui-voicefeedback")
implementation project(":libnavui-app")
api dependenciesList.mapboxSdkServices
diff --git a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/voicefeedback/VoiceFeedbackScreenshotComponentInstaller.kt b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/voicefeedback/VoiceFeedbackScreenshotComponentInstaller.kt
new file mode 100644
index 00000000000..c873e26c2e3
--- /dev/null
+++ b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/voicefeedback/VoiceFeedbackScreenshotComponentInstaller.kt
@@ -0,0 +1,31 @@
+package com.mapbox.navigation.dropin.voicefeedback
+
+import com.mapbox.maps.MapView
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.ui.base.installer.ComponentInstaller
+import com.mapbox.navigation.ui.base.installer.Installation
+import com.mapbox.navigation.ui.maps.util.ViewUtils.capture
+import com.mapbox.navigation.ui.voicefeedback.internal.ScreenshotCapturer
+import com.mapbox.navigation.ui.voicefeedback.internal.VoiceFeedbackComponent
+import com.mapbox.navigation.ui.voicefeedback.view.MapboxVoiceFeedbackButton
+
+/**
+ * Installs the voice feedback button component into the current Mapbox Navigation flow.
+ *
+ * @param mapView The [MapView] that will be used to capture screenshots during feedback. Leave null
+ * if you don't want to capture screenshots.
+ * @param button The visual representation of the voice feedback button.
+ * @return An [Installation] handle that can be used to remove the component.
+ */
+@ExperimentalPreviewMapboxNavigationAPI
+fun ComponentInstaller.voiceFeedbackButton(
+ mapView: MapView? = null,
+ button: MapboxVoiceFeedbackButton,
+): Installation {
+ val screenshotCapturer = mapView?.let {
+ ScreenshotCapturer { callback ->
+ it.capture { screenshot -> callback(screenshot) }
+ }
+ }
+ return component(VoiceFeedbackComponent(button, screenshotCapturer))
+}
diff --git a/libnavui-voicefeedback/.gitignore b/libnavui-voicefeedback/.gitignore
new file mode 100644
index 00000000000..c2a44471591
--- /dev/null
+++ b/libnavui-voicefeedback/.gitignore
@@ -0,0 +1,2 @@
+/build
+src/main/assets/sdk_versions/*
\ No newline at end of file
diff --git a/libnavui-voicefeedback/build.gradle b/libnavui-voicefeedback/build.gradle
new file mode 100644
index 00000000000..904b9f2fbe9
--- /dev/null
+++ b/libnavui-voicefeedback/build.gradle
@@ -0,0 +1,79 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-parcelize'
+apply plugin: 'org.jetbrains.dokka'
+apply plugin: 'com.jaredsburrows.license'
+apply plugin: 'com.mapbox.android.sdk.versions'
+apply from: "../gradle/ktlint.gradle"
+apply from: file("../gradle/artifact-settings.gradle")
+apply from: "../gradle/kdoc-settings.gradle"
+
+version = project.ext.versionName
+group = project.ext.mapboxArtifactGroupId
+
+android {
+ compileSdkVersion androidVersions.compileSdkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ minSdkVersion androidVersions.minSdkVersion
+ targetSdkVersion androidVersions.targetSdkVersion
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'proguard-rules.pro', "../proguard/proguard-project.pro"
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ unitTests.includeAndroidResources = true
+ }
+
+ buildFeatures {
+ viewBinding true
+ }
+}
+
+dependencies {
+ api project(":libnavigation-voicefeedback")
+ api project(":libnavui-base")
+ implementation project(":libnavui-resources")
+
+ api dependenciesList.mapboxSdkServices
+
+ implementation dependenciesList.kotlinStdLib
+
+ implementation dependenciesList.coroutinesAndroid
+
+ // androidX
+ implementation dependenciesList.androidXConstraintLayout
+ implementation dependenciesList.androidXAppCompat
+ implementation dependenciesList.androidXCoreKtx
+ implementation dependenciesList.androidXLifecycleViewmodel
+
+ apply from: "../gradle/unit-testing-dependencies.gradle"
+ testImplementation(project(':libtesting-utils'))
+ testImplementation project(':libtesting-navigation-util')
+ testImplementation dependenciesList.androidXFragmentTesting
+}
+
+dokkaHtmlPartial {
+ outputDirectory.set(kdocPath)
+ moduleName.set("UI Feedback Agent")
+ dokkaSourceSets {
+ configureEach {
+ reportUndocumented.set(true)
+ perPackageOption {
+ matchingRegex.set("com.mapbox.navigation.ui.voicefeedback.internal.*")
+ suppress.set(true)
+ }
+ }
+ }
+}
+
+apply from: "../gradle/track-public-apis.gradle"
+apply from: "../gradle/jacoco.gradle"
+apply from: "../gradle/publish.gradle"
diff --git a/libnavui-voicefeedback/gradle.properties b/libnavui-voicefeedback/gradle.properties
new file mode 100644
index 00000000000..01017c63583
--- /dev/null
+++ b/libnavui-voicefeedback/gradle.properties
@@ -0,0 +1,3 @@
+POM_ARTIFACT_ID=ui-voicefeedback
+POM_ARTIFACT_TITLE=Mapbox Navigation Feedback Agent
+POM_DESCRIPTION=Artifact that provides ability to collect feedback using voice
\ No newline at end of file
diff --git a/libnavui-voicefeedback/proguard-rules.pro b/libnavui-voicefeedback/proguard-rules.pro
new file mode 100644
index 00000000000..481bb434814
--- /dev/null
+++ b/libnavui-voicefeedback/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/AndroidManifest.xml b/libnavui-voicefeedback/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..3dbf7681d34
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/ComponentInstaller.kt b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/ComponentInstaller.kt
new file mode 100644
index 00000000000..0edf6826dac
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/ComponentInstaller.kt
@@ -0,0 +1,19 @@
+package com.mapbox.navigation.ui.voicefeedback
+
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.ui.base.installer.ComponentInstaller
+import com.mapbox.navigation.ui.base.installer.Installation
+import com.mapbox.navigation.ui.voicefeedback.internal.VoiceFeedbackComponent
+import com.mapbox.navigation.ui.voicefeedback.view.MapboxVoiceFeedbackButton
+
+/**
+ * Installs the voice feedback button component into the current Mapbox Navigation flow.
+ *
+ * @param button The visual representation of the voice feedback button.
+ * @return An [Installation] handle that can be used to remove the component.
+ */
+@ExperimentalPreviewMapboxNavigationAPI
+fun ComponentInstaller.voiceFeedbackButton(button: MapboxVoiceFeedbackButton): Installation {
+ return component(VoiceFeedbackComponent(button))
+}
+
diff --git a/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/internal/VoiceFeedbackComponent.kt b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/internal/VoiceFeedbackComponent.kt
new file mode 100644
index 00000000000..ec83af692e6
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/internal/VoiceFeedbackComponent.kt
@@ -0,0 +1,137 @@
+package com.mapbox.navigation.ui.voicefeedback.internal
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.graphics.Bitmap
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.core.MapboxNavigation
+import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback
+import com.mapbox.navigation.core.telemetry.events.FeedbackHelper
+import com.mapbox.navigation.ui.base.lifecycle.UIComponent
+import com.mapbox.navigation.ui.voicefeedback.view.MapboxVoiceFeedbackButton
+import com.mapbox.navigation.ui.voicefeedback.view.MapboxVoiceFeedbackDialog
+import com.mapbox.navigation.ui.voicefeedback.view.VoiceFeedbackViewModel
+import com.mapbox.navigation.utils.internal.logE
+import com.mapbox.navigation.voicefeedback.ASRState
+import com.mapbox.navigation.voicefeedback.postVoiceFeedback
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * Interface for capturing map screenshots to be included with feedback.
+ */
+@ExperimentalPreviewMapboxNavigationAPI
+fun interface ScreenshotCapturer {
+ /**
+ * @param callback Callback to be invoked with the captured screenshot as a [Bitmap].
+ */
+ fun capture(callback: (Bitmap) -> Unit)
+}
+
+/**
+ * UI component that orchestrates the voice feedback flow: button interaction,
+ * [VoiceFeedbackViewModel] lifecycle, ASR dialog, screenshot capture, and feedback posting.
+ *
+ * @param button the voice feedback button view that triggers the flow
+ * @param screenshotCapturer optional screenshot capturer that can be used to capture screenshots
+ * before posting feedback.
+ */
+@ExperimentalPreviewMapboxNavigationAPI
+class VoiceFeedbackComponent(
+ private val button: MapboxVoiceFeedbackButton,
+ private val screenshotCapturer: ScreenshotCapturer? = null,
+) : UIComponent() {
+
+ private var mapboxNavigation: MapboxNavigation? = null
+ private var viewModel: VoiceFeedbackViewModel? = null
+
+ override fun onAttached(mapboxNavigation: MapboxNavigation) {
+ super.onAttached(mapboxNavigation)
+ this.mapboxNavigation = mapboxNavigation
+
+ val activity = button.context.findFragmentActivity() ?: run {
+ logE(TAG) { "Cannot attach: host Activity is not a FragmentActivity" }
+ return
+ }
+ val viewModel = ViewModelProvider(activity)[VoiceFeedbackViewModel::class.java]
+ this.viewModel = viewModel
+
+ coroutineScope.launch {
+ viewModel.state.collect { state ->
+ if (state is ASRState.Result) {
+ postFeedback(state)
+ }
+ }
+ }
+
+ button.setOnClickListener {
+ showDialog()
+ viewModel.onVoiceFeedbackButtonClicked()
+ }
+ }
+
+ private fun showDialog() {
+ val fragmentManager =
+ button.context.findFragmentManager() ?: run {
+ logE(TAG) { "Cannot update dialog: host Activity is not a FragmentActivity" }
+ return
+ }
+
+ MapboxVoiceFeedbackDialog().show(fragmentManager, MapboxVoiceFeedbackDialog.TAG)
+ }
+
+ override fun onDetached(mapboxNavigation: MapboxNavigation) {
+ button.setOnClickListener(null)
+ this.viewModel = null
+ this.mapboxNavigation = null
+ super.onDetached(mapboxNavigation)
+ }
+
+ private suspend fun captureScreenshot(): Bitmap? = suspendCancellableCoroutine { cont ->
+ if (screenshotCapturer != null) {
+ screenshotCapturer.capture {
+ if (cont.isActive) cont.resume(it)
+ }
+ } else {
+ cont.resume(null)
+ }
+ }
+
+ private suspend fun postFeedback(result: ASRState.Result) {
+ val nav = mapboxNavigation ?: return
+ val screenshot = captureScreenshot()?.let {
+ FeedbackHelper.encodeScreenshot(it)
+ } ?: ""
+ nav.postVoiceFeedback(
+ feedbackSubType = result.feedbackType,
+ description = result.text,
+ screenshot = screenshot,
+ userFeedbackCallback = UserFeedbackCallback {/*no-op*/},
+ )
+ }
+
+ private companion object {
+ private const val TAG = "VoiceFeedbackComponent"
+ }
+}
+
+private fun Context.findFragmentManager(): FragmentManager? {
+ var ctx = this
+ while (true) {
+ if (ctx is FragmentActivity) return ctx.supportFragmentManager
+ if (ctx is ContextWrapper) ctx = ctx.baseContext else return null
+ }
+}
+
+private fun Context.findFragmentActivity(): FragmentActivity? {
+ var ctx = this
+ while (true) {
+ if (ctx is FragmentActivity) return ctx
+ if (ctx is ContextWrapper) ctx = ctx.baseContext else return null
+ }
+}
diff --git a/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackButton.kt b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackButton.kt
new file mode 100644
index 00000000000..746dd134755
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackButton.kt
@@ -0,0 +1,82 @@
+package com.mapbox.navigation.ui.voicefeedback.view
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.annotation.StyleRes
+import androidx.annotation.UiThread
+import androidx.core.content.ContextCompat
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.ui.voicefeedback.R
+import com.mapbox.navigation.ui.voicefeedback.databinding.MapboxVoiceFeedbackButtonLayoutBinding
+
+@ExperimentalPreviewMapboxNavigationAPI
+@UiThread
+class MapboxVoiceFeedbackButton : FrameLayout {
+ private val binding =
+ MapboxVoiceFeedbackButtonLayoutBinding.inflate(LayoutInflater.from(context), this)
+
+ val iconImage: ImageView = binding.iconImage
+
+ constructor(context: Context) : this(context, null)
+
+ constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
+
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
+ this(context, attrs, defStyleAttr, R.style.MapboxStyleVoiceFeedbackButton)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes) {
+ context.theme
+ .obtainStyledAttributes(
+ attrs,
+ R.styleable.MapboxVoiceFeedbackButton,
+ defStyleAttr,
+ defStyleRes,
+ ).apply {
+ try {
+ applyAttributes(this)
+ } finally {
+ recycle()
+ }
+ }
+ }
+
+ fun updateStyle(
+ @StyleRes style: Int,
+ ) {
+ context.obtainStyledAttributes(style, R.styleable.MapboxVoiceFeedbackButton).apply {
+ try {
+ applyAttributes(this)
+ } finally {
+ recycle()
+ }
+ }
+ }
+
+ private fun applyAttributes(typedArray: TypedArray) {
+ typedArray
+ .getResourceId(
+ R.styleable.MapboxVoiceFeedbackButton_voiceFeedbackButtonIcon,
+ R.drawable.mapbox_ic_voice_feedback,
+ ).also {
+ iconImage.setImageResource(it)
+ }
+ typedArray
+ .getColorStateList(
+ R.styleable.MapboxVoiceFeedbackButton_voiceFeedbackButtonIconTint,
+ )?.also {
+ iconImage.imageTintList = it
+ }
+ background =
+ typedArray.getDrawable(R.styleable.MapboxVoiceFeedbackButton_voiceFeedbackButtonBackground)
+ ?: ContextCompat.getDrawable(context, R.drawable.mapbox_voice_feedback_bg_button)
+ }
+}
diff --git a/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackDialog.kt b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackDialog.kt
new file mode 100644
index 00000000000..bbe5b8e233c
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackDialog.kt
@@ -0,0 +1,176 @@
+package com.mapbox.navigation.ui.voicefeedback.view
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+import androidx.core.view.isVisible
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.ui.voicefeedback.R
+import com.mapbox.navigation.ui.voicefeedback.databinding.MapboxVoiceFeedbackDialogLayoutBinding
+import com.mapbox.navigation.voicefeedback.ASRState
+import kotlinx.coroutines.launch
+import androidx.core.graphics.drawable.toDrawable
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+
+@ExperimentalPreviewMapboxNavigationAPI
+class MapboxVoiceFeedbackDialog : DialogFragment() {
+
+ private lateinit var binding: MapboxVoiceFeedbackDialogLayoutBinding
+ private lateinit var viewModel: VoiceFeedbackViewModel
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return Dialog(requireContext(), theme)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return MapboxVoiceFeedbackDialogLayoutBinding.inflate(inflater, container, false)
+ .also { binding = it }
+ .root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ viewModel = ViewModelProvider(requireActivity())[VoiceFeedbackViewModel::class.java]
+
+ dialog?.window?.apply {
+ val width = if (resources.isTablet() || resources.configuration.isLandscape()) {
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ } else {
+ ViewGroup.LayoutParams.MATCH_PARENT
+ }
+ setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT)
+ setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
+ setGravity(Gravity.BOTTOM)
+ setFlags(FLAG_NOT_TOUCH_MODAL, FLAG_NOT_TOUCH_MODAL)
+ clearFlags(FLAG_DIM_BEHIND)
+ }
+
+ binding.closeButton.setOnClickListener {
+ dismissAllowingStateLoss()
+ viewModel.onCloseDialogButtonClicked()
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
+ viewModel.state.collectLatest { state -> renderState(state) }
+ }
+ }
+ }
+
+ viewModel.onDialogVisible()
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ viewModel.onDialogDismissed()
+ }
+
+ private suspend fun renderState(state: ASRState?) {
+ when (state) {
+ ASRState.Idle, null -> showContentBlock(
+ icon = R.drawable.mapbox_ic_spinner_anim,
+ header = R.string.mapbox_voice_feedback__share_your_feedback,
+ description = R.string.mapbox_voice_feedback__connecting,
+ )
+ is ASRState.Listening -> showContentBlock(
+ icon = R.drawable.mapbox_ic_mic_pulsing,
+ header = R.string.mapbox_voice_feedback__share_your_feedback,
+ description = R.string.mapbox_voice_feedback__listening,
+ )
+ is ASRState.SpeechFinishedWaitingForResult -> showContentBlock(
+ icon = R.drawable.mapbox_ic_spinner_anim,
+ header = R.string.mapbox_voice_feedback__share_your_feedback,
+ description = R.string.mapbox_voice_feedback__processing,
+ )
+
+ is ASRState.Error,
+ is ASRState.Interrupted,
+ is ASRState.InterruptedByTimeout -> {
+ showInfoBlock(
+ icon = R.drawable.mapbox_ic_error,
+ header = R.string.mapbox_voice_feedback__error_title,
+ description = R.string.mapbox_voice_feedback__error_description,
+ )
+ dismissWithDelay()
+ }
+ is ASRState.NoResult -> {
+ showInfoBlock(
+ icon = R.drawable.mapbox_ic_error,
+ header = R.string.mapbox_voice_feedback__speech_not_recognized_title,
+ description = R.string.mapbox_voice_feedback__error_description,
+ )
+ dismissWithDelay()
+ }
+ is ASRState.Result -> {
+ showInfoBlock(
+ icon = R.drawable.mapbox_ic_ok_sign,
+ header = R.string.mapbox_voice_feedback__success_title,
+ description = R.string.mapbox_voice_feedback__success_description,
+ )
+ dismissWithDelay()
+ }
+ }
+ }
+
+ private fun showContentBlock(
+ icon: Int,
+ header: Int,
+ description: Int,
+ ) = with(binding) {
+ binding.contentBlock.isVisible = true
+ binding.infoBlock.isVisible = false
+ contentIconImage.setImageResource(icon)
+ (contentIconImage.drawable as? AnimatedVectorDrawable)?.start()
+ contentHeader.setText(header)
+ contentDescription.setText(description)
+ }
+
+ private fun showInfoBlock(
+ icon: Int,
+ header: Int,
+ description: Int,
+ ) = with(binding) {
+ binding.contentBlock.isVisible = false
+ binding.infoBlock.isVisible = true
+ infoIcon.setImageResource(icon)
+ infoHeader.setText(header)
+ infoDescription.setText(description)
+ }
+
+ private suspend fun dismissWithDelay(delay: Long = AUTO_DISMISS_DELAY_MS) {
+ delay(delay)
+ dismissAllowingStateLoss()
+ }
+
+ companion object Companion {
+ internal const val TAG = "VoiceFeedbackDialog"
+ private const val AUTO_DISMISS_DELAY_MS = 3_000L
+ }
+}
+
+private fun Resources.isTablet(): Boolean = getBoolean(R.bool.is_tablet)
+
+private fun Configuration.isLandscape(): Boolean = orientation ==
+ Configuration.ORIENTATION_LANDSCAPE
diff --git a/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/VoiceFeedbackViewModel.kt b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/VoiceFeedbackViewModel.kt
new file mode 100644
index 00000000000..becad0ccb10
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/java/com/mapbox/navigation/ui/voicefeedback/view/VoiceFeedbackViewModel.kt
@@ -0,0 +1,51 @@
+package com.mapbox.navigation.ui.voicefeedback.view
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.voicefeedback.ASRState
+import com.mapbox.navigation.voicefeedback.FeedbackAgentSession
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.launch
+
+@ExperimentalPreviewMapboxNavigationAPI
+internal class VoiceFeedbackViewModel(
+ private val session: FeedbackAgentSession = FeedbackAgentSession.getRegisteredInstance(),
+) : ViewModel() {
+
+ val state: StateFlow get() = session.asrState
+
+ private val isDialogVisible = MutableStateFlow(false)
+
+ init {
+ session.connect()
+ }
+
+ fun onVoiceFeedbackButtonClicked() {
+ viewModelScope.launch {
+ // wait until session is idle and dialog is visible before starting microphone
+ combine(session.asrState, isDialogVisible) { state, isDialogVisible ->
+ state to isDialogVisible
+ }.firstOrNull { (state, isDialogVisible) ->
+ state != null && isDialogVisible
+ }
+
+ session.startListening()
+ }
+ }
+
+ fun onCloseDialogButtonClicked() {
+ session.interruptListening()
+ }
+
+ fun onDialogVisible() {
+ isDialogVisible.value = true
+ }
+
+ fun onDialogDismissed() {
+ isDialogVisible.value = false
+ }
+}
diff --git a/libnavui-voicefeedback/src/main/res/animator/mapbox_spinner_path.xml b/libnavui-voicefeedback/src/main/res/animator/mapbox_spinner_path.xml
new file mode 100644
index 00000000000..46793bc8295
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/animator/mapbox_spinner_path.xml
@@ -0,0 +1,9 @@
+
diff --git a/libnavui-voicefeedback/src/main/res/animator/mapbox_spinner_rotation.xml b/libnavui-voicefeedback/src/main/res/animator/mapbox_spinner_rotation.xml
new file mode 100644
index 00000000000..5e5fcf25b5f
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/animator/mapbox_spinner_rotation.xml
@@ -0,0 +1,8 @@
+
diff --git a/libnavui-voicefeedback/src/main/res/animator/pulse_circle_animation.xml b/libnavui-voicefeedback/src/main/res/animator/pulse_circle_animation.xml
new file mode 100644
index 00000000000..17153abe45b
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/animator/pulse_circle_animation.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_bg_close_button_circle.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_bg_close_button_circle.xml
new file mode 100644
index 00000000000..abbf895e00e
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_bg_close_button_circle.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_bg_voice_feedback_dialog.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_bg_voice_feedback_dialog.xml
new file mode 100644
index 00000000000..a704200683f
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_bg_voice_feedback_dialog.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_cancel.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_cancel.xml
new file mode 100644
index 00000000000..f3a448e1d21
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_cancel.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_error.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_error.xml
new file mode 100644
index 00000000000..6798e390bd5
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_error.xml
@@ -0,0 +1,34 @@
+
+
+
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_mic.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_mic.xml
new file mode 100644
index 00000000000..30f98466170
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_mic.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_mic_pulsing.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_mic_pulsing.xml
new file mode 100644
index 00000000000..0f0d9339995
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_mic_pulsing.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_ok_sign.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_ok_sign.xml
new file mode 100644
index 00000000000..1fcce4dbaf7
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_ok_sign.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_spinner_anim.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_spinner_anim.xml
new file mode 100644
index 00000000000..e0f25668516
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_spinner_anim.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_spinner_icon.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_spinner_icon.xml
new file mode 100644
index 00000000000..c0bb62f07f1
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_spinner_icon.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_voice_feedback.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_voice_feedback.xml
new file mode 100644
index 00000000000..add00b49cb2
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_ic_voice_feedback.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/drawable/mapbox_voice_feedback_bg_button.xml b/libnavui-voicefeedback/src/main/res/drawable/mapbox_voice_feedback_bg_button.xml
new file mode 100644
index 00000000000..c90e76ee24d
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/drawable/mapbox_voice_feedback_bg_button.xml
@@ -0,0 +1,10 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/res/layout/mapbox_voice_feedback_button_layout.xml b/libnavui-voicefeedback/src/main/res/layout/mapbox_voice_feedback_button_layout.xml
new file mode 100644
index 00000000000..3914f6b9f1a
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/layout/mapbox_voice_feedback_button_layout.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/layout/mapbox_voice_feedback_dialog_layout.xml b/libnavui-voicefeedback/src/main/res/layout/mapbox_voice_feedback_dialog_layout.xml
new file mode 100644
index 00000000000..fabda323f6b
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/layout/mapbox_voice_feedback_dialog_layout.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libnavui-voicefeedback/src/main/res/values-ar/strings.xml b/libnavui-voicefeedback/src/main/res/values-ar/strings.xml
new file mode 100644
index 00000000000..b1524b41dda
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-ar/strings.xml
@@ -0,0 +1,12 @@
+
+
+ شاركنا ملاحظاتك
+ جارٍ الاستماع...
+ جارٍ المعالجة…
+ شكرًا لمشاركتك
+ ملاحظاتك تساعدنا على التحسن
+ عذرًا! حدث خطأ ما
+ يرجى المحاولة مرة أخرى
+ عذرًا! لم أسمع ذلك جيدًا
+ لا يوجد اتصال
+
diff --git a/libnavui-voicefeedback/src/main/res/values-bg/strings.xml b/libnavui-voicefeedback/src/main/res/values-bg/strings.xml
new file mode 100644
index 00000000000..830b5bb4878
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-bg/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Споделете вашето мнение
+ Слушане...
+ Обработва се…
+ Благодаря, че сподели
+ Вашата обратна връзка ни помага да се подобряваме
+ Опа! Нещо се обърка
+ Моля, опитайте отново
+ Опа! Не разбрах това
+ Няма връзка
+
diff --git a/libnavui-voicefeedback/src/main/res/values-bs-rBA/strings.xml b/libnavui-voicefeedback/src/main/res/values-bs-rBA/strings.xml
new file mode 100644
index 00000000000..ac9d373f2e4
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-bs-rBA/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Podijelite svoje mišljenje
+ Slušanje…
+ Obrada…
+ Hvala što ste podijelili
+ Vaše povratne informacije nam pomažu da se poboljšamo
+ Ups! Nešto je pošlo po zlu
+ Pokušajte ponovo
+ Ups! Nisam to uhvatio/uhvatila
+ Nema veze
+
diff --git a/libnavui-voicefeedback/src/main/res/values-cs/strings.xml b/libnavui-voicefeedback/src/main/res/values-cs/strings.xml
new file mode 100644
index 00000000000..104d8832625
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-cs/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Sdílejte svou zpětnou vazbu
+ Naslouchám…
+ Zpracovává se…
+ Děkuji za sdílení
+ Vaše zpětná vazba nám pomáhá se zlepšovat
+ Jejda! Něco se pokazilo
+ Zkuste to prosím znovu
+ Jejda! To jsem nezachytil.
+ Žádné připojení
+
diff --git a/libnavui-voicefeedback/src/main/res/values-da/strings.xml b/libnavui-voicefeedback/src/main/res/values-da/strings.xml
new file mode 100644
index 00000000000..7b80a0d4cc0
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-da/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Del din feedback
+ Lytter…
+ Behandler…
+ Tak fordi du delte
+ Din feedback hjælper os med at blive bedre
+ Ups! Noget gik galt
+ Prøv venligst igen
+ Ups! Det fangede jeg ikke
+ Ingen forbindelse
+
diff --git a/libnavui-voicefeedback/src/main/res/values-de/strings.xml b/libnavui-voicefeedback/src/main/res/values-de/strings.xml
new file mode 100644
index 00000000000..c3f1fd26f0b
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-de/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Teilen Sie Ihr Feedback
+ Zuhören…
+ Verarbeitung…
+ Danke fürs Teilen
+ Ihr Feedback hilft uns, uns zu verbessern
+ Hoppla! Etwas ist schiefgelaufen
+ Bitte versuchen Sie es erneut
+ Hoppla! Das habe ich nicht verstanden
+ Keine Verbindung
+
diff --git a/libnavui-voicefeedback/src/main/res/values-el/strings.xml b/libnavui-voicefeedback/src/main/res/values-el/strings.xml
new file mode 100644
index 00000000000..af29356f5ae
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-el/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Μοιραστείτε τα σχόλιά σας
+ Ακρόαση…
+ Γίνεται επεξεργασία…
+ Ευχαριστώ που το μοιράστηκες
+ Τα σχόλιά σας μας βοηθούν να βελτιωνόμαστε
+ Ουπς! Κάτι πήγε στραβά
+ Παρακαλώ δοκιμάστε ξανά
+ Ουπς! Δεν το κατάλαβα αυτό
+ Χωρίς Σύνδεση
+
diff --git a/libnavui-voicefeedback/src/main/res/values-en-rAU/strings.xml b/libnavui-voicefeedback/src/main/res/values-en-rAU/strings.xml
new file mode 100644
index 00000000000..4639bb4ed58
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-en-rAU/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Share your feedback
+ Listening…
+ Processing…
+ Thanks for sharing
+ Your feedback helps us improve
+ Whoops! Something went wrong
+ Please try again
+ Whoops! Didn’t quite catch that
+ No Connection
+
diff --git a/libnavui-voicefeedback/src/main/res/values-en-rGB/strings.xml b/libnavui-voicefeedback/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 00000000000..ece9c5ddb7b
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Share your feedback
+ Listening…
+ Processing…
+ Thanks for sharing
+ Your feedback helps us to improve
+ Oops! Something went wrong
+ Please try again
+ Oops! Didn’t catch that
+ No Connection
+
diff --git a/libnavui-voicefeedback/src/main/res/values-en-rIN/strings.xml b/libnavui-voicefeedback/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 00000000000..38fe6049264
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,12 @@
+
+
+ अपना फीडबैक साझा करें
+ सुन रहे हैं…
+ प्रोसेसिंग…
+ Thank you for sharing.
+ Your feedback helps us improve
+ अरे! कुछ गड़बड़ हो गई है
+ कृपया फिर से प्रयास करें
+ अरे! वह समझ में नहीं आया
+ कोई कनेक्शन नहीं
+
diff --git a/libnavui-voicefeedback/src/main/res/values-en-rSG/strings.xml b/libnavui-voicefeedback/src/main/res/values-en-rSG/strings.xml
new file mode 100644
index 00000000000..0154eb81ae4
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-en-rSG/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Kongsi maklum balas anda
+ Sedang mendengar…
+ Sedang diproses…
+ Thanks for sharing
+ Your feedback helps us improve
+ Aiyo! Something went wrong lah
+ Please try again
+ Aiyo! Didn’t catch that leh
+ No Connection
+
diff --git a/libnavui-voicefeedback/src/main/res/values-en-rUS/strings.xml b/libnavui-voicefeedback/src/main/res/values-en-rUS/strings.xml
new file mode 100644
index 00000000000..277595a9b99
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-en-rUS/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Share your feedback
+ Listening…
+ Processing…
+ Thanks for sharing
+ Your feedback helps us improve
+ Oops! Something went wrong
+ Please try again
+ Oops! Didn’t catch that
+ No Connection
+
diff --git a/libnavui-voicefeedback/src/main/res/values-es-rES/strings.xml b/libnavui-voicefeedback/src/main/res/values-es-rES/strings.xml
new file mode 100644
index 00000000000..1061d118fa5
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-es-rES/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Comparte tus comentarios
+ Escuchando…
+ Procesando…
+ Gracias por compartir
+ Tus comentarios nos ayudan a mejorar
+ ¡Vaya! Algo salió mal
+ Por favor, inténtalo de nuevo
+ ¡Ups! No entendí eso
+ Sin conexión
+
diff --git a/libnavui-voicefeedback/src/main/res/values-es-rMX/strings.xml b/libnavui-voicefeedback/src/main/res/values-es-rMX/strings.xml
new file mode 100644
index 00000000000..4d337a13b0b
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-es-rMX/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Comparte tus comentarios
+ Escuchando…
+ Procesando…
+ Gracias por compartir
+ Tus comentarios nos ayudan a mejorar
+ ¡Ups! Algo salió mal
+ Por favor, inténtalo de nuevo
+ ¡Ups! No entendí eso
+ Sin conexión
+
diff --git a/libnavui-voicefeedback/src/main/res/values-es/strings.xml b/libnavui-voicefeedback/src/main/res/values-es/strings.xml
new file mode 100644
index 00000000000..664f3725a5c
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-es/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Comparte tus comentarios
+ Escuchando…
+ Procesando…
+ Gracias por compartir
+ Tus comentarios nos ayudan a mejorar
+ ¡Ups! Algo salió mal
+ Por favor intenta de nuevo
+ ¡Ups! No entendí eso
+ Sin conexión
+
diff --git a/libnavui-voicefeedback/src/main/res/values-et/strings.xml b/libnavui-voicefeedback/src/main/res/values-et/strings.xml
new file mode 100644
index 00000000000..a0a1d5845e9
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-et/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Jaga oma tagasisidet
+ Kuulan…
+ Töötlemine…
+ Aitäh jagamast
+ Teie tagasiside aitab meil paremaks saada
+ Oih! Midagi läks valesti
+ Palun proovige uuesti
+ Oih! Ei saanud sellest aru
+ Ühendus puudub
+
diff --git a/libnavui-voicefeedback/src/main/res/values-fi/strings.xml b/libnavui-voicefeedback/src/main/res/values-fi/strings.xml
new file mode 100644
index 00000000000..01d884bfc2c
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-fi/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Jaa palautteesi
+ Kuunnellaan…
+ Käsitellään…
+ Kiitos jakamisesta
+ Palautteesi auttaa meitä parantamaan toimintaamme
+ Hups! Jotain meni pieleen
+ Yritä uudelleen
+ Hups! En saanut siitä selvää
+ Ei yhteyttä
+
diff --git a/libnavui-voicefeedback/src/main/res/values-fr-rCA/strings.xml b/libnavui-voicefeedback/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 00000000000..5b83ee067f5
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Partagez vos commentaires
+ Écoute en cours…
+ Traitement en cours…
+ Merci pour le partage
+ Vos commentaires nous aident à nous améliorer
+ Oups ! Quelque chose s\'est mal passé
+ Veuillez réessayer
+ Oups ! Je n’ai pas compris.
+ Pas de connexion
+
diff --git a/libnavui-voicefeedback/src/main/res/values-fr/strings.xml b/libnavui-voicefeedback/src/main/res/values-fr/strings.xml
new file mode 100644
index 00000000000..8b2bab11ff2
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-fr/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Partagez vos commentaires
+ Écoute en cours…
+ Traitement en cours…
+ Merci pour le partage
+ Vos commentaires nous aident à nous améliorer
+ Oups ! Quelque chose s\'est mal passé
+ Veuillez réessayer
+ Oups ! Je n\'ai pas compris.
+ Pas de connexion
+
diff --git a/libnavui-voicefeedback/src/main/res/values-hr/strings.xml b/libnavui-voicefeedback/src/main/res/values-hr/strings.xml
new file mode 100644
index 00000000000..95a49ffec6d
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-hr/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Podijelite svoje povratne informacije
+ Slušanje…
+ Obrada…
+ Hvala što ste podijelili
+ Vaše povratne informacije pomažu nam da se poboljšamo
+ Ups! Nešto je pošlo po zlu
+ Pokušajte ponovno
+ Ups! Nisam to uhvatio/la
+ Nema veze
+
diff --git a/libnavui-voicefeedback/src/main/res/values-hu/strings.xml b/libnavui-voicefeedback/src/main/res/values-hu/strings.xml
new file mode 100644
index 00000000000..8c18dbd1ebc
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-hu/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Ossza meg visszajelzését
+ Hallgatás…
+ Feldolgozás…
+ Köszönöm, hogy megosztottad
+ Az Ön visszajelzése segít nekünk a fejlődésben
+ Hoppá! Valami hiba történt
+ Kérjük, próbálja meg újra
+ Hoppá! Ezt nem értettem.
+ Nincs kapcsolat
+
diff --git a/libnavui-voicefeedback/src/main/res/values-in/strings.xml b/libnavui-voicefeedback/src/main/res/values-in/strings.xml
new file mode 100644
index 00000000000..8cd1e88f598
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-in/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Bagikan masukan Anda
+ Mendengarkan…
+ Memproses…
+ Terima kasih sudah berbagi
+ Masukan Anda membantu kami untuk menjadi lebih baik
+ Ups! Terjadi kesalahan
+ Silakan coba lagi
+ Ups! Saya tidak menangkap itu
+ Tidak Ada Koneksi
+
diff --git a/libnavui-voicefeedback/src/main/res/values-is/strings.xml b/libnavui-voicefeedback/src/main/res/values-is/strings.xml
new file mode 100644
index 00000000000..5c96252d2c2
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-is/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Deildu ábendingum þínum
+ Hlusta…
+ Í vinnslu…
+ Takk fyrir að deila þessu
+ Endurgjöf þín hjálpar okkur að bæta þjónustuna.
+ Úbbs! Eitthvað fór úrskeiðis
+ Vinsamlegast reyndu aftur
+ Úbbs! Ég náði því ekki
+ Engin tenging
+
diff --git a/libnavui-voicefeedback/src/main/res/values-it/strings.xml b/libnavui-voicefeedback/src/main/res/values-it/strings.xml
new file mode 100644
index 00000000000..0e4febd6757
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-it/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Condividi il tuo feedback
+ In ascolto…
+ Elaborazione in corso…
+ Grazie per aver condiviso
+ Il tuo feedback ci aiuta a migliorare
+ Ops! Qualcosa è andato storto
+ Per favore riprova
+ Ops! Non ho capito
+ Nessuna connessione
+
diff --git a/libnavui-voicefeedback/src/main/res/values-iw/strings.xml b/libnavui-voicefeedback/src/main/res/values-iw/strings.xml
new file mode 100644
index 00000000000..d3374829ae2
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-iw/strings.xml
@@ -0,0 +1,12 @@
+
+
+ שתפו את המשוב שלכם
+ מקשיב…
+ מעבד…
+ תודה ששיתפת
+ המשוב שלך עוזר לנו להשתפר
+ אופס! משהו השתבש
+ אנא נסה שוב
+ אופס! לא קלטתי את זה
+ אין חיבור
+
diff --git a/libnavui-voicefeedback/src/main/res/values-ja-rJP/strings.xml b/libnavui-voicefeedback/src/main/res/values-ja-rJP/strings.xml
new file mode 100644
index 00000000000..901e88aab0b
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-ja-rJP/strings.xml
@@ -0,0 +1,12 @@
+
+
+ ご意見をお聞かせください
+ 聞き取り中…
+ 処理中…
+ 共有してくれてありがとうございます
+ ご意見は、私たちの改善に役立ちます。
+ おっと!何か問題が発生しました
+ もう一度お試しください
+ おっと!聞き取れませんでした
+ 接続なし
+
diff --git a/libnavui-voicefeedback/src/main/res/values-ja/strings.xml b/libnavui-voicefeedback/src/main/res/values-ja/strings.xml
new file mode 100644
index 00000000000..a2543c95525
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-ja/strings.xml
@@ -0,0 +1,12 @@
+
+
+ ご意見をお聞かせください
+ 聞き取り中…
+ 処理中…
+ 共有してくれてありがとうございます
+ あなたのフィードバックは、私たちの改善に役立ちます。
+ おっと!何か問題が発生しました
+ もう一度お試しください
+ おっと!聞き取れませんでした
+ 接続なし
+
diff --git a/libnavui-voicefeedback/src/main/res/values-ko/strings.xml b/libnavui-voicefeedback/src/main/res/values-ko/strings.xml
new file mode 100644
index 00000000000..b9cb50b60cb
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-ko/strings.xml
@@ -0,0 +1,12 @@
+
+
+ 의견을 공유해 주세요
+ 듣는 중…
+ 처리 중…
+ 공유해 주셔서 감사합니다
+ 귀하의 피드백은 저희가 개선하는 데 도움이 됩니다
+ 이런! 문제가 발생했습니다
+ 다시 시도해 주세요
+ 이런! 잘 못 들었어요
+ 연결 없음
+
diff --git a/libnavui-voicefeedback/src/main/res/values-lt/strings.xml b/libnavui-voicefeedback/src/main/res/values-lt/strings.xml
new file mode 100644
index 00000000000..45bf46817a8
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-lt/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Pasidalinkite savo atsiliepimais
+ Klausoma…
+ Apdorojama…
+ Ačiū, kad pasidalinote
+ Jūsų atsiliepimai padeda mums tobulėti
+ Oi! Kažkas nepavyko
+ Pabandykite dar kartą
+ Oi! Nepagavau to
+ Nėra ryšio
+
diff --git a/libnavui-voicefeedback/src/main/res/values-lv/strings.xml b/libnavui-voicefeedback/src/main/res/values-lv/strings.xml
new file mode 100644
index 00000000000..acf5716c236
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-lv/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Dalieties ar savu atsauksmi
+ Klausās…
+ Notiek apstrāde…
+ Paldies, ka dalījāties
+ Jūsu atsauksmes palīdz mums uzlaboties
+ Ak! Kaut kas nogāja greizi
+ Lūdzu, mēģiniet vēlreiz
+ Ups! Nesapratu to
+ Nav savienojuma
+
diff --git a/libnavui-voicefeedback/src/main/res/values-ms/strings.xml b/libnavui-voicefeedback/src/main/res/values-ms/strings.xml
new file mode 100644
index 00000000000..7ed30097ff8
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-ms/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Kongsi maklum balas anda
+ Sedang mendengar…
+ Memproses…
+ Terima kasih kerana berkongsi
+ Maklum balas anda membantu kami untuk menambah baik
+ Oops! Sesuatu telah berlaku kesilapan
+ Sila cuba lagi
+ Oops! Tidak dapat menangkap itu
+ Tiada Sambungan
+
diff --git a/libnavui-voicefeedback/src/main/res/values-nb/strings.xml b/libnavui-voicefeedback/src/main/res/values-nb/strings.xml
new file mode 100644
index 00000000000..c32d8d29d7b
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-nb/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Del dine tilbakemeldinger
+ Lytter…
+ Behandler …
+ Takk for at du delte
+ Din tilbakemelding hjelper oss å bli bedre
+ Oops! Noe gikk galt
+ Vennligst prøv igjen
+ Oi! Fikk ikke med meg det
+ Ingen tilkobling
+
diff --git a/libnavui-voicefeedback/src/main/res/values-night/colors.xml b/libnavui-voicefeedback/src/main/res/values-night/colors.xml
new file mode 100644
index 00000000000..51ca900bc4e
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-night/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #181B20
+ #292C33
+ #EDEFF2
+ #A6B2C6
+ #EDEFF2
+ #0BDA95
+ #EA3136
+
diff --git a/libnavui-voicefeedback/src/main/res/values-nl-rBE/strings.xml b/libnavui-voicefeedback/src/main/res/values-nl-rBE/strings.xml
new file mode 100644
index 00000000000..558ba73583c
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-nl-rBE/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Deel je feedback
+ Luisteren...
+ Bezig met verwerken…
+ Bedankt om dit te delen
+ Uw feedback helpt ons om te verbeteren
+ Oeps! Er is iets misgegaan
+ Probeer het opnieuw
+ Oeps! Dat heb ik niet begrepen
+ Geen verbinding
+
diff --git a/libnavui-voicefeedback/src/main/res/values-nl-rNL/strings.xml b/libnavui-voicefeedback/src/main/res/values-nl-rNL/strings.xml
new file mode 100644
index 00000000000..a7437c6202e
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-nl-rNL/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Deel je feedback
+ Luisteren...
+ Bezig met verwerken…
+ Bedankt voor het delen
+ Uw feedback helpt ons te verbeteren
+ Oeps! Er is iets misgegaan
+ Probeer het opnieuw
+ Oeps! Dat heb ik niet begrepen
+ Geen verbinding
+
diff --git a/libnavui-voicefeedback/src/main/res/values-nl/strings.xml b/libnavui-voicefeedback/src/main/res/values-nl/strings.xml
new file mode 100644
index 00000000000..823c05765ae
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-nl/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Deel uw feedback
+ Luisteren...
+ Bezig met verwerken…
+ Bedankt voor het delen
+ Uw feedback helpt ons te verbeteren
+ Oeps! Er is iets misgegaan
+ Probeer het opnieuw
+ Oeps! Dat heb ik niet begrepen
+ Geen verbinding
+
diff --git a/libnavui-voicefeedback/src/main/res/values-no-rNO/strings.xml b/libnavui-voicefeedback/src/main/res/values-no-rNO/strings.xml
new file mode 100644
index 00000000000..999866676f7
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-no-rNO/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Del tilbakemeldingen din
+ Lytter…
+ Behandler …
+ Takk for at du delte
+ Din tilbakemelding hjelper oss å bli bedre
+ Oi! Noe gikk galt
+ Prøv igjen
+ Uff da! Fikk ikke med meg det
+ Ingen tilkobling
+
diff --git a/libnavui-voicefeedback/src/main/res/values-pl/strings.xml b/libnavui-voicefeedback/src/main/res/values-pl/strings.xml
new file mode 100644
index 00000000000..93f236725a5
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-pl/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Podziel się swoją opinią
+ Słucham…
+ Przetwarzanie…
+ Dziękuję za udostępnienie
+ Twoja opinia pomaga nam się rozwijać
+ Ups! Coś poszło nie tak
+ Proszę spróbować ponownie
+ Ups! Nie zrozumiałem tego.
+ Brak połączenia
+
diff --git a/libnavui-voicefeedback/src/main/res/values-pt-rBR/strings.xml b/libnavui-voicefeedback/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 00000000000..e8f48d2f414
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Compartilhe seu feedback
+ Ouvindo...
+ Processando…
+ Obrigado por compartilhar
+ Seu feedback nos ajuda a melhorar
+ Ops! Algo deu errado
+ Por favor, tente novamente
+ Ops! Não entendi isso
+ Sem Conexão
+
diff --git a/libnavui-voicefeedback/src/main/res/values-pt/strings.xml b/libnavui-voicefeedback/src/main/res/values-pt/strings.xml
new file mode 100644
index 00000000000..e8f48d2f414
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-pt/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Compartilhe seu feedback
+ Ouvindo...
+ Processando…
+ Obrigado por compartilhar
+ Seu feedback nos ajuda a melhorar
+ Ops! Algo deu errado
+ Por favor, tente novamente
+ Ops! Não entendi isso
+ Sem Conexão
+
diff --git a/libnavui-voicefeedback/src/main/res/values-ro/strings.xml b/libnavui-voicefeedback/src/main/res/values-ro/strings.xml
new file mode 100644
index 00000000000..010d76a28d8
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-ro/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Trimiteți feedback-ul dvs.
+ Ascultare…
+ Se procesează…
+ Mulțumesc că ai împărtășit
+ Feedback-ul dumneavoastră ne ajută să ne îmbunătățim
+ Ups! Ceva nu a mers bine
+ Vă rugăm să încercați din nou
+ Ups! Nu am prins asta
+ Fără conexiune
+
diff --git a/libnavui-voicefeedback/src/main/res/values-ru/strings.xml b/libnavui-voicefeedback/src/main/res/values-ru/strings.xml
new file mode 100644
index 00000000000..d6c83a23740
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-ru/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Поделитесь своим мнением
+ Прослушивание…
+ Обработка…
+ Спасибо за то, что поделились
+ Ваш отзыв помогает нам совершенствоваться
+ Ой! Что-то пошло не так
+ Пожалуйста, попробуйте еще раз
+ Ой! Не расслышал
+ Нет подключения
+
diff --git a/libnavui-voicefeedback/src/main/res/values-sk/strings.xml b/libnavui-voicefeedback/src/main/res/values-sk/strings.xml
new file mode 100644
index 00000000000..2ef807a0d08
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-sk/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Podeľte sa o svoju spätnú väzbu
+ Počúvam…
+ Spracováva sa…
+ Ďakujeme za zdieľanie
+ Vaša spätná väzba nám pomáha zlepšovať sa
+ Ups! Niečo sa pokazilo
+ Skúste to prosím znova
+ Ups! Nezachytil som to
+ Žiadne pripojenie
+
diff --git a/libnavui-voicefeedback/src/main/res/values-sl/strings.xml b/libnavui-voicefeedback/src/main/res/values-sl/strings.xml
new file mode 100644
index 00000000000..281fc5d6f5f
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-sl/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Delite svoje mnenje
+ Poslušanje…
+ Obdelava …
+ Hvala, da ste delili
+ Vaše povratne informacije nam pomagajo izboljšati naše storitve
+ Ups! Nekaj je šlo narobe
+ Poskusite znova
+ Ups! Tega nisem razumel
+ Ni povezave
+
diff --git a/libnavui-voicefeedback/src/main/res/values-sq/strings.xml b/libnavui-voicefeedback/src/main/res/values-sq/strings.xml
new file mode 100644
index 00000000000..d66e70ac6c4
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-sq/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Ndani mendimet tuaja
+ Duke dëgjuar…
+ Duke përpunuar…
+ Faleminderit që e ndave
+ Mendimet tuaja na ndihmojnë të përmirësohemi
+ Ups! Diçka shkoi keq
+ Ju lutemi provoni përsëri
+ Ups! Nuk e kapa atë
+ Pa lidhje
+
diff --git a/libnavui-voicefeedback/src/main/res/values-sr/strings.xml b/libnavui-voicefeedback/src/main/res/values-sr/strings.xml
new file mode 100644
index 00000000000..03685abb217
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-sr/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Podelite svoje mišljenje
+ Слушање…
+ Обрада…
+ Hvala što ste podelili
+ Vaše povratne informacije nam pomažu da se poboljšamo
+ Упс! Нешто је пошло наопако
+ Pokušajte ponovo
+ Ups! Nisam to uhvatio.
+ Nema veze
+
diff --git a/libnavui-voicefeedback/src/main/res/values-sv/strings.xml b/libnavui-voicefeedback/src/main/res/values-sv/strings.xml
new file mode 100644
index 00000000000..45528620410
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-sv/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Dela din feedback
+ Lyssnar…
+ Bearbetar…
+ Tack för att du delade med dig
+ Din feedback hjälper oss att bli bättre
+ Hoppsan! Något gick fel
+ Var god försök igen
+ Hoppsan! Det uppfattade jag inte
+ Ingen anslutning
+
diff --git a/libnavui-voicefeedback/src/main/res/values-sw600dp-land/dimens.xml b/libnavui-voicefeedback/src/main/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 00000000000..3be3c2a351d
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,30 @@
+
+
+
+ 90dp
+ 40dp
+
+
+ 540dp
+ 24dp
+ 12dp
+
+
+ 24dp
+
+
+ 60dp
+ 36dp
+
+
+ 32sp
+ 160dp
+ 28sp
+ 12dp
+
+
+ 80dp
+ 20dp
+ 8dp
+ 14dp
+
diff --git a/libnavui-voicefeedback/src/main/res/values-sw600dp/bools.xml b/libnavui-voicefeedback/src/main/res/values-sw600dp/bools.xml
new file mode 100644
index 00000000000..5fc3a0d0dfa
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-sw600dp/bools.xml
@@ -0,0 +1,4 @@
+
+
+ true
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/res/values-th/strings.xml b/libnavui-voicefeedback/src/main/res/values-th/strings.xml
new file mode 100644
index 00000000000..14647b675ca
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-th/strings.xml
@@ -0,0 +1,12 @@
+
+
+ แบ่งปันความคิดเห็นของคุณ
+ กำลังฟัง...
+ กำลังประมวลผล…
+ ขอบคุณที่แบ่งปัน
+ ความคิดเห็นของคุณช่วยให้เราพัฒนาขึ้น
+ ขออภัย! เกิดข้อผิดพลาดบางอย่าง
+ กรุณาลองใหม่อีกครั้ง
+ อุ๊ย! ฟังไม่ทันค่ะ
+ ไม่มีการเชื่อมต่อ
+
diff --git a/libnavui-voicefeedback/src/main/res/values-tr/strings.xml b/libnavui-voicefeedback/src/main/res/values-tr/strings.xml
new file mode 100644
index 00000000000..4770c394808
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-tr/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Görüşlerinizi paylaşın
+ Dinleniyor…
+ İşleniyor…
+ Paylaştığınız için teşekkürler
+ Geri bildiriminiz bize gelişmemizde yardımcı oluyor
+ Hata! Bir şeyler ters gitti
+ Lütfen tekrar deneyin
+ Hata! Bunu anlayamadım
+ Bağlantı Yok
+
diff --git a/libnavui-voicefeedback/src/main/res/values-uk/strings.xml b/libnavui-voicefeedback/src/main/res/values-uk/strings.xml
new file mode 100644
index 00000000000..1e84acc2e0f
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-uk/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Поділіться своїми відгуками
+ Слухаю…
+ Обробка…
+ Дякую, що поділилися
+ Ваш відгук допомагає нам ставати кращими
+ Ой! Щось пішло не так
+ Будь ласка, спробуйте ще раз
+ Ой! Не зрозумів(ла) цього
+ Немає з\'єднання
+
diff --git a/libnavui-voicefeedback/src/main/res/values-vi/strings.xml b/libnavui-voicefeedback/src/main/res/values-vi/strings.xml
new file mode 100644
index 00000000000..d8900a2871a
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-vi/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Chia sẻ phản hồi của bạn
+ Đang lắng nghe…
+ Đang xử lý…
+ Cảm ơn bạn đã chia sẻ
+ Phản hồi của bạn giúp chúng tôi cải thiện
+ Ôi! Đã xảy ra sự cố
+ Vui lòng thử lại
+ Ôi! Tôi không nghe rõ điều đó
+ Không có kết nối
+
diff --git a/libnavui-voicefeedback/src/main/res/values-zh-rCN/strings.xml b/libnavui-voicefeedback/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 00000000000..e9ee4feb873
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,12 @@
+
+
+ 分享您的反馈
+ 正在聆听…
+ 处理中…
+ 感谢分享
+ 您的反馈有助于我们改进
+ 哎呀!出了点问题
+ 请再试一次
+ 哎呀!没听清楚
+ 无连接
+
diff --git a/libnavui-voicefeedback/src/main/res/values-zh-rHK/strings.xml b/libnavui-voicefeedback/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 00000000000..d835cbc6cb0
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,12 @@
+
+
+ 分享你的意見
+ 正在聆聽…
+ 處理中…
+ 多謝分享
+ 您的意見有助我們改進
+ 哎呀!出現了一些問題
+ 請再試一次
+ 哎呀!我聽唔清楚你講咩
+ 無連接
+
diff --git a/libnavui-voicefeedback/src/main/res/values-zh-rTW/strings.xml b/libnavui-voicefeedback/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 00000000000..e393314b135
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,12 @@
+
+
+ 分享您的意見
+ 聆聽中…
+ 處理中…
+ 感謝分享
+ 您的回饋幫助我們改進
+ 哎呀!出了點問題
+ 請再試一次
+ 哎呀!沒聽清楚
+ 無連線
+
diff --git a/libnavui-voicefeedback/src/main/res/values/attrs.xml b/libnavui-voicefeedback/src/main/res/values/attrs.xml
new file mode 100644
index 00000000000..6c99fc09670
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values/attrs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/res/values/bools.xml b/libnavui-voicefeedback/src/main/res/values/bools.xml
new file mode 100644
index 00000000000..84d7d2f5cf2
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values/bools.xml
@@ -0,0 +1,4 @@
+
+
+ false
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/res/values/colors.xml b/libnavui-voicefeedback/src/main/res/values/colors.xml
new file mode 100644
index 00000000000..f53c296e5f0
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values/colors.xml
@@ -0,0 +1,11 @@
+
+
+ #FFFFFF
+ #F2F4F7
+ #05070A
+ #536275
+ #23262D
+ #3072F5
+ #09AA74
+ #EB252A
+
diff --git a/libnavui-voicefeedback/src/main/res/values/dimens.xml b/libnavui-voicefeedback/src/main/res/values/dimens.xml
new file mode 100644
index 00000000000..79ec458700d
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values/dimens.xml
@@ -0,0 +1,30 @@
+
+
+
+ 60dp
+ 28dp
+
+
+ 340dp
+ 16dp
+ 8dp
+
+
+ 16dp
+
+
+ 40dp
+ 32dp
+
+
+ 20sp
+ 120dp
+ 18sp
+ 4dp
+
+
+ 48dp
+ 16dp
+ 4dp
+ 16dp
+
diff --git a/libnavui-voicefeedback/src/main/res/values/strings.xml b/libnavui-voicefeedback/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..80d021de80a
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Share your feedback
+ Listening…
+ Processing…
+ Thanks for sharing
+ Your feedback helps us improve
+ Oops! Something went wrong
+ Please try again
+ Oops! Didn’t catch that
+ No Connection
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/main/res/values/styles.xml b/libnavui-voicefeedback/src/main/res/values/styles.xml
new file mode 100644
index 00000000000..3fb01622607
--- /dev/null
+++ b/libnavui-voicefeedback/src/main/res/values/styles.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libnavui-voicefeedback/src/test/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackDialogTest.kt b/libnavui-voicefeedback/src/test/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackDialogTest.kt
new file mode 100644
index 00000000000..0d3b9ee85df
--- /dev/null
+++ b/libnavui-voicefeedback/src/test/java/com/mapbox/navigation/ui/voicefeedback/view/MapboxVoiceFeedbackDialogTest.kt
@@ -0,0 +1,86 @@
+package com.mapbox.navigation.ui.voicefeedback.view
+
+import androidx.fragment.app.FragmentActivity
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.voicefeedback.ASRState
+import com.mapbox.navigation.voicefeedback.FeedbackAgentSession
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkConstructor
+import io.mockk.unmockkConstructor
+import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows
+import org.robolectric.android.controller.ActivityController
+
+@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
+@RunWith(RobolectricTestRunner::class)
+class MapboxVoiceFeedbackDialogTest {
+
+ private lateinit var activityController: ActivityController
+ private lateinit var mockSession: FeedbackAgentSession
+ private lateinit var asrStateFlow: MutableStateFlow
+
+ @Before
+ fun setUp() {
+ mockkConstructor(FeedbackAgentSession.Builder::class)
+ asrStateFlow = MutableStateFlow(ASRState.Idle)
+ val builder = mockk()
+ mockSession = mockk(relaxUnitFun = true)
+ every { mockSession.asrState } returns asrStateFlow
+ every { anyConstructed().build() } returns mockSession
+ every { anyConstructed().options(any()) } returns builder
+ every { builder.options(any()) } returns builder
+ every { builder.build() } returns mockSession
+
+ activityController = Robolectric.buildActivity(FragmentActivity::class.java)
+ activityController.create().start().resume()
+ }
+
+ @After
+ fun tearDown() {
+ activityController.pause().stop().destroy()
+ unmockkConstructor(FeedbackAgentSession.Builder::class)
+ }
+
+ @Test
+ fun `startListening not called when session already in non-idle state`() {
+ asrStateFlow.value = ASRState.Listening("test")
+ every { mockSession.startListening() } just Runs
+
+ val activity = activityController.get()
+ MapboxVoiceFeedbackDialog().show(
+ activity.supportFragmentManager,
+ MapboxVoiceFeedbackDialog.TAG
+ )
+
+ // startListening should NOT be called because current state is Listening, not Idle
+ verify(exactly = 0) { mockSession.startListening() }
+ }
+
+ @Test
+ fun `startListening called when session is in idle state`() {
+ asrStateFlow.value = ASRState.Idle
+ every { mockSession.startListening() } just Runs
+
+ val activity = activityController.get()
+ MapboxVoiceFeedbackDialog().show(
+ activity.supportFragmentManager,
+ MapboxVoiceFeedbackDialog.TAG
+ )
+
+ // Advance main looper to let coroutine execute
+ Shadows.shadowOf(activity.mainLooper).idle()
+
+ // startListening should be called because current state is Idle
+ verify { mockSession.startListening() }
+ }
+}
diff --git a/libnavui-voicefeedback/src/test/java/com/mapbox/navigation/ui/voicefeedback/view/VoiceFeedbackViewModelTest.kt b/libnavui-voicefeedback/src/test/java/com/mapbox/navigation/ui/voicefeedback/view/VoiceFeedbackViewModelTest.kt
new file mode 100644
index 00000000000..e4d277f5d19
--- /dev/null
+++ b/libnavui-voicefeedback/src/test/java/com/mapbox/navigation/ui/voicefeedback/view/VoiceFeedbackViewModelTest.kt
@@ -0,0 +1,69 @@
+package com.mapbox.navigation.ui.voicefeedback.view
+
+import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
+import com.mapbox.navigation.core.MapboxNavigation
+import com.mapbox.navigation.voicefeedback.FeedbackAgentSession
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkConstructor
+import io.mockk.unmockkConstructor
+import io.mockk.verify
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
+class VoiceFeedbackViewModelTest {
+
+ private lateinit var viewModel: VoiceFeedbackViewModel
+ private lateinit var mockSession: FeedbackAgentSession
+
+ @Before
+ fun setUp() {
+ mockkConstructor(FeedbackAgentSession.Builder::class)
+ val builder = mockk()
+ mockSession = mockk(relaxUnitFun = true)
+ every { anyConstructed().build() } returns mockSession
+ every { anyConstructed().options(any()) } returns builder
+ every { builder.options(any()) } returns builder
+ every { builder.build() } returns mockSession
+
+ viewModel = VoiceFeedbackViewModel()
+ }
+
+ @After
+ fun tearDown() {
+ unmockkConstructor(FeedbackAgentSession.Builder::class)
+ }
+
+ @Test
+ fun `attach calls session onAttached and connect`() {
+ every { mockSession.onAttached(any()) } just Runs
+ every { mockSession.connect() } just Runs
+ val mapboxNavigation = mockk(relaxed = true)
+
+ viewModel.attach(mapboxNavigation)
+
+ verify { mockSession.onAttached(mapboxNavigation) }
+ verify { mockSession.connect() }
+ }
+
+ @Test
+ fun `detach calls session onDetached`() {
+ every { mockSession.onDetached(any()) } just Runs
+ val mapboxNavigation = mockk(relaxed = true)
+
+ viewModel.detach(mapboxNavigation)
+
+ verify { mockSession.onDetached(mapboxNavigation) }
+ }
+
+ @Test
+ fun `session is created and accessible`() {
+ val session = viewModel.session
+
+ assert(session === mockSession)
+ }
+}
diff --git a/libnavui-voicefeedback/src/test/resources/robolectric.properties b/libnavui-voicefeedback/src/test/resources/robolectric.properties
new file mode 100644
index 00000000000..932b01b9ebc
--- /dev/null
+++ b/libnavui-voicefeedback/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk=28
diff --git a/settings.gradle b/settings.gradle
index 64af2a73a9f..8d0544e0219 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -22,6 +22,7 @@ include ':examples',
':libnavui-maneuver',
':libnavui-resources',
':libnavui-voice',
+ ':libnavui-voicefeedback',
':libnavui-speedlimit',
':libnavui-shield',
':libnavui-status',