Skip to content

Commit

Permalink
feat: locale support fix #13
Browse files Browse the repository at this point in the history
  • Loading branch information
sowens-csd committed Dec 23, 2019
1 parent aef349c commit 0ab2350
Show file tree
Hide file tree
Showing 13 changed files with 569 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
.pub/

build/
example/.flutter-plugins-dependencies
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"program": "example/lib/port.dart"
}
]
}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.7.0

### New

* locales method returns the list of available languages for speech
* new optional localeId parameter on listen method supports choosing the comprehension language separately from the current system locale.

## 0.6.3

### Fix
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ This plugin contains a set of classes that make it easy to use the speech recogn
capabilities of the mobile device in Flutter. It supports both Android and iOS.

## Recent Updates
The 0.7.0 version adds the ability to select the recognition language using the `localeId`
parameter on the `listen` method. It also has a new `locales` method that returns a list
of supported locales for speech on the device.

The 0.6.0 version added a Duration parameter to the listen method to set a maximum time
to listen before automatically cancelling.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.Registrar
import org.json.JSONObject
import android.content.Context
import android.content.BroadcastReceiver
import java.util.*


enum class SpeechToTextErrors {
multipleRequests,
Expand All @@ -46,13 +50,15 @@ class SpeechToTextPlugin(activity: Activity, channel: MethodChannel ):
private val pluginActivity: Activity = activity
private val channel: MethodChannel = channel
private val application: Application = activity.application
private val minSdkForSpeechSupport = 8
private val minSdkForSpeechSupport = 21
private val speechToTextPermissionCode = 78521
private var activeResult: Result? = null
private var initializedSuccessfully: Boolean = false
private var permissionToRecordAudio: Boolean = false
private var speechRecognizer: SpeechRecognizer? = null
private var recognizerIntent: Intent? = null
private var previousRecognizerLang: String? = null
private val defaultLanguageTag: String = Locale.getDefault().toLanguageTag()

companion object {
@JvmStatic
Expand All @@ -67,9 +73,17 @@ class SpeechToTextPlugin(activity: Activity, channel: MethodChannel ):
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"initialize" -> initialize( result )
"listen" -> startListening( result )
"listen" -> {
if (null != call.arguments && call.arguments is String) {
val localeId = call.arguments as String
startListening( result, localeId )
} else {
startListening( result, defaultLanguageTag )
}
}
"stop" -> stopListening( result )
"cancel" -> cancelListening( result )
"locales" -> locales( result )
else -> result.notImplemented()
}
}
Expand All @@ -87,31 +101,32 @@ class SpeechToTextPlugin(activity: Activity, channel: MethodChannel ):
initializeIfPermitted( application )
}

