Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync Release/0.4.0 #51

Merged
merged 41 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
dbde0d9
fix: #43 Shift+Tab fails to focus previous fix
ArturKalach Sep 17, 2024
8fbb6be
chore: update version
ArturKalach Sep 17, 2024
408bad6
fix: text input tap focus fix
ArturKalach Sep 29, 2024
51b9160
chore: update version
ArturKalach Sep 29, 2024
af99068
fix: detach text inptu fix
ArturKalach Oct 1, 2024
2ccbfa7
chore: update version
ArturKalach Oct 1, 2024
4f79fc8
chore: update version
ArturKalach Oct 2, 2024
1d621d1
fix: new arch events on android
ArturKalach Oct 10, 2024
f17d701
chore: update version
ArturKalach Oct 10, 2024
225c45f
fix: update android event names
ArturKalach Oct 10, 2024
828402b
chore: udpate version
ArturKalach Oct 10, 2024
2af6760
chore: udpate version
ArturKalach Oct 11, 2024
f01b68b
feat: update focus system
ArturKalach Sep 23, 2024
e75476d
feat: alpha test
ArturKalach Sep 23, 2024
1dac106
fix: importsNotUsedAsValues fix
ArturKalach Sep 23, 2024
f0c8dfa
feat: update android for gesture handler
ArturKalach Oct 1, 2024
99435bd
feat: update press action on android
ArturKalach Oct 1, 2024
386128f
feat: update ios long press
ArturKalach Oct 2, 2024
bf79e3b
feat: rn screen
ArturKalach Oct 6, 2024
fa2f95f
test
ArturKalach Oct 12, 2024
52e3ac1
feat: update ios autofocus
ArturKalach Oct 13, 2024
f714dca
feat: update focus for newarch
ArturKalach Oct 20, 2024
90fa8f8
chore: update version
ArturKalach Oct 20, 2024
172629f
feat: update auto focus for ios
ArturKalach Oct 21, 2024
a1875a1
chore: update version
ArturKalach Oct 21, 2024
753782d
fix: android fixes
ArturKalach Oct 23, 2024
5094d8c
chore: update version
ArturKalach Oct 23, 2024
694d258
chore: update readme
ArturKalach Oct 23, 2024
3c05f1d
chore: update readme
ArturKalach Oct 23, 2024
1a669e4
feat: update module
ArturKalach Oct 26, 2024
93e16ca
feat: prepare rc
ArturKalach Oct 26, 2024
bf15790
feat: format native code
ArturKalach Oct 27, 2024
34d0821
feat: update android events
ArturKalach Nov 1, 2024
ef2717e
chore: update version
ArturKalach Nov 1, 2024
c44a229
feat: update auto focus for andriod
ArturKalach Nov 2, 2024
ded7b18
chore: update version
ArturKalach Nov 2, 2024
13c2741
feat: update auto focus for android
ArturKalach Nov 2, 2024
4529715
chore: update version
ArturKalach Nov 2, 2024
7a6b3f4
feat: update todos and readme
ArturKalach Nov 2, 2024
1359ce1
feat: update desable and focus state
ArturKalach Nov 3, 2024
db5be4b
chore: update version
ArturKalach Nov 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .github/images/android_example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .github/images/ios_example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
328 changes: 211 additions & 117 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,12 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
return () -> {
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
moduleInfos.put(
A11yKeyboardModule.NAME,
new ReactModuleInfo(
A11yKeyboardModule.NAME,
A11yKeyboardModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
));
moduleInfos.put(A11yKeyboardModule.NAME, new ReactModuleInfo(A11yKeyboardModule.NAME, A11yKeyboardModule.NAME, false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
));
return moduleInfos;
};
}
Expand Down
29 changes: 27 additions & 2 deletions android/src/main/java/com/externalkeyboard/events/EventHelper.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
package com.externalkeyboard.events;

import android.view.KeyEvent;

import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.EventDispatcher;

