Skip to content

Commit

Permalink
Release 0.5.0 (#82)
Browse files Browse the repository at this point in the history
* KeyboardFocusTextInput: text input wrapper for standardize TextInput focusing behavior (#80)

* feat: add text input wrapper

* chore: update readme with example

* feat: update is keyboard connected functionality (#81)

* chore: update version

* fix: revert back privious useKeyboardConnected logic

* chore: update version
  • Loading branch information
ArturKalach authored Jul 4, 2024
1 parent e383cc9 commit 94f34b4
Show file tree
Hide file tree
Showing 68 changed files with 1,005 additions and 496 deletions.
194 changes: 112 additions & 82 deletions README.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions android/src/main/java/com/reactnativea11y/A11yPackage.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.module.model.ReactModuleInfo;
import com.reactnativea11y.components.RCA11yFocusWrapperManager;
import com.reactnativea11y.components.RCA11yTextInputWrapperManager;

public class A11yPackage extends TurboReactPackage {

Expand Down Expand Up @@ -50,6 +52,7 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = new ArrayList<>();
viewManagers.add(new RCA11yFocusWrapperManager());
viewManagers.add(new RCA11yTextInputWrapperManager());
return viewManagers;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.reactnativea11y;
package com.reactnativea11y.components;

import android.content.Context;
import android.util.AttributeSet;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.reactnativea11y;
package com.reactnativea11y.components;

import android.view.KeyEvent;
import android.view.View;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.reactnativea11y.components;


import android.content.Context;
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.View;

import com.facebook.react.views.textinput.ReactEditText;
import com.facebook.react.views.view.ReactViewGroup;

public class RCA11yTextInputWrapper extends ReactViewGroup {

public static final byte FOCUS_BY_PRESS = 1;
private ReactEditText reactEditText = null;

private int focusType = 0;

public RCA11yTextInputWrapper(Context context) {
super(context);
}

public void setEditText(ReactEditText editText) {
if (editText != null) {
this.reactEditText = editText;
} else {
this.reactEditText.setOnFocusChangeListener(null);
}
}

public void setFocusType(int focusType) {
this.focusType = focusType;
}

public void setBlurType(int blurType) {
// Stub, Android does not allow to type in EditField from another view. Even focus remains typing with soft or hard keyboard won't work
}


@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (focusType == FOCUS_BY_PRESS && keyCode == KeyEvent.KEYCODE_SPACE) {
this.handleTextInputFocus();
}
return super.onKeyDown(keyCode, event);
}

@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
if ((direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) && focusType != FOCUS_BY_PRESS) {
this.handleTextInputFocus();
return true;
}

return super.requestFocus(direction, previouslyFocusedRect);
}

private void handleTextInputFocus() {
this.reactEditText.requestFocusFromJS();
this.setFocusable(false);

this.reactEditText.setOnFocusChangeListener((textInput, hasTextEditFocus) -> {
if (!hasTextEditFocus) {
this.setFocusable(true);
this.reactEditText.setFocusable(false);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.reactnativea11y.components;

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

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.textinput.ReactEditText;

import com.facebook.react.views.view.ReactViewGroup;
import com.reactnativea11y.events.FocusChangeEvent;

import java.util.Map;

@ReactModule(name = RCA11yTextInputWrapperManager.NAME)
public class RCA11yTextInputWrapperManager extends com.reactnativea11y.RCA11yTextInputWrapperManagerSpec<RCA11yTextInputWrapper> {

public static final String NAME = "RCA11yTextInputWrapper";

@Override
public String getName() {
return NAME;
}

@Override
public RCA11yTextInputWrapper createViewInstance(ThemedReactContext context) {
return subscribeOnHierarchy(new RCA11yTextInputWrapper(context));
}

@Override
protected void addEventEmitters(final ThemedReactContext reactContext, ReactViewGroup viewGroup) {
viewGroup.setFocusable(true);
viewGroup.setOnFocusChangeListener(
(v, hasFocus) -> {
FocusChangeEvent event = new FocusChangeEvent(viewGroup.getId(), hasFocus);
UIManagerHelper.getEventDispatcherForReactTag(reactContext, v.getId()).dispatchEvent(event);
});
}

protected RCA11yTextInputWrapper subscribeOnHierarchy(RCA11yTextInputWrapper viewGroup) {
viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
if (child instanceof ReactEditText) {
viewGroup.setEditText((ReactEditText) child);
}
}

@Override
public void onChildViewRemoved(View parent, View child) {
if (child instanceof ReactEditText) {
viewGroup.setEditText(null);
}
}
});

return viewGroup;
}

@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
Map<String, Object> export = MapBuilder.<String, Object>builder().build();
if (export == null) {
export = MapBuilder.newHashMap();
}

export.put(FocusChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onFocusChange"));

return export;
}

@Override
@ReactProp(name = "focusType")
public void setFocusType(RCA11yTextInputWrapper view, int value) {
view.setFocusType(value);
}

@Override
@ReactProp(name = "blurType")
public void setBlurType(RCA11yTextInputWrapper view, int value) {
view.setBlurType(value);
}


@Override
@ReactProp(name = "canBeFocused", defaultBoolean = true)
public void setCanBeFocused(RCA11yTextInputWrapper view, boolean value) {
view.setFocusable(value);
}

@Override
public void onDropViewInstance(@NonNull ReactViewGroup viewGroup) {
if (viewGroup instanceof RCA11yTextInputWrapper) {
RCA11yTextInputWrapper wrapper = (RCA11yTextInputWrapper) viewGroup;
wrapper.setEditText(null);
wrapper.setOnFocusChangeListener(null);
}
super.onDropViewInstance(viewGroup);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import android.os.Build;
import android.view.KeyEvent;

import com.reactnativea11y.RCA11yFocusWrapper;

import java.util.HashMap;

public class KeyboardKeyPressHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,16 @@
import android.content.res.Configuration;
import android.util.Log;
import android.view.View;
import android.os.Build;

import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.reactnativea11y.RCA11yUIManagerHelper;

import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;

public class KeyboardService implements LifecycleEventListener {
private final String NEW_CONFIG = "newConfig";
private final String ON_CONFIGURATION_CHANGED = "onConfigurationChanged";

private final ReactApplicationContext context;
private final BroadcastReceiver receiver;
private boolean isBroadcastRegistered = false;

public boolean isKeyboardConnected() {
final int keyboard = context.getResources().getConfiguration().keyboard;
Expand All @@ -38,11 +33,14 @@ public boolean isKeyboardConnected() {
public KeyboardService(ReactApplicationContext context) {
this.context = context;
context.addLifecycleEventListener(this);

receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Configuration newConfig = intent.getParcelableExtra(NEW_CONFIG);
keyboardChanged(newConfig.hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO);
if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
Configuration newConfig = context.getResources().getConfiguration();
keyboardChanged(newConfig.hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO);
}
}
};
keyboardChanged(isKeyboardConnected());
Expand Down Expand Up @@ -70,31 +68,21 @@ public void setKeyboardFocus(int tag) {
});
}

@Override
@SuppressLint("UnspecifiedRegisterReceiverFlag")
public void onHostResume() {
final Activity activity = context.getCurrentActivity();

if (activity == null) {
return;
}
private void registerBroadcast() {
if (isBroadcastRegistered) return;
isBroadcastRegistered = true;
final Activity activity = context.getCurrentActivity();

/**
* Starting with Android 14, apps and services that target Android 14 and use context-registered
* receivers are required to specify a flag to indicate whether or not the receiver should be
* exported to all other apps on the device: either RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED
* <a href="https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported"/>
*/
if (Build.VERSION.SDK_INT >= 34 && context.getApplicationInfo().targetSdkVersion >= 34) {
final int RECEIVER_NOT_EXPORTED = 4; // Same to Context.RECEIVER_NOT_EXPORTED but it allows to build with older SDK
activity.registerReceiver(receiver, new IntentFilter(ON_CONFIGURATION_CHANGED), RECEIVER_NOT_EXPORTED);
} else {
activity.registerReceiver(receiver, new IntentFilter(ON_CONFIGURATION_CHANGED));
if (activity != null) {
activity.registerReceiver(receiver, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
}
}

@Override
public void onHostPause() {
private void unregisterBroadcast() {
if (!isBroadcastRegistered) return;
isBroadcastRegistered = false;

final Activity activity = context.getCurrentActivity();
if (activity == null) return;
try {
Expand All @@ -103,6 +91,17 @@ public void onHostPause() {
}
}

@Override
@SuppressLint("UnspecifiedRegisterReceiverFlag")
public void onHostResume() {
registerBroadcast();
}

@Override
public void onHostPause() {
unregisterBroadcast();
}

@Override
public void onHostDestroy() {

Expand Down
15 changes: 15 additions & 0 deletions android/src/newarch/RCA11yTextInputWrapperManagerSpec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.reactnativea11y;

import com.facebook.react.viewmanagers.RCA11yTextInputWrapperManagerInterface;
import com.facebook.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;
import com.facebook.soloader.SoLoader;
import com.reactnativea11y.BuildConfig;

public abstract class RCA11yTextInputWrapperManagerSpec<T extends ReactViewGroup> extends ReactViewManager implements RCA11yTextInputWrapperManagerInterface<T> {
static {
if (BuildConfig.CODEGEN_MODULE_REGISTRATION != null) {
SoLoader.loadLibrary(BuildConfig.CODEGEN_MODULE_REGISTRATION);
}
}
}
13 changes: 13 additions & 0 deletions android/src/oldarch/RCA11yTextInputWrapperManagerSpec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativea11y;

import com.facebook.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;
import com.reactnativea11y.components.RCA11yTextInputWrapper;

public abstract class RCA11yTextInputWrapperManagerSpec<T extends ReactViewGroup> extends ReactViewManager {
public abstract void setCanBeFocused(T wrapper, boolean canBeFocused);

public abstract void setFocusType(RCA11yTextInputWrapper view, int value);

public abstract void setBlurType(RCA11yTextInputWrapper view, int value);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package com.a11ynewarch;

import android.content.Intent;
import android.content.res.Configuration;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;

public class MainActivity extends ReactActivity {
private final String ON_CONFIGURATION_CHANGED = "onConfigurationChanged";
private final String NEW_CONFIG = "newConfig";
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
Expand All @@ -36,12 +31,4 @@ protected ReactActivityDelegate createReactActivityDelegate() {
DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
);
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final Intent intent = new Intent(ON_CONFIGURATION_CHANGED);
intent.putExtra(NEW_CONFIG, newConfig);
this.sendBroadcast(intent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import android.app.Application;

import com.a11ynewarch.textinput.RCTEditTextPackage;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
Expand All @@ -26,7 +25,6 @@ public boolean getUseDeveloperSupport() {
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new RCTEditTextPackage());

return packages;
}
Expand Down
Loading

0 comments on commit 94f34b4

Please sign in to comment.