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

React Native 0.77.x #7945

Open
ertugruldogan opened this issue Dec 9, 2024 · 8 comments
Open

React Native 0.77.x #7945

ertugruldogan opened this issue Dec 9, 2024 · 8 comments

Comments

@ertugruldogan
Copy link

ertugruldogan commented Dec 9, 2024

The for Android 27.01.2025 topic updated.

Image

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\react\modal\ModalHostLayout.kt

package com.reactnativenavigation.react.modal

import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.view.View
import android.view.ViewGroup
import android.view.ViewStructure
import android.view.accessibility.AccessibilityEvent
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.uimanager.ThemedReactContext
import com.reactnativenavigation.options.Options
import com.reactnativenavigation.options.params.Bool
import com.reactnativenavigation.utils.CompatUtils
import com.reactnativenavigation.viewcontrollers.viewcontroller.YellowBoxDelegate
import com.reactnativenavigation.viewcontrollers.viewcontroller.overlay.ViewControllerOverlay
import java.util.*

@SuppressLint("ViewConstructor")
open class ModalHostLayout(reactContext: ThemedReactContext) : ViewGroup(reactContext), LifecycleEventListener {
    val viewController = ModalLayoutController(
        reactContext,
        reactContext.currentActivity, CompatUtils.generateViewId().toString(),
        YellowBoxDelegate(reactContext), Options().apply {
            hardwareBack.dismissModalOnPress = Bool(false)
        }, ViewControllerOverlay(reactContext),
        getHostId = { this.id }
    )
    private val mHostView = viewController.view.modalContentLayout

    init {
        (context as ReactContext).addLifecycleEventListener(this)
    }

@TargetApi(23)
override fun dispatchProvideStructure(structure: ViewStructure?) {
    structure?.let { 
        mHostView.dispatchProvideStructure(it)
    }
}


    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}

    override fun addView(child: View?, index: Int) {
        UiThreadUtil.assertOnUiThread()
        mHostView.addView(child, index)
    }

    override fun getChildCount(): Int {
        return mHostView.childCount
    }

    override fun getChildAt(index: Int): View? {
        return mHostView.getChildAt(index)
    }

    override fun removeView(child: View?) {
        UiThreadUtil.assertOnUiThread()
        mHostView.removeView(child)
    }

    override fun removeViewAt(index: Int) {
        UiThreadUtil.assertOnUiThread()
        val child = getChildAt(index)
        mHostView.removeView(child)
    }

    override fun addChildrenForAccessibility(outChildren: ArrayList<View?>?) {}

    override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent?): Boolean { return false }

    open fun onDropInstance() { (this.context as ReactContext).removeLifecycleEventListener(this) }

    override fun onHostResume() {}

    override fun onHostPause() {}

    override fun onHostDestroy() { onDropInstance() }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\react\NavigationModule.java

package com.reactnativenavigation.react;

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

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.reactnativenavigation.NavigationActivity;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.options.LayoutFactory;
import com.reactnativenavigation.options.LayoutNode;
import com.reactnativenavigation.options.Options;
import com.reactnativenavigation.options.parsers.JSONParser;
import com.reactnativenavigation.options.parsers.LayoutNodeParser;
import com.reactnativenavigation.options.parsers.TypefaceLoader;
import com.reactnativenavigation.react.events.EventEmitter;
import com.reactnativenavigation.utils.LaunchArgsParser;
import com.reactnativenavigation.utils.Now;
import com.reactnativenavigation.utils.SystemUiUtils;
import com.reactnativenavigation.utils.UiThread;
import com.reactnativenavigation.utils.UiUtils;
import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;

import java.util.ArrayList;
import java.util.Objects;

import static com.reactnativenavigation.utils.UiUtils.pxToDp;

import android.app.Activity;

public class NavigationModule extends ReactContextBaseJavaModule {
    private static final String NAME = "RNNBridgeModule";

    private final Now now = new Now();
    private final ReactInstanceManager reactInstanceManager;
    private final JSONParser jsonParser;
    private final LayoutFactory layoutFactory;
    private EventEmitter eventEmitter;