public class EventHelper {
public static void focusChanged(ReactContext context, int id, boolean hasFocus) {
FocusChangeEvent event = new FocusChangeEvent(id, hasFocus);
UIManagerHelper.getEventDispatcherForReactTag(context, id).dispatchEvent(event);
int surfaceId = UIManagerHelper.getSurfaceId(context);
FocusChangeEvent event = new FocusChangeEvent(surfaceId, id, hasFocus);
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id);
if (eventDispatcher != null) {
eventDispatcher.dispatchEvent(event);
}
}

public static void pressDown(ReactContext context, int id, int keyCode, KeyEvent keyEvent) {
int surfaceId = UIManagerHelper.getSurfaceId(context);
KeyPressDownEvent keyPressDownEvent = new KeyPressDownEvent(surfaceId, id, keyCode, keyEvent);
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id);
if (eventDispatcher != null) {
eventDispatcher.dispatchEvent(keyPressDownEvent);
}
}

public static void pressUp(ReactContext context, int id, int keyCode, KeyEvent keyEvent, boolean isLongPress) {
int surfaceId = UIManagerHelper.getSurfaceId(context);
KeyPressUpEvent keyPressUpEvent = new KeyPressUpEvent(surfaceId, id, keyCode, keyEvent, isLongPress);
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id);
if (eventDispatcher != null) {
eventDispatcher.dispatchEvent(keyPressUpEvent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,24 @@
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;


public class FocusChangeEvent extends Event<FocusChangeEvent> {
public static String EVENT_NAME = "topOnFocusChange";
public WritableMap mExtraData;

public FocusChangeEvent(int id, Boolean hasFocus) {
super(id);
WritableMap eventPayload = Arguments.createMap();
eventPayload.putBoolean("isFocused", hasFocus);
this.mExtraData = eventPayload;
}
public static String EVENT_NAME = "topFocusChange";
public WritableMap payload;

@Override
public void dispatch(RCTEventEmitter rCTEventEmitter) {
rCTEventEmitter.receiveEvent(this.getViewTag(), this.getEventName(), this.mExtraData);
public FocusChangeEvent(int surfaceId, int id, Boolean hasFocus) {
super(surfaceId, id);
payload = Arguments.createMap();
payload.putBoolean("isFocused", hasFocus);
}

@Override
public short getCoalescingKey() {
return 0;
}

@Override
public boolean canCoalesce() {
return false;
public String getEventName() {
return EVENT_NAME;
}

@Override
public String getEventName() {
return EVENT_NAME;
public WritableMap getEventData() {
return payload;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,34 @@
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;

public class KeyPressDownEvent extends Event<KeyPressDownEvent> {
public static String EVENT_NAME = "topOnKeyDownPress";
public WritableMap mExtraData;
public static String EVENT_NAME = "topKeyDownPress";
public WritableMap payload;

public KeyPressDownEvent(int id, int keyCode, KeyEvent keyEvent) {
super(id);
public KeyPressDownEvent(int surfaceId, int id, int keyCode, KeyEvent keyEvent) {
super(surfaceId, id);

int unicode = keyEvent.getUnicodeChar();
WritableMap eventPayload = Arguments.createMap();
eventPayload.putInt("keyCode", keyCode);
eventPayload.putInt("unicode", unicode);
eventPayload.putString("unicodeChar", String.valueOf((char)unicode));
eventPayload.putBoolean("isLongPress", keyEvent.isLongPress());
eventPayload.putBoolean("isAltPressed", keyEvent.isAltPressed());
eventPayload.putBoolean("isShiftPressed", keyEvent.isShiftPressed());
eventPayload.putBoolean("isCtrlPressed", keyEvent.isCtrlPressed());
eventPayload.putBoolean("isCapsLockOn", keyEvent.isCapsLockOn());
eventPayload.putBoolean("hasNoModifiers", keyEvent.hasNoModifiers());

this.mExtraData = eventPayload;
}

@Override
public void dispatch(RCTEventEmitter rCTEventEmitter) {
rCTEventEmitter.receiveEvent(this.getViewTag(), this.getEventName(), this.mExtraData);
}

@Override
public short getCoalescingKey() {
return 0;
}

@Override
public boolean canCoalesce() {
return false;
payload = Arguments.createMap();
payload.putInt("keyCode", keyCode);
payload.putInt("unicode", unicode);
payload.putString("unicodeChar", String.valueOf((char) unicode));
payload.putBoolean("isLongPress", keyEvent.isLongPress());
payload.putBoolean("isAltPressed", keyEvent.isAltPressed());
payload.putBoolean("isShiftPressed", keyEvent.isShiftPressed());
payload.putBoolean("isCtrlPressed", keyEvent.isCtrlPressed());
payload.putBoolean("isCapsLockOn", keyEvent.isCapsLockOn());
payload.putBoolean("hasNoModifiers", keyEvent.hasNoModifiers());
}

@Override
public String getEventName() {
return EVENT_NAME;
}

@Override
public WritableMap getEventData() {
return payload;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,32 @@
import com.facebook.react.uimanager.events.RCTEventEmitter;

public class KeyPressUpEvent extends Event<KeyPressUpEvent> {
public static String EVENT_NAME = "topOnKeyUpPress";
public WritableMap mExtraData;
public static String EVENT_NAME = "topKeyUpPress";
public WritableMap payload;

public KeyPressUpEvent(int id, int keyCode, KeyEvent keyEvent, boolean isLongPress) {
super(id);
public KeyPressUpEvent(int surfaceId, int id, int keyCode, KeyEvent keyEvent, boolean isLongPress) {
super(surfaceId, id);

int unicode = keyEvent.getUnicodeChar();
WritableMap eventPayload = Arguments.createMap();
eventPayload.putInt("keyCode", keyCode);
eventPayload.putInt("unicode", unicode);
eventPayload.putString("unicodeChar", String.valueOf((char)unicode));
eventPayload.putBoolean("isLongPress", isLongPress);
eventPayload.putBoolean("isAltPressed", keyEvent.isAltPressed());
eventPayload.putBoolean("isShiftPressed", keyEvent.isShiftPressed());
eventPayload.putBoolean("isCtrlPressed", keyEvent.isCtrlPressed());
eventPayload.putBoolean("isCapsLockOn", keyEvent.isCapsLockOn());
eventPayload.putBoolean("hasNoModifiers", keyEvent.hasNoModifiers());

this.mExtraData = eventPayload;
}

@Override
public void dispatch(RCTEventEmitter rCTEventEmitter) {
rCTEventEmitter.receiveEvent(this.getViewTag(), this.getEventName(), this.mExtraData);
}

@Override
public short getCoalescingKey() {
return 0;
}

@Override
public boolean canCoalesce() {
return false;
payload = Arguments.createMap();
payload.putInt("keyCode", keyCode);
payload.putInt("unicode", unicode);
payload.putString("unicodeChar", String.valueOf((char) unicode));
payload.putBoolean("isLongPress", isLongPress);
payload.putBoolean("isAltPressed", keyEvent.isAltPressed());
payload.putBoolean("isShiftPressed", keyEvent.isShiftPressed());
payload.putBoolean("isCtrlPressed", keyEvent.isCtrlPressed());
payload.putBoolean("isCapsLockOn", keyEvent.isCapsLockOn());
payload.putBoolean("hasNoModifiers", keyEvent.hasNoModifiers());
}

@Override
public String getEventName() {
return EVENT_NAME;
}

@Override
public WritableMap getEventData() {
return payload;
}
}
19 changes: 19 additions & 0 deletions android/src/main/java/com/externalkeyboard/helper/FocusHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.externalkeyboard.helper;

import android.view.View;
import android.view.ViewGroup;

public class FocusHelper {
public static View getFocusableView(ViewGroup viewGroup) {
if (viewGroup.getChildCount() > 0) {
View subView = viewGroup.getChildAt(0);
if (subView.isFocusable()) {
return subView;
} else if (subView instanceof ViewGroup) {
return getFocusableView((ViewGroup) subView);
}
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ public void setKeyboardFocus(int tag) {
try {
int uiManagerType = ViewUtil.getUIManagerType(tag);
if (uiManagerType == FABRIC) {
UIManager fabricUIManager =
UIManagerHelper.getUIManager(context, uiManagerType);
UIManager fabricUIManager = UIManagerHelper.getUIManager(context, uiManagerType);
if (fabricUIManager != null) {
View view = fabricUIManager.resolveView(tag);
view.requestFocus();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,117 @@
package com.externalkeyboard.views.ExternalKeyboardView;

import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;

import androidx.annotation.Nullable;
import com.externalkeyboard.events.EventHelper;

import com.externalkeyboard.helper.FocusHelper;
import com.externalkeyboard.services.KeyboardKeyPressHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.views.view.ReactViewGroup;

public class ExternalKeyboardView extends ReactViewGroup {
public boolean hasKeyDownListener = false;
public boolean hasKeyUpListener = false;
public boolean autoFocus = false;
public boolean hasBeenFocused = false;

private final KeyboardKeyPressHandler keyboardKeyPressHandler;
private final Context context;
private View listeningView;

public ExternalKeyboardView(Context context) {
super(context);
this.context = context;
this.keyboardKeyPressHandler = new KeyboardKeyPressHandler();
}


private boolean onKeyPressHandler(ReactViewGroup reactViewGroup, int keyCode, KeyEvent keyEvent, ThemedReactContext reactContext) {
if (!(reactViewGroup instanceof ExternalKeyboardView)) return false;
ExternalKeyboardView viewGroup = (ExternalKeyboardView) reactViewGroup;

if (!viewGroup.hasKeyUpListener && !viewGroup.hasKeyDownListener) {
return false;
}

if (keyCode == KeyEvent.KEYCODE_TAB) {
return false;
}

KeyboardKeyPressHandler.PressInfo pressInfo = keyboardKeyPressHandler.getEventsFromKeyPress(keyCode, keyEvent);

if (pressInfo.firePressDownEvent && viewGroup.hasKeyDownListener) {
EventHelper.pressDown((ReactContext) context, viewGroup.getId(), keyCode, keyEvent);
return true;
}

if (pressInfo.firePressUpEvent && viewGroup.hasKeyUpListener) {
EventHelper.pressUp((ReactContext) context, viewGroup.getId(), keyCode, keyEvent, pressInfo.isLongPress);
return true;
}

return false;
}


@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();

this.listeningView = getFocusingView();
setFocusable(this.listeningView == this);

this.listeningView.setOnFocusChangeListener((focusedView, hasFocus) -> {
EventHelper.focusChanged((ReactContext) context, this.getId(), hasFocus);
});

this.listeningView.setOnKeyListener((View v, int keyCode, KeyEvent keyEvent) -> onKeyPressHandler(this, keyCode, keyEvent, (ThemedReactContext) context));

if (autoFocus && !hasBeenFocused) {
this.autoFocusOnDraw();
hasBeenFocused = true;
}
}

private void autoFocusOnDraw() {
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
focus();

return true;
}
});
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (this.listeningView != null) {
this.listeningView.setOnFocusChangeListener(null);
}
}

private View getFocusingView() {
View focusableView = FocusHelper.getFocusableView(this);
return focusableView != null ? focusableView : this;
}

public void setCanBeFocused(boolean canBeFocused) {
int descendantFocusability = canBeFocused ? ViewGroup.FOCUS_BEFORE_DESCENDANTS : ViewGroup.FOCUS_BLOCK_DESCENDANTS;
this.setDescendantFocusability(descendantFocusability);
}


public void focus() {
View focusingView = this.getFocusingView();
focusingView.requestFocus();
}

}
Loading
Loading