I know Android, and many users, say not to use $event.keyCode || $event.charCode because the returned characters can't be trusted. And its true, it can't. But what about using a regexp to test for the presence of certain characters?
As a case example, US zip codes can be in several formats: 12345, 12345-6789 or 12345 6789. These three formats allow numbers, space and minus sign. But the minus sign could also be the dash sign. So with a physical keyboard you can test for $event.keyCode to match keyCodes [32,109,189] (ie: space, minus, keypad-minus).
But on a softkey board, particularly android soft keyboard, the returned key for almost ALL keys is never correct. On top of which, on android keypress isn't supported, only keydown and keyup - and those present their own challenges. So instead of capturing the keycodes, why not just evaluate the last pressed key and test that character against a regexp?
In my code I have changed things to:
<input id="zip" ng-model="userInfo.zip" ng-keyup="checkKeyCode($event)" ng-blur="setInfo($event,'zip')" >
$scope.checkKeyCode = function() {
var lastChar = $scope.userInfo.zip.slice(-1) ;
var regex1 = /^[\n]*$/ ; // enter key
var regex2 = /^[\d\s-]*$/ ; // numbers, space and minus
if (regex1.test(lastChar)) {
$event.target.blur($event,'zip') ; //force to blur, process data
return ;
}
if (regex2.test(lastChar) == false) {
$scope.errMsg = "Only numbers, space and dash/minus characters are permitted" ;
$scope.errColor = "red" ;
var sLen = $scope.userInfo.zip.length - 1 ;
$scope.userInfo.zip = $scope.userInfo.zip.substring(0,sLen) ; //remove invalid char
return ;
}
if ($scope.userInfo.zip.length > 10) {
$scope.errMsg = "Max length is 10 characters" ;
$scope.errColor = "red" ;
$scope.userInfo.zip = $scope.userInfo.zip.substring(0,10) ;
return ;
}
}
Even though my code works, I have a feeling there are some pitfalls to this method. For example keyCode 109 and 189 are both the minus sign, but does the above regex2 capture both of those or do I need to account a minus sign and a dash - does the regexp treat them differently?
What are the pitfalls and reasons to not do it this way? And last, if this isn't a solid method, then how does one account for keyCodes for Android soft keyboards?
Related
I am facing Performance issues with iOS Swift function to the word-wrap streaming text received in real-time**
We have written an iOS function to perform word-wrapping based on character count on streaming text received in real-time, to display on an OLED. We are trying to replicate what the Android StringTokenizer class and methods do, since iOS Swift does not have an equivalent class/method. But we have performance issues with this function (delay in the output of wrapped words compared to the streaming text being received). We are looking for the best possible method/solution/class in Swift which can handle the entire process of word wrapping of streaming text with minimum delay.
Background:
We are developing an iOS mobile app to convert speech to text in real-time using cloud-based automatic speech-to-text (STT) model APIs including STT APIs from Google, Azure & IBM, and display the transcribed text on an OLED display via a BLE device.
The app uses the mobile device’s microphone to capture “endless” or “infinite” streaming audio feeds, streams the audio to the cloud STT API, and receives stream speech recognition (transcription) results in real-time as the audio is processed.
Unlike standard speech-to-text implementations in voice assistants which listen to voice commands, wait for silence and then give the best output, in infinite streaming, there is a continuous stream of input audio being sent to the STT API which returns interim and final transcription results continuously.
As the transcription data is received from the API (interim/final), the app performs word-wrapping logic to prepare the text for display on the OLED. This logic depends on the number of characters that can be displayed on one line of the OLED and the number of lines in the display.
For example, on our current OLED, we can display 18 characters in one line and a maximum of 5 lines at a time. So if a string of than 18 characters is received from the STT API, the app logic has to find the appropriate space character in the string after which to break it and display the remaining string on the next line(s). . Here’s an example of a string received from the STT API:
TranscribeGlass shows closed captions from any source in your field of view
The expected result on the 5 lines of the OLED after performing word wrapping:
TranscribeGlass
shows closed
captions from any
source in your
field of view
Explanation:
Line 1 - “TranscribeGlass” - because “TranscribeGlass shows” exceeds 18 characters so break the string after the space before “shows”, and wrap it to the next line
Line 2 - “shows closed” - because“shows closed captions” exceeds 18 characters, break the string after the space before “captions”
Line 3 - “captions from any” - because “captions from any source” exceeds 18 characters, break the string after the space before “source”
Line 4 - “source on in your” - because “source on in your field” exceeds 18 characters, break the string after the space before “field”
Line 5 - “field of view” - no wrapping needed since the string is <= 18 characters
In Android, we have a ready-made StringTokenizer class & methods available that allows an application to break a string into tokens.
Our iOS Swift function:
We have written a function WriteOnlyCaption(), which takes a string as input and performs the word wrapping. See the code below:
For strings which has less than 18 characters, we are using the below function
public func writeOnlyCaption(text:String, isFinalBLE: Bool) {
print("TEXT IS NOW:\(text)")
var sendString:String = " "
var previousWrapLocation = 0
var currentWrapLocationIndex = characterPerLine
while(text.count - previousWrapLocation > characterPerLine) {
while(text[currentWrapLocationIndex] != " ") {
//print("While CurrentWrapLocationIndex Index \(currentWrapLocationIndex)")
currentWrapLocationIndex -= 1
}
sendString = sendString + String(text[previousWrapLocation...currentWrapLocationIndex]) + "\n"
line.addLineIndex(textIndex: currentWrapLocationIndex)
currentWrapLocationIndex += 1
previousWrapLocation = currentWrapLocationIndex
currentWrapLocationIndex = currentWrapLocationIndex + characterPerLine
}
sendString = sendString + String(text[previousWrapLocation..<text.count]) + "\n"
print("sendSting:->\(sendString)")
line.addLineIndex(textIndex: sendString.count)
line.setSendString(sendText: sendString)
line.tooString()
if previousLine == 0 {
previousLine = line.getLines().count
}
print("previousLine:->\(previousLine)")
var seed = ""
var counter = 0
var stringData = line.getSendString().components(separatedBy: "\n")
stringData.removeLast()
if stringData.count < 5 {
repeat {
if counter >= 0 {
seed = seed + stringData[counter] + "\n"
}
counter += 1
} while(stringData.count != counter)
} else {
counter = stringData.count - 5
repeat {
if counter > 0 {
seed = seed + stringData[counter] + "\n"
}
counter += 1
} while(stringData.count != counter)
}
print("Seeeddddddd:->\(seed)")
print("counter: \(counter)")
if seed != "" {
if isFinalBLE {
var seedBytes = seed.bytes
if previousLine == line.getLines().count {
seedBytes.append(0x03)
} else if line.getLines().count > previousLine {
seedBytes.append(0x02)
seedBytes.append(0x03)
}
seedBytes.append(0x00)
print("-- seedBytes: \(seedBytes) --")
previousLine = line.getLines().count
self.writeDataWithResponse(writeData: Data(seedBytes), characteristic: self.getCharsticFrom(UUID: CHAR.GLASS_WRITE)!)
} else {
var seedBytes = seed.bytes
if previousLine == line.getLines().count {
//seedBytes.append(0x00)
} else if line.getLines().count > previousLine {
if line.getLines().count - previousLine == 1 {
seedBytes.append(0x02)
} else if line.getLines().count - previousLine == 2{
seedBytes.append(0x02)
seedBytes.append(0x02)
} else {
seedBytes.append(0x02)
}
}
seedBytes.append(0x00)
print("-- seedBytes: \(seedBytes) --")
previousLine = line.getLines().count
self.writeDataWithResponse(writeData: Data(seedBytes), characteristic: self.getCharsticFrom(UUID: CHAR.GLASS_WRITE)!)
}
}
}
As you can see, we are using four while loops to perform the word wrapping correctly and send it to the OLED.
The main issue with this function is that four while loops and different if-else conditions create lots of delays (varies from 450ms to 900ms) while processing the text string, and this results in poor performance - either a lag in the display of text on the OLED compared to the streaming audio or entire chunks of transcribed text get skipped.
When I check the keyCodes on the softKeyboard for .- , the e.key in JavaScript is "Unidentified" and the e.keyCode is 229 for all of those characters.
The device I am using is a TC52k scanner and the scan trigger has the same key and keyCode... so it can't be completely disabled.
I don't know how many things I tried that didn't work... but I will post some...
This is a dynamic field bound with rivets.js using JSON objects
$(document).on("keydown", ".qty-input", function(e) {
let code = e.keyCode;
if (code == 229 && $(this).val().charAt(str.length - 1) == ".") {
$(this).val($(this).val().slice(0, -1));
}
});
^Basically, that says that if the last character has the code 229 and the last character of the entered string is a '.', then cut the last character off... but doesn't work correctly.
I also did this that basically says to only accept keys that have keycodes between 48 and 57 which are keys 1-9 on the android soft keyboard.
<input class="qty-input" placeholder="0" type="number" min="0" step="1" onkeypress="return event.charCode >= 48 && event.charCode <= 57 && event.keyCode != 229" pattern="[0-9]*" autofocus>
Also didn't work...
Does anyone have experience with this and can help?
I know there's lots of unsolved posts about this online.
It's the last thing I have to do and its keeping my app from being deployed!
Also... I've tried these function using events: keyup, keydown, change, input and keypress...
Here is the best workaround that I found (since 229 also triggers the 'scanner' trigger, I have to detect whether a string includes a barcode and a quantity and will have to separate the two(the keyCode for a [.] again is the same as the keyCode for the scanner trigger))
The 2 conditions determine 2 things:
If the value is longer than 3, then it is not allowed as a quantity because the quantity can not be over '500', so when it has a length longer than 5, it is considered to be a barcode.
let oldVal = '';
let newVal = '';
let barcode = '';
$(document).on("keydown", "input.received-qty", function(e) {
let code = e.keyCode || e.which || e.charCode;
oldVal = $(this).val();
});
$(document).on("keyup", "input.received-qty", function(e) {
let code = e.keyCode || e.which || e.charCode;
newVal = $(this).val();
if (code == 229 && e.target.value.length >= 3) {
barcode = $(this).val().slice(oldVal.length - 1, -1);
$(this).val(oldVal);
} else if (code == 229 && e.target.value.length <= 3) {
let x = oldVal.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "");
$(this).val('');
$(this).val(x);
}
console.log("oldVal : ", "[", oldVal, "]", "newVal : ", "[", $(this).val(), "]");
});
This is the best way I've tried so far.
I'm working on an Android app, and I do not want people to use emoji in the input.
How can I remove emoji characters from a string?
Emojis can be found in the following ranges (source) :
U+2190 to U+21FF
U+2600 to U+26FF
U+2700 to U+27BF
U+3000 to U+303F
U+1F300 to U+1F64F
U+1F680 to U+1F6FF
You can use this line in your script to filter them all at once:
text.replace("/[\u2190-\u21FF]|[\u2600-\u26FF]|[\u2700-\u27BF]|[\u3000-\u303F]|[\u1F300-\u1F64F]|[\u1F680-\u1F6FF]/g", "");
Latest emoji data can be found here:
http://unicode.org/Public/emoji/
There is a folder named with emoji version.
As app developers a good idea is to use latest version available.
When You look inside a folder, You'll see text files in it.
You should check emoji-data.txt. It contains all standard emoji codes.
There are a lot of small symbol code ranges for emoji.
Best support will be to check all these in Your app.
Some people ask why there are 5 digit codes when we can only specify 4 after \u.
Well these are codes made from surrogate pairs. Usually 2 symbols are used to encode one emoji.
For example, we have a string.
String s = ...;
UTF-16 representation
byte[] utf16 = s.getBytes("UTF-16BE");
Iterate over UTF-16
for(int i = 0; i < utf16.length; i += 2) {
Get one char
char c = (char)((char)(utf16[i] & 0xff) << 8 | (char)(utf16[i + 1] & 0xff));
Now check for surrogate pairs. Emoji are located on the first plane, so check first part of pair in range 0xd800..0xd83f.
if(c >= 0xd800 && c <= 0xd83f) {
high = c;
continue;
}
For second part of surrogate pair range is 0xdc00..0xdfff. And we can now convert a pair to one 5 digit code.
else if(c >= 0xdc00 && c <= 0xdfff) {
low = c;
long unicode = (((long)high - 0xd800) * 0x400) + ((long)low - 0xdc00) + 0x10000;
}
All other symbols are not pairs so process them as is.
else {
long unicode = c;
}
Now use data from emoji-data.txt to check if it's emoji.
If it is, then skip it. If not then copy bytes to output byte array.
Finally byte array is converted to String by
String out = new String(outarray, Charset.forName("UTF-16BE"));
For those using Kotlin, Char.isSurrogate can help as well. Find and remove the indexes that are true from that.
Here is what I use to remove emojis. Note: This only works on API 24 and forwards
public String remove_Emojis_For_Devices_API_24_Onwards(String name)
{
// we will store all the non emoji characters in this array list
ArrayList<Character> nonEmoji = new ArrayList<>();
// this is where we will store the reasembled name
String newName = "";
//Character.UnicodeScript.of () was not added till API 24 so this is a 24 up solution
if (Build.VERSION.SDK_INT > 23) {
/* we are going to cycle through the word checking each character
to find its unicode script to compare it against known alphabets*/
for (int i = 0; i < name.length(); i++) {
// currently emojis don't have a devoted unicode script so they return UNKNOWN
if (!(Character.UnicodeScript.of(name.charAt(i)) + "").equals("UNKNOWN")) {
nonEmoji.add(name.charAt(i));//its not an emoji so we add it
}
}
// we then cycle through rebuilding the string
for (int i = 0; i < nonEmoji.size(); i++) {
newName += nonEmoji.get(i);
}
}
return newName;
}
so if we pass in a string:
remove_Emojis_For_Devices_API_24_Onwards("😊 test 😊 Indic:ढ Japanese:な 😊 Korean:ㅂ");
it returns: test Indic:ढ Japanese:な Korean:ㅂ
Emoji placement or count doesn't matter
I have written a calculator type app. My mates found that entering single decimal points only into the editText's makes the app crash. Decimal numbers and integers work fine, but I get a number format exception when .'s are entered.
I want to check if a single . has been placed in an editText, in order for me to display a toast telling the user to stop trying to crash the app.
My issue is that a . doesn't have a numerical value...
You can wrap it in a try/catch which should be done anyway when parsing text. So something like
try
{
int someInt = Integer.parseInt(et.getText().toString());
// other code
}
catch (NumberFormatException e)
{
// notify user with Toast, alert, etc...
}
This way it will protect against any number format exception and will make the code more reusable later on.
You can treat .1 as 0.1 by the following.
String text = et.getText().toString();
int len = text.length();
// Do noting if edit text just contains a "." without numbers
if(len==0 || (len==1 && text.charAt(0).equals(".")))
return;
if(text.charAt(0).equals(".") && text.length() > 1) {
text = "0" + text;
}
// Do your parsing and calculations
Is there a way to suggest or restrict keyboard input when selecting a NumberPicker so only the number controls are shown when entering values, similar to how you can use android:inputType="number" with a EditText?
I have a series of values, from 0.0 to 100.0 in 0.1 increments that I'd like to be able to use a NumberPicker to select in Android 4.3. In order to have the numbers selectable, I've created an array of strings which corresponds to these values, as shown below:
NumberPicker np = (NumberPicker) rootView.findViewById(R.id.programmingNumberPicker);
int numberOfIntensityOptions = 1001;
BigDecimal[] intensityDecimals = new BigDecimal[numberOfIntensityOptions];
for(int i = 0; i < intensityDecimals.length; i++ )
{
// Gets exact representations of 0.1, 0.2, 0.3 ... 99.9, 100.0
intensityDecimals[i] = BigDecimal.valueOf(i).divide(BigDecimal.TEN);
}
intensityStrings = new String[numberOfIntensityOptions];
for(int i = 0; i < intensityDecimals.length; i ++)
{
intensityStrings[i] = intensityDecimals[i].toString();
}
// this will allow a user to select numbers, and bring up a full keyboard. Alphabetic keys are
// ignored - Can I somehow change the keyboard for this control to suggest to use *only* a number keyboard
// to make it much more intuitive?
np.setMinValue(0);
np.setMaxValue(intensityStrings.length-1);
np.setDisplayedValues(intensityStrings);
np.setWrapSelectorWheel(false);
As more info, I've noticed that if I dont use the setDisplayedValues() method and instead set the integers directly, the numeric keyboard will be used, but the problem here is that the number being entered is 10 times more than it should be - e.g. if you enter "15" into the control its interpreted as "1.5"
// This will allow a user to select using a number keyboard, but input needs to be 10x more than it should be.
np.setMinValue(0);
np.setMaxValue(numberOfIntensityOptions-1);
np.setFormatter(new NumberPicker.Formatter() {
#Override
public String format(int value) {
return BigDecimal.valueOf(value).divide(BigDecimal.TEN).toString();
}
});
Any suggestions on how to raise a numeric keyboard to allow a user to enter decimal numbers like this?
I have successfully achieved this, borrowing heavily from #LuksProg's helpful answer to another question. The basic idea is to search for the EditText component of the NumberPicker and then to assign the input type as numeric. First add this method (thanks again to #LuksProg):
private EditText findInput(ViewGroup np) {
int count = np.getChildCount();
for (int i = 0; i < count; i++) {
final View child = np.getChildAt(i);
if (child instanceof ViewGroup) {
findInput((ViewGroup) child);
} else if (child instanceof EditText) {
return (EditText) child;
}
}
return null;
}
Then, in my Activity's onCreate() method I call this and set the input type:
np.setMinValue(0);
np.setMaxValue(intensityStrings.length-1);
EditText input = findInput(np);
input.setInputType(InputType.TYPE_CLASS_NUMBER);
That did the trick for me!
My answer is to improve on the basis of Alan Moore, just change the last line
input.setInputType(InputType.TYPE_CLASS_NUMBER);
to
input.setRawInputType(InputType.TYPE_CLASS_NUMBER);
then there will no more problem like personne3000 said, no more crashes.
I'm sorry my English is not good, but I hope you can understand what I mean.