    @SuppressWarnings("WeakerAccess")
    public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, LayoutFactory layoutFactory) {
        this(reactContext, reactInstanceManager, new JSONParser(), layoutFactory);
    }

    public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, JSONParser jsonParser, LayoutFactory layoutFactory) {
        super(reactContext);
        this.reactInstanceManager = reactInstanceManager;
        this.jsonParser = jsonParser;
        this.layoutFactory = layoutFactory;
        reactContext.addLifecycleEventListener(new LifecycleEventListenerAdapter() {
            @Override
            public void onHostPause() {
                super.onHostPause();
                UiUtils.runOnMainThread(() -> {
                    if (activity() != null) navigator().onHostPause();
                });
            }

            @Override
            public void onHostResume() {
                eventEmitter = new EventEmitter(reactContext);
                navigator().setEventEmitter(eventEmitter);
                layoutFactory.init(
                        activity(),
                        eventEmitter,
                        navigator().getChildRegistry(),
                        ((NavigationApplication) activity().getApplication()).getExternalComponents()
                );
                UiUtils.runOnMainThread(() -> navigator().onHostResume());
            }
        });
    }

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

    @ReactMethod
    public void getLaunchArgs(String commandId, Promise promise) {
        promise.resolve(LaunchArgsParser.parse(activity()));
    }

    private WritableMap createNavigationConstantsMap() {
        ReactApplicationContext ctx = getReactApplicationContext();
        final Activity currentActivity = ctx.getCurrentActivity();
        WritableMap constants = Arguments.createMap();
        constants.putString(Constants.BACK_BUTTON_JS_KEY, Constants.BACK_BUTTON_ID);
        constants.putDouble(Constants.BOTTOM_TABS_HEIGHT_KEY, pxToDp(ctx, UiUtils.getBottomTabsHeight(ctx)));
        constants.putDouble(Constants.STATUS_BAR_HEIGHT_KEY, pxToDp(ctx, SystemUiUtils.getStatusBarHeight(currentActivity)));
        constants.putDouble(Constants.TOP_BAR_HEIGHT_KEY, pxToDp(ctx, UiUtils.getTopBarHeight(ctx)));
        return constants;
    }

    @ReactMethod
    public void getNavigationConstants(Promise promise) {
        promise.resolve(createNavigationConstantsMap());
    }

    @ReactMethod(isBlockingSynchronousMethod = true)
    public WritableMap getNavigationConstantsSync() {
        return createNavigationConstantsMap();
    }

    @ReactMethod
    public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) {
        final LayoutNode layoutTree = LayoutNodeParser.parse(Objects.requireNonNull(jsonParser.parse(rawLayoutTree).optJSONObject("root")));
        handle(() -> {
            final ViewController<?> viewController = layoutFactory.create(layoutTree);
            navigator().setRoot(viewController, new NativeCommandListener("setRoot", commandId, promise, eventEmitter, now), reactInstanceManager);
        });
    }

    @ReactMethod
    public void setDefaultOptions(ReadableMap options) {
        handle(() -> {
            Options defaultOptions = parse(options);
            layoutFactory.setDefaultOptions(defaultOptions);
            navigator().setDefaultOptions(defaultOptions);
        });
    }

    @ReactMethod
    public void mergeOptions(String onComponentId, @Nullable ReadableMap options) {
        handle(() -> navigator().mergeOptions(onComponentId, parse(options)));
    }

    @ReactMethod
    public void push(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) {
        final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
        handle(() -> {
            final ViewController<?> viewController = layoutFactory.create(layoutTree);
            navigator().push(onComponentId, viewController, new NativeCommandListener("push", commandId, promise, eventEmitter, now));
        });
    }

    @ReactMethod
    public void setStackRoot(String commandId, String onComponentId, ReadableArray children, Promise promise) {
        handle(() -> {
            ArrayList<ViewController<?>> _children = new ArrayList<>();
            for (int i = 0; i < children.size(); i++) {
                final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(children.getMap(i)));
                _children.add(layoutFactory.create(layoutTree));
            }
            navigator().setStackRoot(onComponentId, _children, new NativeCommandListener("setStackRoot", commandId, promise, eventEmitter, now));
        });
    }

    @ReactMethod
    public void pop(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
        handle(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener("pop", commandId, promise, eventEmitter, now)));
    }

    @ReactMethod
    public void popTo(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
        handle(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener("popTo", commandId, promise, eventEmitter, now)));
    }

    @ReactMethod
    public void popToRoot(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
        handle(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener("popToRoot", commandId, promise, eventEmitter, now)));
    }

    @ReactMethod
    public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promise) {
        final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
        handle(() -> {
            final ViewController<?> viewController = layoutFactory.create(layoutTree);
            navigator().showModal(viewController, new NativeCommandListener("showModal", commandId, promise, eventEmitter, now));
        });
    }

    @ReactMethod
    public void dismissModal(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) {
        handle(() -> {
            navigator().mergeOptions(componentId, parse(mergeOptions));
            navigator().dismissModal(componentId, new NativeCommandListener("dismissModal", commandId, promise, eventEmitter, now));
        });
    }

    @ReactMethod
    public void dismissAllModals(String commandId, @Nullable ReadableMap mergeOptions, Promise promise) {
        handle(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener("dismissAllModals", commandId, promise, eventEmitter, now)));
    }

    @ReactMethod
    public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise promise) {
        final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree));
        handle(() -> {
            final ViewController<?> viewController = layoutFactory.create(layoutTree);
            navigator().showOverlay(viewController, new NativeCommandListener("showOverlay", commandId, promise, eventEmitter, now));
        });
    }

    @ReactMethod
    public void dismissOverlay(String commandId, String componentId, Promise promise) {
        handle(() -> navigator().dismissOverlay(componentId, new NativeCommandListener("dismissOverlay", commandId, promise, eventEmitter, now)));
    }

    @ReactMethod
    public void dismissAllOverlays(String commandId, Promise promise) {
        handle(() -> navigator().dismissAllOverlays(new NativeCommandListener("dismissAllOverlays", commandId, promise, eventEmitter, now)));
    }

    private Navigator navigator() {
        return activity().getNavigator();
    }

    private Options parse(@Nullable ReadableMap mergeOptions) {
        ReactApplicationContext ctx = getReactApplicationContext();
        return mergeOptions ==
                null ? Options.EMPTY : Options.parse(ctx, new TypefaceLoader(activity()), jsonParser.parse(mergeOptions));
    }

    protected void handle(Runnable task) {
        UiThread.post(() -> {
            if (getCurrentActivity() != null && !activity().isFinishing()) {
                task.run();
            }
        });
    }

    protected NavigationActivity activity() {
        return (NavigationActivity) getCurrentActivity();
    }

    @Override
    public void invalidate() {
        final NavigationActivity navigationActivity = activity();
        if (navigationActivity != null) {
            navigationActivity.onCatalystInstanceDestroy();
        }
        super.invalidate();
    }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\react\ReactView.java

package com.reactnativenavigation.react;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.reactnativenavigation.viewcontrollers.viewcontroller.ScrollEventListener;
import com.reactnativenavigation.react.events.ComponentType;
import com.reactnativenavigation.react.events.EventEmitter;
import com.reactnativenavigation.viewcontrollers.viewcontroller.IReactView;
import com.reactnativenavigation.views.component.Renderable;

import androidx.annotation.RestrictTo;

@SuppressLint("ViewConstructor")
public class ReactView extends ReactRootView implements IReactView, Renderable {

    private final ReactInstanceManager reactInstanceManager;
    private final String componentId;
    private final String componentName;
    private boolean isAttachedToReactInstance = false;
    private final JSTouchDispatcher jsTouchDispatcher;

    public ReactView(final Context context, ReactInstanceManager reactInstanceManager, String componentId, String componentName) {
        super(context);
        this.reactInstanceManager = reactInstanceManager;
        this.componentId = componentId;
        this.componentName = componentName;
        jsTouchDispatcher = new JSTouchDispatcher(this);
        setIsFabric(ReactNativeFeatureFlags.enableFabricRenderer());
    }

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

    public void start() {
        if (isAttachedToReactInstance) return;
        isAttachedToReactInstance = true;
        final Bundle opts = new Bundle();
        opts.putString("componentId", componentId);
        startReactApplication(reactInstanceManager, componentName, opts);
    }

    @Override
    public boolean isReady() {
        return isAttachedToReactInstance;
    }

    @Override
    public ReactView asView() {
        return this;
    }

    @Override
    public void destroy() {
        unmountReactApplication();
    }

    public void sendComponentWillStart(ComponentType type) {
        this.post(()->{
            if (this.reactInstanceManager == null) return;
            ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
            if (currentReactContext != null)
                new EventEmitter(currentReactContext).emitComponentWillAppear(componentId, componentName, type);
        });
    }

    public void sendComponentStart(ComponentType type) {
        this.post(()->{
            if (this.reactInstanceManager == null) return;
            ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
            if (currentReactContext != null) {
                new EventEmitter(currentReactContext).emitComponentDidAppear(componentId, componentName, type);
            }
        });
    }

    public void sendComponentStop(ComponentType type) {
        if (this.reactInstanceManager == null) return;
        ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
        if (currentReactContext != null) {
            new EventEmitter(currentReactContext).emitComponentDidDisappear(componentId, componentName, type);
        }
    }

    @Override
    public void sendOnNavigationButtonPressed(String buttonId) {
        if (this.reactInstanceManager == null) return;
        ReactContext currentReactContext = reactInstanceManager.getCurrentReactContext();
        if (currentReactContext != null) {
            new EventEmitter(currentReactContext).emitOnNavigationButtonPressed(componentId, buttonId);
        }
    }

    @Override
    public ScrollEventListener getScrollEventListener() {
        return new ScrollEventListener(getEventDispatcher());
    }

    @Override
    public void dispatchTouchEventToJs(MotionEvent event) {
        jsTouchDispatcher.handleTouchEvent(event, getEventDispatcher());
    }

    @Override
    public boolean isRendered() {
        return getChildCount() >= 1;
    }

    public EventDispatcher getEventDispatcher() {
        ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
        return reactContext == null ? null : reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
    }

    @RestrictTo(RestrictTo.Scope.TESTS)
    public String getComponentName() {
        return componentName;
    }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\utils\ReactTypefaceUtils.java

/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

// This file was copied over from react-native
// to provide backwards compatibility < rn 0.62
//
// TODO: Remove me and use com.facebook.react.views.text.ReactTypefaceUtils
// once we drop support for rn < 0.62

package com.reactnativenavigation.utils;

import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.views.text.ReactFontManager;
import com.facebook.react.common.ReactConstants;
import java.util.ArrayList;
import java.util.List;

public class ReactTypefaceUtils {
  public static final int UNSET = -1;

