commit 813441645c61e834563619ca0c227ba5d371526c Author: mmanningau Date: Thu Jan 22 16:32:36 2026 +1100 Added .gitignore and updated to include the USB connection and stream on certainty of word/phrase results.... diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..bfdec4f --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Speech To Keyboard \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..670a1de --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml new file mode 100644 index 0000000..539e3b8 --- /dev/null +++ b/.idea/studiobot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..6de5218 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,52 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (contains personal SDK path) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation Editor temp files +.navigation/ + +# Captures +captures/ + +# IntelliJ / Android Studio project files +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries + +# Keystore files (NEVER commit these!) +*.jks +*.keystore +*.pem + +# OS-specific files +.DS_Store +Thumbs.db diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..7153d68 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,58 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "net.mmanningau.speechtokeyboard" + compileSdk = 36 + + defaultConfig { + applicationId = "net.mmanningau.speechtokeyboard" + minSdk = 28 + targetSdk = 36 + versionCode = 4 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.activity) + implementation(libs.androidx.constraintlayout) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + // 1. The "Brain": Vosk Offline Speech Recognition + implementation("com.alphacephei:vosk-android:0.3.47") + // (Optional) Helper for memory management if needed later + // Removed the following as it was listed as optional and it did cause errors - + // so to avoid a whole list of duplicate class found errors - this is already required via the VOSK libraries + // implementation("net.java.dev.jna:jna:5.13.0") + + // 2. The "Mouth": USB Serial Driver for Android + implementation("com.github.mik3y:usb-serial-for-android:3.7.0") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/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/app/src/androidTest/java/net/mmanningau/speechtokeyboard/ExampleInstrumentedTest.kt b/app/src/androidTest/java/net/mmanningau/speechtokeyboard/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..6f980b5 --- /dev/null +++ b/app/src/androidTest/java/net/mmanningau/speechtokeyboard/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package net.mmanningau.speechtokeyboard + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("net.mmanningau.speechtokeyboard", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c9cfb64 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/net/mmanningau/speechtokeyboard/MainActivity.kt b/app/src/main/java/net/mmanningau/speechtokeyboard/MainActivity.kt new file mode 100644 index 0000000..d010159 --- /dev/null +++ b/app/src/main/java/net/mmanningau/speechtokeyboard/MainActivity.kt @@ -0,0 +1,149 @@ +package net.mmanningau.speechtokeyboard // <--- MAKE SURE THIS MATCHES YOUR PACKAGE NAME + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import org.vosk.Model +import org.vosk.android.SpeechService +import java.io.File +import java.util.zip.ZipInputStream + +class MainActivity : AppCompatActivity() { + + // UI Components + private lateinit var statusText: TextView + + // Vosk Components + private var model: Model? = null + private var speechService: SpeechService? = null + + // 1. THE FILE PICKER REGISTRY + // This handles the result when the user picks a ZIP file + private val pickZipFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { uri -> + // User picked a file. Now we install it. + installModelFromUri(uri) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + statusText = findViewById(R.id.status_text_view) // Make sure you have a TextView with this ID in your layout + statusText.text = "Checking for existing model...." + + // ADD THIS LINE AT THE BOTTOM: + // This attempts to load the model immediately if files exist + initVoskModel() + } + + // 2. SETUP THE MENU + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.main_menu, menu) + return true + } + + // 3. HANDLE MENU CLICKS + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_load_model -> { + openFilePicker() + true + } + R.id.action_test_model -> { + // Launch the new Test Activity + val intent = Intent(this, TestModelActivity::class.java) + startActivity(intent) + true + } + else -> super.onOptionsItemSelected(item) + } + } + + // 4. OPEN SYSTEM FILE PICKER + private fun openFilePicker() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/zip" // Only show ZIP files + } + pickZipFile.launch(intent) + } + + // 5. INSTALLATION LOGIC (Unzip to private storage) + private fun installModelFromUri(uri: android.net.Uri) { + statusText.text = "Installing model... please wait." + + // Run in background thread to avoid freezing UI + Thread { + try { + contentResolver.openInputStream(uri)?.use { inputStream -> + val zipInputStream = ZipInputStream(inputStream) + val targetDir = File(filesDir, "vosk-model") + + // Clean up old model if exists + if (targetDir.exists()) targetDir.deleteRecursively() + targetDir.mkdirs() + + // Unzip loop + var entry = zipInputStream.nextEntry + while (entry != null) { + val outFile = File(targetDir, entry.name) + if (entry.isDirectory) { + outFile.mkdirs() + } else { + // Ensure parent dir exists + outFile.parentFile?.mkdirs() + outFile.outputStream().use { output -> + zipInputStream.copyTo(output) + } + } + entry = zipInputStream.nextEntry + } + } + + // Back to UI Thread to say success + runOnUiThread { + statusText.text = "Model Installed! Initializing..." + initVoskModel() + } + + } catch (e: Exception) { + runOnUiThread { + Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show() + statusText.text = "Installation Failed." + } + } + }.start() + } + + // 6. INITIALIZE VOSK "BRAIN" + // Replace your existing initVoskModel with this updated version + private fun initVoskModel() { + val modelPath = File(filesDir, "vosk-model") + + // Check if the directory exists before trying to load + if (!modelPath.exists()) { + statusText.text = "No model found. Please load one." + return + } + + val actualModelDir = modelPath.listFiles()?.firstOrNull { it.isDirectory } ?: modelPath + + try { + model = Model(actualModelDir.absolutePath) + statusText.text = "Model Loaded & Ready!" + // Optional: Enable your 'Test' button here if you disabled it + } catch (e: Exception) { + statusText.text = "Error loading saved model: ${e.message}" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mmanningau/speechtokeyboard/TestModelActivity.kt b/app/src/main/java/net/mmanningau/speechtokeyboard/TestModelActivity.kt new file mode 100644 index 0000000..3f9610a --- /dev/null +++ b/app/src/main/java/net/mmanningau/speechtokeyboard/TestModelActivity.kt @@ -0,0 +1,254 @@ +package net.mmanningau.speechtokeyboard + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Bundle +import android.widget.ImageButton +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import org.json.JSONObject +import org.vosk.Model +import org.vosk.Recognizer +import org.vosk.android.RecognitionListener +import org.vosk.android.SpeechService +import java.io.File + +import android.content.Context +import android.hardware.usb.UsbManager +import com.hoho.android.usbserial.driver.UsbSerialPort +import com.hoho.android.usbserial.driver.UsbSerialProber +import com.hoho.android.usbserial.util.SerialInputOutputManager + +class TestModelActivity : AppCompatActivity(), RecognitionListener { + + private lateinit var outputText: TextView + private lateinit var micButton: ImageButton + + // Vosk Components + private var model: Model? = null + private var speechService: SpeechService? = null + private var isListening = false + + // USB Components + private var usbPort: UsbSerialPort? = null + private var usbIoManager: SerialInputOutputManager? = null // Handles the data flow + + private var committedText = "" // Stores the finalized sentences + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_test_model) + + // Bind UI + outputText = findViewById(R.id.text_output_log) + micButton = findViewById(R.id.btn_mic_toggle) + + // Check Permissions immediately + checkAudioPermission() + + // Setup Button Listener + micButton.setOnClickListener { + toggleListening() + } + + private fun connectToPico() { + val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager + + // 1. Find the Device + // (This probes specifically for devices listed in your device_filter.xml) + val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager) + if (availableDrivers.isEmpty()) { + outputText.append("\n> No USB device found.") + return + } + + // Assume the first device found is the Pico + val driver = availableDrivers[0] + val connection = usbManager.openDevice(driver.device) + + if (connection == null) { + outputText.append("\n> Permission denied. Re-plug device?") + return + } + + // 2. Open the Port + // Most Picos use port 0. + usbPort = driver.ports[0] + + try { + usbPort?.open(connection) + // 3. Set Parameters (Must match your Pico's C/Python code!) + // 115200 Baud, 8 Data bits, 1 Stop bit, No Parity + usbPort?.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE) + + outputText.append("\n> USB Connected to Pico!") + } catch (e: Exception) { + outputText.append("\n> USB Error: ${e.message}") + } + } + + // Initialize the model in background + initModel() + } + + private fun initModel() { + // We look for the folder inside private storage (same logic as MainActivity) + val modelPath = File(filesDir, "vosk-model") + + if (!modelPath.exists()) { + outputText.text = "Error: Model not found. Please go back and load a model first." + micButton.isEnabled = false + return + } + + Thread { + try { + // Find the actual model folder inside + val actualModelDir = modelPath.listFiles()?.firstOrNull { it.isDirectory } ?: modelPath + model = Model(actualModelDir.absolutePath) + + runOnUiThread { + outputText.append("\n\n> Model Loaded. Ready.") + } + } catch (e: Exception) { + runOnUiThread { + outputText.text = "Error loading model: ${e.message}" + } + } + }.start() + } + + private fun sendToPico(text: String) { + if (usbPort == null) return // Safety check + + try { + // Convert text to bytes and send + val data = text.toByteArray(Charsets.UTF_8) + usbPort?.write(data, 1000) // 1000ms timeout + } catch (e: Exception) { + outputText.append("\n[Send Failed: ${e.message}]") + } + } + + private fun toggleListening() { + if (model == null) { + Toast.makeText(this, "Model not loaded yet", Toast.LENGTH_SHORT).show() + return + } + + if (isListening) { + stopRecognition() + } else { + startRecognition() + } + } + + private fun startRecognition() { + try { + val recognizer = Recognizer(model, 16000.0f) // 16kHz is standard for Vosk + speechService = SpeechService(recognizer, 16000.0f) + //speechService?.addListener(this) <----- removed this as it generated an error + speechService?.startListening(this) + + isListening = true + micButton.setColorFilter(android.graphics.Color.RED) // Turn button red + outputText.text = "" // Clear previous text + outputText.append("> Listening...\n") + + } catch (e: Exception) { + outputText.append("\nError starting mic: ${e.message}") + } + } + + private fun stopRecognition() { + speechService?.stop() + speechService = null + isListening = false + micButton.clearColorFilter() // Reset button color + outputText.append("\n> Stopped.") + } + + // --- Vosk Listener Callbacks --- + + override fun onResult(hypothesis: String?) { + hypothesis?.let { + val text = parseVoskResult(it) + if (text.isNotEmpty()) { + // 1. Update the UI History + // Add the new sentence to our history + committedText += "$text. " + // Update screen + outputText.text = "$committedText" + + // 2. SEND TO PICO + // We append a space because speech engines strip trailing spaces, + // and you don't want "helloworld" typed into your computer. + sendToPico("$text ") + } + } + } + + override fun onPartialResult(hypothesis: String?) { + // Optional: Shows words as they are being spoken (streaming) + // You can enable this if you want to see "typing" effect + hypothesis?.let { + // Parse the "partial" JSON key + val partial = JSONObject(it).optString("partial", "") + + if (partial.isNotEmpty()) { + // Display: [History] + [Current Streaming Guess] + outputText.text = "$committedText $partial..." + } + } + } + + override fun onFinalResult(hypothesis: String?) { + // Final flush when stopping + hypothesis?.let { + val text = parseVoskResult(it) + if (text.isNotEmpty()) { + outputText.append("$text\n") + } + } + } + + override fun onError(exception: Exception?) { + outputText.append("\nError: ${exception?.message}") + } + + override fun onTimeout() { + outputText.append("\nTimeout.") + } + + // Helper to clean JSON: {"text": "hello world"} -> "hello world" + private fun parseVoskResult(json: String): String { + return try { + JSONObject(json).optString("text", "") + } catch (e: Exception) { + "" + } + } + + // Permission Helper + private fun checkAudioPermission() { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 1) + } + } + + // Cleanup on exit + override fun onDestroy() { + super.onDestroy() + speechService?.shutdown() + + // Close USB + try { + usbPort?.close() + } catch (e: Exception) { + // Ignore errors on close + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..181dcd1 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_test_model.xml b/app/src/main/res/layout/activity_test_model.xml new file mode 100644 index 0000000..1ccaf7e --- /dev/null +++ b/app/src/main/res/layout/activity_test_model.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml new file mode 100644 index 0000000..04ad5b3 --- /dev/null +++ b/app/src/main/res/menu/main_menu.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..39d7edd --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c8524cd --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..26bddfe --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Speech To Keyboard + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..23e9634 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +