From b4e59f889b25e5494e30dba4869dfe2b00df26f5 Mon Sep 17 00:00:00 2001 From: Dominic Go Date: Sat, 17 Aug 2024 02:24:14 +0800 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20Impl:=20`RNIBlurView`=20Sc?= =?UTF-8?q?affolding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/RNIBlurView/RNIBlurView.h | 27 ++ ios/RNIBlurView/RNIBlurView.mm | 104 ++++++++ .../RNIBlurViewComponentDescriptor.h | 28 +++ ios/RNIBlurView/RNIBlurViewDelegate.swift | 238 ++++++++++++++++++ ios/RNIBlurView/RNIBlurViewManager.mm | 33 +++ ios/RNIBlurView/RNIBlurViewShadowNode.h | 45 ++++ .../RNIBlurView/RNIBlurNativeView.ts | 14 ++ .../RNIBlurView/RNIBlurView.tsx | 41 +++ .../RNIBlurView/RNIBlurViewNativeComponent.ts | 14 ++ .../RNIBlurView/RNIBlurViewTypes.ts | 33 +++ src/native_components/RNIBlurView/index.ts | 2 + 11 files changed, 579 insertions(+) create mode 100644 ios/RNIBlurView/RNIBlurView.h create mode 100644 ios/RNIBlurView/RNIBlurView.mm create mode 100644 ios/RNIBlurView/RNIBlurViewComponentDescriptor.h create mode 100644 ios/RNIBlurView/RNIBlurViewDelegate.swift create mode 100644 ios/RNIBlurView/RNIBlurViewManager.mm create mode 100644 ios/RNIBlurView/RNIBlurViewShadowNode.h create mode 100644 src/native_components/RNIBlurView/RNIBlurNativeView.ts create mode 100644 src/native_components/RNIBlurView/RNIBlurView.tsx create mode 100644 src/native_components/RNIBlurView/RNIBlurViewNativeComponent.ts create mode 100644 src/native_components/RNIBlurView/RNIBlurViewTypes.ts create mode 100644 src/native_components/RNIBlurView/index.ts diff --git a/ios/RNIBlurView/RNIBlurView.h b/ios/RNIBlurView/RNIBlurView.h new file mode 100644 index 0000000..ade03a9 --- /dev/null +++ b/ios/RNIBlurView/RNIBlurView.h @@ -0,0 +1,27 @@ +// +// RNIBlurView.h +// react-native-ios-visual-effect-view +// +// Created by Dominic Go on 6/6/24. +// + +#import + +#if RCT_NEW_ARCH_ENABLED +#import +#else +#import +#endif + +@protocol RNIContentViewParentDelegate; +@protocol RNIContentViewDelegate; + +@class RCTBridge; + +#if RCT_NEW_ARCH_ENABLED +namespace react = facebook::react; +#endif + +@interface RNIBlurView : RNIBaseView + +@end diff --git a/ios/RNIBlurView/RNIBlurView.mm b/ios/RNIBlurView/RNIBlurView.mm new file mode 100644 index 0000000..c4f1b42 --- /dev/null +++ b/ios/RNIBlurView/RNIBlurView.mm @@ -0,0 +1,104 @@ +// +// RNIBlurView.mm +// react-native-ios-visual-effect-view +// +// Created by Dominic Go on 6/6/24. +// + +#import "RNIBlurView.h" + +#import "react-native-ios-visual-effect-view/Swift.h" +#import + +#import + + +#import +#import + +#if RCT_NEW_ARCH_ENABLED +#include "RNIBlurViewComponentDescriptor.h" + +#include +#include + +#import +#import +#import +#import + +#include +#include +#include +#include + +#import +#import +#else +#import +#import +#endif + +#ifdef RCT_NEW_ARCH_ENABLED +using namespace facebook::react; +#endif + + +@interface RNIBlurView () < + RNIContentViewParentDelegate, +#ifdef RCT_NEW_ARCH_ENABLED + RCTRNIBlurViewViewProtocol +#else + RCTInvalidating +#endif +> { + // TBA +} +@end + +@implementation RNIBlurView { +} + +// MARK: - Init +// ------------ + +- (void)initCommon { + [super initCommon]; +} + +// MARK: - RNIBaseView +// ------------------- + ++ (Class)viewDelegateClass +{ + return [RNIBlurViewDelegate class]; +} + +// MARK: - Fabric +// -------------- + +#if RCT_NEW_ARCH_ENABLED ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +Class RNIBlurViewCls(void) +{ + return RNIBlurView.class; +} +#else + +// MARK: - Paper +// ------------- + +- (void)invalidate +{ + // to be impl. +} + +#endif +@end + + + diff --git a/ios/RNIBlurView/RNIBlurViewComponentDescriptor.h b/ios/RNIBlurView/RNIBlurViewComponentDescriptor.h new file mode 100644 index 0000000..63faec0 --- /dev/null +++ b/ios/RNIBlurView/RNIBlurViewComponentDescriptor.h @@ -0,0 +1,28 @@ +// +// RNIBlurViewComponentDescriptor.h +// react-native-ios-visual-effect-view +// +// Created by Dominic Go on 6/6/24. +// + +#if __cplusplus +#pragma once + +#include "RNIBlurViewShadowNode.h" +#include "RNIBaseViewComponentDescriptor.h" + +#include +#include + + +namespace facebook::react { + +class RNIBlurViewComponentDescriptor final + : public RNIBaseViewComponentDescriptor { + +public: + using RNIBaseViewComponentDescriptor::RNIBaseViewComponentDescriptor; +}; + +} // namespace facebook::react +#endif diff --git a/ios/RNIBlurView/RNIBlurViewDelegate.swift b/ios/RNIBlurView/RNIBlurViewDelegate.swift new file mode 100644 index 0000000..56afab7 --- /dev/null +++ b/ios/RNIBlurView/RNIBlurViewDelegate.swift @@ -0,0 +1,238 @@ +// +// RNIBlurViewDelegate.swift +// react-native-ios-visual-effect-view +// +// Created by Dominic Go on 6/6/24. +// + +import UIKit +import react_native_ios_utilities +import DGSwiftUtilities +import VisualEffectBlurView + +@objc(RNIBlurViewDelegate) +public final class RNIBlurViewDelegate: UIView, RNIContentView { + + public static var propKeyPathMap: Dictionary> { + return [:]; + }; + + public enum Events: String, CaseIterable { + case onDidSetViewID; + } + + // MARK: Properties + // ---------------- + + var _didSendEvents = false; + + + var visualEffectView: VisualEffectView?; + + // MARK: - Properties - RNIContentViewDelegate + // ------------------------------------------- + + public weak var parentReactView: RNIContentViewParentDelegate?; + + // MARK: Properties - Props + // ------------------------ + + public var reactProps: NSDictionary = [:]; + + // TBA + + // MARK: Init + // ---------- + + public override init(frame: CGRect) { + super.init(frame: frame); + }; + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented"); + } + + // MARK: Functions + // --------------- + + public override func didMoveToWindow() { + guard self.window != nil, + let parentReactView = self.parentReactView + else { return }; + }; + + func _setupContent(){ + + let imageConfig: ImageConfigGradient = ImageConfigGradient( + colors: [.black, .clear], + startPointPreset: .top, + endPointPreset: .bottom, + size: UIScreen.main.bounds.size + ); + + let gradientImage = try! imageConfig.makeImage(); + + let visualEffectView = try! VisualEffectView(rawFilterTypes: []); + self.visualEffectView = visualEffectView; + + self.addSubview(visualEffectView); + visualEffectView.translatesAutoresizingMaskIntoConstraints = false; + visualEffectView.shouldOnlyShowBgLayer = true; + + NSLayoutConstraint.activate([ + visualEffectView.leadingAnchor.constraint( + equalTo: self.leadingAnchor + ), + visualEffectView.trailingAnchor.constraint( + equalTo: self.trailingAnchor + ), + visualEffectView.topAnchor.constraint( + equalTo: self.topAnchor + ), + visualEffectView.bottomAnchor.constraint( + equalTo: self.bottomAnchor + ), + ]); + + try! visualEffectView.setFiltersViaEffectDesc( + withFilterTypes: [ + .variadicBlur( + radius: 0, + maskImage: gradientImage.cgImage, + shouldNormalizeEdges: true + ), + .colorBlackAndWhite(amount: 0.1), + ], + shouldImmediatelyApplyFilter: true + ); + + let tapGesture = UITapGestureRecognizer(); + tapGesture.isEnabled = true; + + tapGesture.addAction { _ in + try! visualEffectView.updateCurrentFiltersViaEffectDesc( + withFilterTypes: [ + .variadicBlur( + radius: 16, + maskImage: gradientImage.cgImage, + shouldNormalizeEdges: true + ), + ] + ); + + UIView.animate(withDuration: 1) { + try! visualEffectView.applyRequestedFilterEffects(); + + } completion: { _ in + try! visualEffectView.updateCurrentFiltersViaEffectDesc( + withFilterTypes: [ + .colorBlackAndWhite(amount: 0.75), + ] + ); + UIView.animate(withDuration: 1) { + try! visualEffectView.applyRequestedFilterEffects(); + }; + }; + }; + + visualEffectView.addGestureRecognizer(tapGesture); + }; +}; + +extension RNIBlurViewDelegate: RNIContentViewDelegate { + + public typealias KeyPathRoot = RNIBlurViewDelegate; + + // MARK: Paper + Fabric + // -------------------- + + public func notifyOnInit(sender: RNIContentViewParentDelegate) { + self._setupContent(); + }; + + public func notifyOnMountChildComponentView( + sender: RNIContentViewParentDelegate, + childComponentView: UIView, + index: NSInteger, + superBlock: () -> Void + ) { + #if !RCT_NEW_ARCH_ENABLED + superBlock(); + #endif + + // Note: Window might not be available yet + self.addSubview(childComponentView); + }; + + public func notifyOnUnmountChildComponentView( + sender: RNIContentViewParentDelegate, + childComponentView: UIView, + index: NSInteger, + superBlock: () -> Void + ) { + #if !RCT_NEW_ARCH_ENABLED + superBlock(); + #endif + + } + + public func notifyDidSetProps(sender: RNIContentViewParentDelegate) { + // no-op + }; + + public func notifyOnUpdateLayoutMetrics( + sender: RNIContentViewParentDelegate, + oldLayoutMetrics: RNILayoutMetrics, + newLayoutMetrics: RNILayoutMetrics + ) { + // no-op + }; + + public func notifyOnViewCommandRequest( + sender: RNIContentViewParentDelegate, + forCommandName commandName: String, + withCommandArguments commandArguments: NSDictionary, + resolve resolveBlock: (NSDictionary) -> Void, + reject rejectBlock: (String) -> Void + ) { + // no-op + }; + + // MARK: Fabric Only + // ----------------- + + #if RCT_NEW_ARCH_ENABLED + public func notifyOnUpdateProps( + sender: RNIContentViewParentDelegate, + oldProps: NSDictionary, + newProps: NSDictionary + ) { + // no-op + }; + + public func notifyOnUpdateState( + sender: RNIContentViewParentDelegate, + oldState: NSDictionary?, + newState: NSDictionary + ) { + // no-op + }; + + public func notifyOnFinalizeUpdates( + sender: RNIContentViewParentDelegate, + updateMaskRaw: Int, + updateMask: RNIComponentViewUpdateMask + ) { + // no-op + }; + + public func notifyOnPrepareForReuse(sender: RNIContentViewParentDelegate) { + // no-op + }; + #else + + // MARK: - Paper Only + // ------------------ + + #endif +}; diff --git a/ios/RNIBlurView/RNIBlurViewManager.mm b/ios/RNIBlurView/RNIBlurViewManager.mm new file mode 100644 index 0000000..1d2ceb0 --- /dev/null +++ b/ios/RNIBlurView/RNIBlurViewManager.mm @@ -0,0 +1,33 @@ +// +// RNIBlurViewManager.m +// react-native-ios-visual-effect-view +// +// Created by Dominic Go on 6/6/24. +// + +#import "RNIBlurView.h" +#import + +#import "react-native-ios-utilities/RNIBaseViewUtils.h" + +#import "RCTBridge.h" +#import +#import + + +@interface RNIBlurViewManager : RCTViewManager +@end + +@implementation RNIBlurViewManager + +RCT_EXPORT_MODULE(RNIBlurView) + +#ifndef RCT_NEW_ARCH_ENABLED +- (UIView *)view +{ + return [[RNIBlurView new] initWithBridge:self.bridge]; +} +#endif + +@end + diff --git a/ios/RNIBlurView/RNIBlurViewShadowNode.h b/ios/RNIBlurView/RNIBlurViewShadowNode.h new file mode 100644 index 0000000..7dbf1d8 --- /dev/null +++ b/ios/RNIBlurView/RNIBlurViewShadowNode.h @@ -0,0 +1,45 @@ +// +// RNIBlurViewShadowNode.h +// react-native-ios-visual-effect-view +// +// Created by Dominic Go on 6/6/24. +// + +#if __cplusplus +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include + + +namespace facebook::react { + +JSI_EXPORT extern const char RNIBlurViewComponentName[] = "RNIBlurView"; + +class JSI_EXPORT RNIBlurViewShadowNode final : public RNIBaseViewShadowNode< + RNIBlurViewComponentName, + RNIBaseViewProps, + RNIBaseViewEventEmitter +> { + +public: + using RNIBaseViewShadowNode::RNIBaseViewShadowNode; + + static RNIBaseViewState initialStateData( + const Props::Shared&r , // props + const ShadowNodeFamily::Shared&, // family + const ComponentDescriptor& // componentDescriptor + ) { + return {}; + } +}; + +} // facebook::react +#endif diff --git a/src/native_components/RNIBlurView/RNIBlurNativeView.ts b/src/native_components/RNIBlurView/RNIBlurNativeView.ts new file mode 100644 index 0000000..b80ceb9 --- /dev/null +++ b/src/native_components/RNIBlurView/RNIBlurNativeView.ts @@ -0,0 +1,14 @@ +import type { HostComponent, ViewProps } from 'react-native'; + +import { default as RNIBlurViewNativeComponent } from './RNIBlurViewNativeComponent'; +import type { SharedViewEvents } from 'react-native-ios-utilities'; + +export interface RNIBlurNativeViewBaseProps extends ViewProps { +}; + +export type RNIBlurNativeViewProps = + SharedViewEvents + & RNIBlurNativeViewBaseProps; + +export const RNIBlurNativeView = + RNIBlurViewNativeComponent as unknown as HostComponent; diff --git a/src/native_components/RNIBlurView/RNIBlurView.tsx b/src/native_components/RNIBlurView/RNIBlurView.tsx new file mode 100644 index 0000000..4989ed9 --- /dev/null +++ b/src/native_components/RNIBlurView/RNIBlurView.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; + +import { RNIBlurNativeView } from './RNIBlurNativeView'; + +import type { + RNIBlurViewProps, + RNIBlurViewRef, + StateReactTag, + StateViewID +} from './RNIBlurViewTypes'; + +export const RNIBlurView = React.forwardRef< + RNIBlurViewRef, + RNIBlurViewProps +>((props, ref) => { + + const [viewID, setViewID] = React.useState(); + const [reactTag, setReactTag] = React.useState(); + + React.useImperativeHandle(ref, () => ({ + getReactTag: () => { + return reactTag; + }, + getViewID: () => { + return viewID; + }, + })); + + return ( + { + setViewID(event.nativeEvent.viewID); + setReactTag(event.nativeEvent.reactTag); + props.onDidSetViewID?.(event); + }} + > + {props.children} + + ); +}); \ No newline at end of file diff --git a/src/native_components/RNIBlurView/RNIBlurViewNativeComponent.ts b/src/native_components/RNIBlurView/RNIBlurViewNativeComponent.ts new file mode 100644 index 0000000..015904f --- /dev/null +++ b/src/native_components/RNIBlurView/RNIBlurViewNativeComponent.ts @@ -0,0 +1,14 @@ +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import type { BubblingEventHandler } from 'react-native/Libraries/Types/CodegenTypes'; +import type { HostComponent, ViewProps } from 'react-native'; + + +interface NativeProps extends ViewProps { + onDidSetViewID: BubblingEventHandler<{}>; +}; + +// stubs +export default codegenNativeComponent('RNIBlurView', { + excludedPlatforms: ['android'], + interfaceOnly: true, +}) as HostComponent; \ No newline at end of file diff --git a/src/native_components/RNIBlurView/RNIBlurViewTypes.ts b/src/native_components/RNIBlurView/RNIBlurViewTypes.ts new file mode 100644 index 0000000..84273a1 --- /dev/null +++ b/src/native_components/RNIBlurView/RNIBlurViewTypes.ts @@ -0,0 +1,33 @@ +import type { PropsWithChildren } from "react"; +import type { ViewProps } from "react-native"; + +import type { OnDidSetViewIDEventPayload } from "react-native-ios-utilities"; +import type { RNIBlurNativeViewProps } from "./RNIBlurNativeView"; + +export type StateViewID = OnDidSetViewIDEventPayload['viewID'] | undefined; +export type StateReactTag = OnDidSetViewIDEventPayload['reactTag'] | undefined; + +export type RNIBlurViewRef = { + getViewID: () => StateViewID; + getReactTag: () => StateReactTag; +}; + +export type RNIBlurViewInheritedOptionalProps = Partial>; + +export type RNIBlurViewInheritedRequiredProps = {}; + +export type RNIBlurViewInheritedProps = + RNIBlurViewInheritedOptionalProps + & RNIBlurViewInheritedRequiredProps; + +export type RNIBlurViewBaseProps = { + // TBA +}; + +export type RNIBlurViewProps = PropsWithChildren< + RNIBlurViewInheritedProps + & RNIBlurViewBaseProps + & ViewProps +>; \ No newline at end of file diff --git a/src/native_components/RNIBlurView/index.ts b/src/native_components/RNIBlurView/index.ts new file mode 100644 index 0000000..813b375 --- /dev/null +++ b/src/native_components/RNIBlurView/index.ts @@ -0,0 +1,2 @@ +export * from './RNIBlurView'; +export * from './RNIBlurViewTypes'; \ No newline at end of file