Skip to content

Commit

Permalink
Adds Wear voice input
Browse files Browse the repository at this point in the history
  • Loading branch information
kul3r4 committed Jun 12, 2024
1 parent b883fb9 commit 2f6441c
Show file tree
Hide file tree
Showing 21 changed files with 449 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/apply_spotless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ jobs:
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Apply Spotless

- name: Run spotlessApply for Wear
run: ./gradlew :wear:spotlessApply --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ jobs:
run: ./gradlew :compose:recomposehighlighter:build
- name: Build kotlin snippets
run: ./gradlew :kotlin:build
- name: Build Wear snippets
run: ./gradlew :wear:build
11 changes: 11 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ recyclerview = "1.3.2"
# @keep
targetSdk = "34"
version-catalog-update = "0.8.3"
playServicesWearable = "18.1.0"
wearComposeMaterial = "1.2.1"
wearComposeFoundation = "1.2.1"
coreSplashscreen = "1.0.1"
horologist = "0.5.24"

[libraries]
accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version.ref = "accompanist" }
Expand Down Expand Up @@ -110,6 +115,12 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" }
compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "wearComposeMaterial" }
compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "wearComposeFoundation" }
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" }
horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" }
horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" }

[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ include(
":compose:recomposehighlighter",
":kotlin",
":compose:snippets",
":wear",
)
1 change: 1 addition & 0 deletions wear/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
78 changes: 78 additions & 0 deletions wear/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}

android {
namespace = "com.example.wear"
compileSdk = 34

defaultConfig {
applicationId = "com.example.wear"
minSdk = 26
targetSdk = 33
versionCode = 1
versionName = "1.0"
vectorDrawables {
useSupportLibrary = true
}

}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
jvmToolchain(17)
}

buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}

dependencies {
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
androidTestImplementation(composeBom)

implementation(libs.play.services.wearable)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.compose.material)
implementation(libs.compose.foundation)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.core.splashscreen)
implementation(libs.horologist.compose.layout)
implementation(libs.horologist.compose.material)
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
testImplementation(libs.junit)

androidTestImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.compose.ui.test)
debugImplementation(libs.androidx.compose.ui.tooling)
}
8 changes: 8 additions & 0 deletions wear/lint.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Ignore the IconLocation for the Tile preview images -->
<issue id="IconLocation">
<ignore path="res/drawable/tile_preview.png" />
<ignore path="res/drawable-round/tile_preview.png" />
</issue>
</lint>
21 changes: 21 additions & 0 deletions wear/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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
39 changes: 39 additions & 0 deletions wear/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-feature android:name="android.hardware.type.watch" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault">
<uses-library
android:name="com.google.android.wearable"
android:required="true" />

<!--
Set to true if your app is Standalone, that is, it does not require the handheld
app to run.
-->
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />

<activity
android:name=".snippets.MainActivity"
android:exported="true"
android:taskAffinity=""
android:theme="@android:style/Theme.DeviceDefault">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
43 changes: 43 additions & 0 deletions wear/src/main/java/com/example/wear/snippets/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.wear.snippets

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import com.example.wear.snippets.voiceinput.VoiceInputScreen
import com.google.android.horologist.annotations.ExperimentalHorologistApi

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setTheme(android.R.style.Theme_DeviceDefault)

setContent {
WearApp()
}
}
}

@OptIn(ExperimentalHorologistApi::class)
@Composable
fun WearApp() {
VoiceInputScreen()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.wear.snippets.voiceinput

/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import android.content.Intent
import android.speech.RecognizerIntent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.example.wear.R
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.ItemType
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.material.Chip
import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll

/**
* Shows voice input option
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun VoiceInputScreen() {
// [START android_wear_voice_input]
var textForVoiceInput by remember { mutableStateOf("") }

val voiceLauncher =
rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { activityResult ->
// This is where you process the intent and extract the speech text from the intent.
activityResult.data?.let { data ->
val results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
textForVoiceInput = results?.get(0) ?: "None"
}
}

// [START_EXCLUDE android_wear_voice_input]
val scrollState = rememberScrollState()

ScreenScaffold(scrollState = scrollState) {
val padding = ScalingLazyColumnDefaults.padding(
first = ItemType.Text,
last = ItemType.Chip
)()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
.rotaryWithScroll(scrollState)
.padding(padding),
verticalArrangement = Arrangement.Center
) {
// [END_EXCLUDE android_wear_voice_input]
// Create an intent that can start the Speech Recognizer activity
val voiceIntent: Intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
)

putExtra(
RecognizerIntent.EXTRA_PROMPT,
stringResource(R.string.voice_text_entry_label)
)
}
// Invoke the process from a chip
Chip(
onClick = {
voiceLauncher.launch(voiceIntent)
},
label = stringResource(R.string.voice_input_label),
secondaryLabel = textForVoiceInput
)
// [START_EXCLUDE android_wear_voice_input]
}
}
// [END_EXCLUDE android_wear_voice_input]
// [END android_wear_voice_input]
}
Loading

0 comments on commit 2f6441c

Please sign in to comment.