fun sdkVersionTooLow(result: Result): Boolean {
private fun sdkVersionTooLow(result: Result): Boolean {
if ( Build.VERSION.SDK_INT < minSdkForSpeechSupport ) {
result.success(false)
return true;
}
return false;
}

fun isNotInitialized(result: Result): Boolean {
private fun isNotInitialized(result: Result): Boolean {
if ( !initializedSuccessfully ) {
result.success(false)
}
return !initializedSuccessfully
}

fun startListening(result: Result) {
private fun startListening(result: Result, languageTag: String ) {
if ( sdkVersionTooLow( result ) || isNotInitialized( result )) {
return
}
setupRecognizerIntent(languageTag)
speechRecognizer?.startListening(recognizerIntent)
notifyListening(isRecording = true)
result.success(true)
}

fun stopListening(result: Result) {
private fun stopListening(result: Result) {
if ( sdkVersionTooLow( result ) || isNotInitialized( result )) {
return
}
Expand All @@ -120,7 +135,7 @@ class SpeechToTextPlugin(activity: Activity, channel: MethodChannel ):
result.success(true)
}

fun cancelListening(result: Result) {
private fun cancelListening(result: Result) {
if ( sdkVersionTooLow( result ) || isNotInitialized( result )) {
return
}
Expand All @@ -129,6 +144,17 @@ class SpeechToTextPlugin(activity: Activity, channel: MethodChannel ):
result.success(true)
}

private fun locales(result: Result) {
if ( sdkVersionTooLow( result ) || isNotInitialized( result )) {
return
}
val detailsIntent = RecognizerIntent.getVoiceDetailsIntent(pluginActivity)
// val detailsIntent = Intent(RecognizerIntent.ACTION_GET_LANGUAGE_DETAILS)
pluginActivity.sendOrderedBroadcast(
detailsIntent, null, LanguageDetailsChecker( result, pluginActivity ),
null, Activity.RESULT_OK, null, null)
}

private fun notifyListening(isRecording: Boolean) {
val status = when (isRecording) {
true -> SpeechToTextStatus.listening.name
Expand Down Expand Up @@ -167,16 +193,27 @@ class SpeechToTextPlugin(activity: Activity, channel: MethodChannel ):
setRecognitionListener(this@SpeechToTextPlugin)
}

setupRecognizerIntent( defaultLanguageTag )
}

initializedSuccessfully = permissionToRecordAudio
activeResult?.success(permissionToRecordAudio)
activeResult = null
}

private fun setupRecognizerIntent( languageTag: String ) {
if ( previousRecognizerLang == null || previousRecognizerLang != languageTag ) {
previousRecognizerLang = languageTag;
recognizerIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, application.packageName)
putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
if ( languageTag != Locale.getDefault().toLanguageTag()) {
putExtra(RecognizerIntent.EXTRA_LANGUAGE, languageTag );
}
}
}

initializedSuccessfully = permissionToRecordAudio
activeResult?.success(permissionToRecordAudio)
activeResult = null
}
}

override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>?,
Expand Down Expand Up @@ -224,3 +261,42 @@ class SpeechToTextPlugin(activity: Activity, channel: MethodChannel ):
override fun onEvent(p0: Int, p1: Bundle?) {}
override fun onBeginningOfSpeech() {}
}

// See https://stackoverflow.com/questions/10538791/how-to-set-the-language-in-speech-recognition-on-android/10548680#10548680
class LanguageDetailsChecker(flutterResult: Result, pluginActivity: Activity) : BroadcastReceiver() {
private val pluginActivity: Activity = pluginActivity
private val result: Result = flutterResult
private var supportedLanguages: List<String>? = null

private var languagePreference: String? = null

override fun onReceive(context: Context, intent: Intent) {
val results = getResultExtras(true)
if (results.containsKey(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE)) {
languagePreference = results.getString(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE)
}
if (results.containsKey(RecognizerIntent.EXTRA_SUPPORTED_LANGUAGES)) {
supportedLanguages = results.getStringArrayList(
RecognizerIntent.EXTRA_SUPPORTED_LANGUAGES)
createResponse( supportedLanguages )
}
}
private fun createResponse( supportedLanguages: List<String>? ) {
val currentLocale = Locale.getDefault()
val localeNames = ArrayList<String>()
localeNames.add( buildIdNameForLocale(currentLocale))
if ( null != supportedLanguages ) {
for ( lang in supportedLanguages) {
val locale = Locale.forLanguageTag(lang)
localeNames.add( buildIdNameForLocale(locale))
}
}
pluginActivity.runOnUiThread { result.success(localeNames) }

}

private fun buildIdNameForLocale( locale: Locale ): String {
val name = locale.displayName.replace(':', ' ')
return "${locale.language}_${locale.country}:$name"
}
}
18 changes: 18 additions & 0 deletions example/ios/Flutter/Flutter.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
#

Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'High-performance, high-fidelity mobile apps.'
s.description = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
DESC
s.homepage = 'https://flutter.io'
s.license = { :type => 'MIT' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework'
end
Loading

0 comments on commit 0ab2350

Please sign in to comment.