  public static int parseFontWeight(@Nullable String fontWeightString) {
    int fontWeightNumeric =
        fontWeightString != null ? parseNumericFontWeight(fontWeightString) : UNSET;
    int fontWeight = fontWeightNumeric != UNSET ? fontWeightNumeric : Typeface.NORMAL;

    if (fontWeight == 700 || "bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
    else if (fontWeight == 400 || "normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;

    return fontWeight;
  }

  public static int parseFontStyle(@Nullable String fontStyleString) {
    int fontStyle = UNSET;
    if ("italic".equals(fontStyleString)) {
      fontStyle = Typeface.ITALIC;
    } else if ("normal".equals(fontStyleString)) {
      fontStyle = Typeface.NORMAL;
    }

    return fontStyle;
  }

  public static @Nullable String parseFontVariant(@Nullable ReadableArray fontVariantArray) {
    if (fontVariantArray == null || fontVariantArray.size() == 0) {
      return null;
    }

    List<String> features = new ArrayList<>();
    for (int i = 0; i < fontVariantArray.size(); i++) {
      // see https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
      String fontVariant = fontVariantArray.getString(i);
      if (fontVariant != null) {
        switch (fontVariant) {
          case "small-caps":
            features.add("'smcp'");
            break;
          case "oldstyle-nums":
            features.add("'onum'");
            break;
          case "lining-nums":
            features.add("'lnum'");
            break;
          case "tabular-nums":
            features.add("'tnum'");
            break;
          case "proportional-nums":
            features.add("'pnum'");
            break;
        }
      }
    }

    return TextUtils.join(", ", features);
  }

  public static Typeface applyStyles(
      @Nullable Typeface typeface,
      int style,
      int weight,
      @Nullable String family,
      AssetManager assetManager) {
    int oldStyle;
    if (typeface == null) {
      oldStyle = 0;
    } else {
      oldStyle = typeface.getStyle();
    }

    int want = 0;
    if ((weight == Typeface.BOLD)
        || ((oldStyle & Typeface.BOLD) != 0 && weight == ReactConstants.UNSET)) {
      want |= Typeface.BOLD;
    }

    if ((style == Typeface.ITALIC)
        || ((oldStyle & Typeface.ITALIC) != 0 && style == ReactConstants.UNSET)) {
      want |= Typeface.ITALIC;
    }

    if (family != null) {
      typeface = ReactFontManager.getInstance().getTypeface(family, want, weight, assetManager);
    } else if (typeface != null) {
      // TODO(t9055065): Fix custom fonts getting applied to text children with different style
      typeface = Typeface.create(typeface, want);
    }

    if (typeface != null) {
      return typeface;
    } else {
      return Typeface.defaultFromStyle(want);
    }
  }

  /**
   * Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
   * return the weight.
   */
  private static int parseNumericFontWeight(String fontWeightString) {
    // This should be much faster than using regex to verify input and Integer.parseInt
    return fontWeightString.length() == 3
            && fontWeightString.endsWith("00")
            && fontWeightString.charAt(0) <= '9'
            && fontWeightString.charAt(0) >= '1'
        ? 100 * (fontWeightString.charAt(0) - '0')
        : UNSET;
  }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\utils\ReactViewGroup.kt

package com.reactnativenavigation.utils

import com.facebook.react.views.view.ReactViewGroup

val ReactViewGroup.borderRadius: Float
    get() =  0f

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\utils\ViewUtils.java

package com.reactnativenavigation.utils;

import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import java.util.ArrayList;
import java.util.List;

import androidx.annotation.Nullable;

import static com.reactnativenavigation.utils.ObjectUtils.perform;

public class ViewUtils {
    @Nullable
    public static <T extends View> T findChildByClass(ViewGroup root, Class<T> clazz) {
        for (int i = 0; i < root.getChildCount(); i++) {
            View view = root.getChildAt(i);
            if (clazz.isAssignableFrom(view.getClass())) {
                return (T) view;
            }

            if (view instanceof ViewGroup) {
                view = findChildByClass((ViewGroup) view, clazz);
                if (view != null && clazz.isAssignableFrom(view.getClass())) {
                    return (T) view;
                }
            }
        }
        return null;
    }

    public static <T> List<T> findChildrenByClassRecursive(ViewGroup root, Class<?> clazz) {
        return findChildrenByClassRecursive(root, clazz, child -> true);
    }

    public static <T> List<T> findChildrenByClassRecursive(ViewGroup root, Class<?> clazz, Matcher<T> matcher) {
        ArrayList<T> ret = new ArrayList<>();
        for (int i = 0; i < root.getChildCount(); i++) {
            View view = root.getChildAt(i);
            if (view instanceof ViewGroup) {
                ret.addAll(findChildrenByClassRecursive((ViewGroup) view, clazz, matcher));
            }
            if (clazz.isAssignableFrom(view.getClass()) && matcher.match((T) view)) {
                ret.add((T) view);
            }
        }
        return ret;
    }

    public static <T> List<T> findChildrenByClass(ViewGroup root, Class<T> clazz) {
        return findChildrenByClass(root, clazz, child -> true);
    }

    public static <T> List<T> findChildrenByClass(ViewGroup root, Class<?> clazz, Matcher<T> matcher) {
        List<T> ret = new ArrayList<>();
        for (int i = 0; i < root.getChildCount(); i++) {
            View child = root.getChildAt(i);
            if (clazz.isAssignableFrom(child.getClass()) && matcher.match((T) child)) {
                ret.add((T) child);
            }
        }
        return ret;
    }

    public interface Matcher<T> {
        boolean match(T child);
    }

    public static boolean isChildOf(ViewGroup parent, View child) {
        if (parent == child) return false;

        for (int i = 0; i < parent.getChildCount(); i++) {
            View view = parent.getChildAt(i);
            if (view == child) {
                return true;
            }

            if (view instanceof ViewGroup) {
                if (isChildOf((ViewGroup) view, child)) return true;
            }
        }
        return false;
    }

    public static int getHeight(View view) {
        if (view.getLayoutParams() == null) return 0;
        return view.getLayoutParams().height < 0 ? view.getHeight() : view.getLayoutParams().height;
    }

    public static Point getLocationOnScreen(View view) {
        int[] xy = new int[2];
        view.getLocationOnScreen(xy);
        return new Point(xy[0], xy[1]);
    }

    public static boolean areDimensionsEqual(View a, View b) {
        return a.getWidth() == b.getWidth() && a.getHeight() == b.getHeight();
    }

    public static int getIndexInParent(View view) {
        ViewParent parent = view.getParent();
        if (parent == null) return -1;
        return ((ViewGroup) parent).indexOfChild(view);
    }

    public static int getBackgroundColor(View view) {
        if (view.getBackground() instanceof ColorDrawable) {
            return ((ColorDrawable) view.getBackground()).getColor();
        }
        throw new RuntimeException("Background is not a ColorDrawable");
    }

    public static boolean isVisible(View view) {
        return perform(view, false, v -> v.getVisibility() == View.VISIBLE);
    }

    public static int topMargin(View view) {
        return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).topMargin;
    }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\viewcontrollers\viewcontroller\LayoutDirectionApplier.kt

package com.reactnativenavigation.viewcontrollers.viewcontroller

import com.facebook.react.ReactInstanceManager
import com.facebook.react.modules.i18nmanager.I18nUtil
import com.reactnativenavigation.options.Options
import com.facebook.react.bridge.ReactContext

class LayoutDirectionApplier {
    fun apply(root: ViewController<*>, options: Options, instanceManager: ReactInstanceManager) {
        val reactContext = instanceManager.currentReactContext as? ReactContext
        if (options.layout.direction.hasValue() && reactContext != null) {
            root.activity.window.decorView.layoutDirection = options.layout.direction.get()
            I18nUtil.getInstance().allowRTL(reactContext, options.layout.direction.isRtl)
            I18nUtil.getInstance().forceRTL(reactContext, options.layout.direction.isRtl)
        }
    }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\views\element\animators\BackgroundColorAnimator.kt

package com.reactnativenavigation.views.element.animators

import android.animation.Animator
import android.animation.ObjectAnimator
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import com.facebook.react.views.text.ReactTextView
import com.reactnativenavigation.options.SharedElementTransitionOptions
import com.reactnativenavigation.utils.ColorUtils
import com.reactnativenavigation.utils.ViewUtils

class BackgroundColorAnimator(from: View, to: View) : PropertyAnimatorCreator<ViewGroup>(from, to) {
    override fun shouldAnimateProperty(fromChild: ViewGroup, toChild: ViewGroup): Boolean {
        val fromBackground = fromChild.background
        val toBackground = toChild.background

        // Sadece ColorDrawable arka planlarla çalışmayı kontrol et
        return fromBackground is ColorDrawable &&
                toBackground is ColorDrawable &&
                fromBackground.color != toBackground.color
    }

    override fun excludedViews() = listOf(ReactTextView::class.java)

    override fun create(options: SharedElementTransitionOptions): Animator {
        val fromColor = (from.background as? ColorDrawable)?.color ?: 0
        val toColor = (to.background as? ColorDrawable)?.color ?: 0

        return ObjectAnimator.ofArgb(from, "backgroundColor", fromColor, toColor)
    }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\views\element\animators\BackgroundColorEvaluator.kt

package com.reactnativenavigation.views.element.animators

import android.animation.TypeEvaluator
import android.graphics.drawable.ColorDrawable
import androidx.core.graphics.ColorUtils

class BackgroundColorEvaluator(private val background: ColorDrawable) : TypeEvaluator<DoubleArray> {
    private val color = DoubleArray(3)

    override fun evaluate(ratio: Float, from: DoubleArray, to: DoubleArray): DoubleArray {
        // LAB renk alanında harmanlama işlemi
        ColorUtils.blendLAB(from, to, ratio.toDouble(), color)
        // LAB renk değerlerini ARGB formatına dönüştürüp arka plana uygula
        background.color = com.reactnativenavigation.utils.ColorUtils.labToColor(color)
        return color
    }
}

react-native-navigation\lib\android\app\src\main\java\com\reactnativenavigation\NavigationApplication.java

package com.reactnativenavigation;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.soloader.SoLoader;
import com.facebook.react.soloader.OpenSourceMergedSoMapping;
import com.reactnativenavigation.react.ReactGateway;
import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;

import java.util.HashMap;
import java.util.Map;

import androidx.annotation.NonNull;

public abstract class NavigationApplication extends Application implements ReactApplication {

	private ReactGateway reactGateway;
	public static NavigationApplication instance;
	final Map<String, ExternalComponentCreator> externalComponents = new HashMap<>();

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        try {
            SoLoader.init(this, OpenSourceMergedSoMapping.INSTANCE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        reactGateway = createReactGateway();
    }

    /**
     * Subclasses of NavigationApplication may override this method to create the singleton instance
     * of {@link ReactGateway}. For example, subclasses may wish to provide a custom {@link ReactNativeHost}
     * with the ReactGateway. This method will be called exactly once, in the application's {@link #onCreate()} method.
     *
     * Custom {@link ReactNativeHost}s must be sure to include {@link com.reactnativenavigation.react.NavigationPackage}
     *
     * @return a singleton {@link ReactGateway}
     */
	protected ReactGateway createReactGateway() {
	    return new ReactGateway(getReactNativeHost());
    }
    
	public ReactGateway getReactGateway() {
		return reactGateway;
	}

    /**
     * Generally no need to override this; override for custom permission handling.
     */
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    }

    /**
     * Register a native View which can be displayed using the given {@code name}
     * @param name Unique name used to register the native view
     * @param creator Used to create the view at runtime
     */
    @SuppressWarnings("unused")
    public void registerExternalComponent(String name, ExternalComponentCreator creator) {
        if (externalComponents.containsKey(name)) {
            throw new RuntimeException("A component has already been registered with this name: " + name);
        }
        externalComponents.put(name, creator);
    }

    public final Map<String, ExternalComponentCreator> getExternalComponents() {
        return externalComponents;
    }
}

react-native-navigation\lib\android\app\src\reactNative71\java\com\reactnativenavigation\react\modal\ModalContentLayout.kt

package com.reactnativenavigation.react.modal

import android.content.Context
import android.view.MotionEvent
import android.view.View
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerModule
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.views.view.ReactViewGroup

class ModalContentLayout(context: Context?) : ReactViewGroup(context) {
    private var hasAdjustedSize = false
    private var viewWidth = 0
    private var viewHeight = 0

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w
        viewHeight = h
        this.updateFirstChildView()
    }

    private fun updateFirstChildView() {
        if (this.childCount > 0) {
            hasAdjustedSize = false
            val viewTag = getChildAt(0).id
            val reactContext: ReactContext = this.getReactContext()
            reactContext.runOnNativeModulesQueueThread {
                val uiManager = reactContext.getNativeModule(UIManagerModule::class.java)
                uiManager?.updateNodeSize(viewTag, viewWidth, viewHeight)
            }
        } else {
            hasAdjustedSize = true
        }
    }

    override fun addView(child: View?, index: Int, params: LayoutParams?) {
        super.addView(child, index, params)
        if (hasAdjustedSize) {
            updateFirstChildView()
        }
    }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        // Dokunma olayları burada işlenebilir
        return super.onInterceptTouchEvent(event)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        // Dokunma olayları burada işlenebilir
        return super.onTouchEvent(event)
    }

    private fun getReactContext(): ReactContext {
        return this.context as ReactContext
    }
}

react-native: 0.77.x
react-native-navigation: 7.40.3
java: 17
node: 22.5.1
gradle: 8.10.2
buildToolsVersion = "35.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
ndkVersion = "27.2.12479018"
kotlinVersion = "2.0.21"

android\gradle.properties
newArchEnabled=false
hermesEnabled=true

This is how Android was tested, it works on 0.77.0. Please write if you encounter any problems.

@andres3dc
Copy link

Hi!
Any solution for this? I have the same error

@ertugruldogan
Copy link
Author

There is a solution but first we need to switch from 0.77.rc version to normal version. Let's wait a bit.

@peteAhn
Copy link

peteAhn commented Jan 8, 2025

At last, next week, it will be released 0.77.
FYI.
https://github.com/facebook/react-native/releases/tag/v0.77.0-rc.6

@oferRounds
Copy link

Does any one has a patch for this issue?

@oferRounds
Copy link

@ertugruldogan you mentioned you have a solution?

@ertugruldogan
Copy link
Author

ertugruldogan commented Jan 27, 2025

The for Android 27.01.2025 topic updated.

@oferRounds
Copy link

@ertugruldogan not sure i understood your last comment :)

@ertugruldogan
Copy link
Author

Topic updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants