From 2cdc7d75cd8ef95be2e981ab44dbdad2fe8c5990 Mon Sep 17 00:00:00 2001 From: Eoin Mcloughlin Date: Sun, 28 Aug 2022 13:51:55 +0100 Subject: [PATCH] Change sounder UI and input handling Let user see the text box that's being entered; pop items off one at a time and display in a history view. This might display the first few symbols faster than they're being heard, depending on the size of the audio buffers. Not sure what to do about that, other than writing a real-time mixer. --- .../java/es/eoinrul/ecwt/DitDahGenerator.kt | 47 -------- .../java/es/eoinrul/ecwt/SounderActivity.kt | 109 ++++++++++-------- app/src/main/res/layout/activity_sounder.xml | 28 +++-- .../metadata/android/en-US/changelogs/44.txt | 1 + 4 files changed, 86 insertions(+), 99 deletions(-) diff --git a/app/src/main/java/es/eoinrul/ecwt/DitDahGenerator.kt b/app/src/main/java/es/eoinrul/ecwt/DitDahGenerator.kt index 3baecbc..b85d9b2 100644 --- a/app/src/main/java/es/eoinrul/ecwt/DitDahGenerator.kt +++ b/app/src/main/java/es/eoinrul/ecwt/DitDahGenerator.kt @@ -312,53 +312,6 @@ fun SequenceToString(sequence: List) : String { return ret } -// Almost certainly the wrong way to do this -fun KeycodeToSoundSequence(keycode : Int) : List { - return when(keycode) { - KeyEvent.KEYCODE_A -> StringToSoundSequence("A"); - KeyEvent.KEYCODE_B -> StringToSoundSequence("B"); - KeyEvent.KEYCODE_C -> StringToSoundSequence("C"); - KeyEvent.KEYCODE_D -> StringToSoundSequence("D"); - KeyEvent.KEYCODE_E -> StringToSoundSequence("E"); - KeyEvent.KEYCODE_F -> StringToSoundSequence("F"); - KeyEvent.KEYCODE_G -> StringToSoundSequence("G"); - KeyEvent.KEYCODE_H -> StringToSoundSequence("H"); - KeyEvent.KEYCODE_I -> StringToSoundSequence("I"); - KeyEvent.KEYCODE_J -> StringToSoundSequence("J"); - KeyEvent.KEYCODE_K -> StringToSoundSequence("K"); - KeyEvent.KEYCODE_L -> StringToSoundSequence("L"); - KeyEvent.KEYCODE_M -> StringToSoundSequence("M"); - KeyEvent.KEYCODE_N -> StringToSoundSequence("N"); - KeyEvent.KEYCODE_O -> StringToSoundSequence("O"); - KeyEvent.KEYCODE_P -> StringToSoundSequence("P"); - KeyEvent.KEYCODE_Q -> StringToSoundSequence("Q"); - KeyEvent.KEYCODE_R -> StringToSoundSequence("R"); - KeyEvent.KEYCODE_S -> StringToSoundSequence("S"); - KeyEvent.KEYCODE_T -> StringToSoundSequence("T"); - KeyEvent.KEYCODE_U -> StringToSoundSequence("U"); - KeyEvent.KEYCODE_V -> StringToSoundSequence("V"); - KeyEvent.KEYCODE_W -> StringToSoundSequence("W"); - KeyEvent.KEYCODE_X -> StringToSoundSequence("X"); - KeyEvent.KEYCODE_Y -> StringToSoundSequence("Y"); - KeyEvent.KEYCODE_Z -> StringToSoundSequence("Z"); - KeyEvent.KEYCODE_0 -> StringToSoundSequence("0"); - KeyEvent.KEYCODE_1 -> StringToSoundSequence("1"); - KeyEvent.KEYCODE_2 -> StringToSoundSequence("2"); - KeyEvent.KEYCODE_3 -> StringToSoundSequence("3"); - KeyEvent.KEYCODE_4 -> StringToSoundSequence("4"); - KeyEvent.KEYCODE_5 -> StringToSoundSequence("5"); - KeyEvent.KEYCODE_6 -> StringToSoundSequence("6"); - KeyEvent.KEYCODE_7 -> StringToSoundSequence("7"); - KeyEvent.KEYCODE_8 -> StringToSoundSequence("8"); - KeyEvent.KEYCODE_9 -> StringToSoundSequence("9"); - KeyEvent.KEYCODE_PERIOD -> StringToSoundSequence("."); - //KeyEvent.KEYCODE_S -> StringToSoundSequence("?"); - else -> { - StringToSoundSequence(" ") - } - } -} - data class DitDahGeneratorSettings(var context : Context? = null) { //TODO These values are duplicated in the settings fragment var toneFrequency = 650 diff --git a/app/src/main/java/es/eoinrul/ecwt/SounderActivity.kt b/app/src/main/java/es/eoinrul/ecwt/SounderActivity.kt index fd37812..8888592 100644 --- a/app/src/main/java/es/eoinrul/ecwt/SounderActivity.kt +++ b/app/src/main/java/es/eoinrul/ecwt/SounderActivity.kt @@ -1,45 +1,31 @@ package es.eoinrul.ecwt -import android.content.Context -import android.media.MediaPlayer import android.os.Bundle import android.text.Editable import android.text.TextWatcher -import android.view.KeyEvent -import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import java.util.* +import kotlin.math.max // Activity that only echos any inputs typed on a keyboard -class SounderActivity : AppCompatActivity() { +class SounderActivity : AppCompatActivity(), DitDahSoundStream.StreamNotificationListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_sounder) //val sounderMode = intent.getStringExtra(SOUNDER_MODE); - - mTextViewTest = findViewById(R.id.keyedText); + mPreviouslySentText = findViewById(R.id.sentText) + mCurrentlySendingText = findViewById(R.id.keyedText) // This is a hidden EditText that's used to bring up the soft keyboard: - mKeyboardInput = findViewById(R.id.sounderInput); + mKeyboardInput = findViewById(R.id.sounderInput) mKeyboardInput?.addTextChangedListener(mInputHandler) - mKeyboardInput?.requestFocus(); + mKeyboardInput?.requestFocus() initSoundPlayer() - onTextEntered("E"); // Display and sound something - this is arbitrary - } - - // This will be called for connected USB/Bluetooth keyboards: - override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { - val sequence = KeycodeToSoundSequence(keyCode) - mTextViewTest?.text = SequenceToString(sequence) - mSoundPlayer?.enqueue(sequence) - mKeyboardInput?.setText("") // Should be empty due to physical keyboard - return true; } override fun onResume() { @@ -48,7 +34,7 @@ class SounderActivity : AppCompatActivity() { } override fun onPause() { - super.onPause(); + super.onPause() mSoundPlayer?.quit() mSoundPlayer = null @@ -57,39 +43,28 @@ class SounderActivity : AppCompatActivity() { private fun initSoundPlayer() { val generatorSettings = DitDahGeneratorSettings(this) mSoundPlayer = DitDahSoundStream(generatorSettings) + mSoundPlayer!!.streamNotificationListener = this + mSpaceDurationMs = (mSoundPlayer!!.durationOf(listOf(SoundTypes.LETTER_SPACE)) * 1000).toLong() } - private fun onTextEntered(typedString : String) { - val sequence = StringToSoundSequence(typedString); + private var mPreviouslySentText : TextView? = null + private var mCurrentlySendingText : TextView? = null + private var mKeyboardInput : EditText? = null - // Display the text: - mTextViewTest?.text = SequenceToString(sequence); + private var mSoundPlayer : DitDahSoundStream? = null + // Flag to indicate if the sound player is waiting to send another symbol: + private var mSoundAwaitingText : Boolean = true - // Then play the sound: - mSoundPlayer?.enqueue(sequence); - - // Finally, clear the input box, as we don't need it anymore: - mKeyboardInput?.setText(""); - } - - private var mTextViewTest : TextView? = null; - private var mKeyboardInput : EditText? = null; - private var mMediaPlayer : MediaPlayer? = null; - - private var mSoundPlayer : DitDahSoundStream? = null; + // Duration of a space with current sound gen. Used to add spaces + // automatically, if user input is slow + private var mSpaceDurationMs : Long = 0 + private var mTimeOfFirstSymbolWait : Long = 0 // Utility to watch our EditText and handle any user input // This only seems to get triggered if we're using the software keyboard private val mInputHandler = object : TextWatcher { override fun afterTextChanged(s: Editable?) { - if(s == null) { - return; - } - - if(s.isNotEmpty()) { - var typedString: String = s.toString().capitalize(); - onTextEntered(typedString) - } + ensurePlaying() } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { @@ -98,4 +73,48 @@ class SounderActivity : AppCompatActivity() { override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { } } + + private fun ensurePlaying() { + if(mSoundAwaitingText && mSoundPlayer != null) { + symbolsExhausted(mSoundPlayer!!) + } + } + + override fun symbolsExhausted(stream: DitDahSoundStream) { + if (!mSoundAwaitingText) { + mTimeOfFirstSymbolWait = System.currentTimeMillis() + } + + mSoundAwaitingText = true + runOnUiThread { + if( mKeyboardInput?.text!!.isNotEmpty() && mSoundAwaitingText ) { + mSoundAwaitingText = false + // Extract and remove the first character + val firstInput = mKeyboardInput!!.text!!.subSequence(0, 1).toString() + mKeyboardInput!!.text = mKeyboardInput!!.text.replace(0, 1, "") + // Move text entry cursor to the end + mKeyboardInput!!.setSelection(mKeyboardInput!!.text.length) + + val sequence = StringToSoundSequence(firstInput.capitalize()) + + val now = System.currentTimeMillis() + val extraSpace = if(now - mTimeOfFirstSymbolWait > mSpaceDurationMs) { + " " + } else { + "" + } + + // Display the text: + mCurrentlySendingText?.text = SequenceToString(sequence) + val maxHistory = 20 + val trimFrom = max(0, mPreviouslySentText?.text!!.length - maxHistory + - extraSpace.length - firstInput.length) + mPreviouslySentText?.text = mPreviouslySentText?.text!!.substring(trimFrom, + mPreviouslySentText?.text!!.length) + extraSpace + firstInput + + // Then play the sound: + mSoundPlayer?.enqueue(sequence) + } + } + } } diff --git a/app/src/main/res/layout/activity_sounder.xml b/app/src/main/res/layout/activity_sounder.xml index 520719a..b926221 100644 --- a/app/src/main/res/layout/activity_sounder.xml +++ b/app/src/main/res/layout/activity_sounder.xml @@ -6,6 +6,21 @@ android:layout_height="match_parent" tools:context="es.eoinrul.ecwt.SounderActivity"> + + + app:layout_constraintTop_toBottomOf="@+id/sentText" /> - + android:layout_margin="16dp" + app:layout_constraintTop_toBottomOf="@+id/keyedText" > diff --git a/fastlane/metadata/android/en-US/changelogs/44.txt b/fastlane/metadata/android/en-US/changelogs/44.txt index 40180d1..ed0417f 100644 --- a/fastlane/metadata/android/en-US/changelogs/44.txt +++ b/fastlane/metadata/android/en-US/changelogs/44.txt @@ -1,2 +1,3 @@ Fixed bug where duration of lessons was too long Added tip if users didn't add spaces to lesson input +Improved "Sounder" UI. Now shows some history and upcoming text.