diff --git a/modules/expo-selectable-text/index.ts b/modules/expo-selectable-text/index.ts index 5fe8d2e4bc..01075393ee 100644 --- a/modules/expo-selectable-text/index.ts +++ b/modules/expo-selectable-text/index.ts @@ -1,26 +1,4 @@ -import { NativeModulesProxy, EventEmitter, Subscription } from 'expo-modules-core'; +import ExpoSelectableTextView from './src/ExpoSelectableTextView' +import {ExpoProTextViewProps} from './src/ExpoSelectableText.types' -// Import the native module. On web, it will be resolved to ExpoSelectableText.web.ts -// and on native platforms to ExpoSelectableText.ts -import ExpoSelectableTextModule from './src/ExpoSelectableTextModule'; -import ExpoSelectableTextView from './src/ExpoSelectableTextView'; -import { ChangeEventPayload, ExpoSelectableTextViewProps } from './src/ExpoSelectableText.types'; - -// Get the native constant value. -export const PI = ExpoSelectableTextModule.PI; - -export function hello(): string { - return ExpoSelectableTextModule.hello(); -} - -export async function setValueAsync(value: string) { - return await ExpoSelectableTextModule.setValueAsync(value); -} - -const emitter = new EventEmitter(ExpoSelectableTextModule ?? NativeModulesProxy.ExpoSelectableText); - -export function addChangeListener(listener: (event: ChangeEventPayload) => void): Subscription { - return emitter.addListener('onChange', listener); -} - -export { ExpoSelectableTextView, ExpoSelectableTextViewProps, ChangeEventPayload }; +export {ExpoSelectableTextView as SelectableText, type ExpoProTextViewProps} diff --git a/modules/expo-selectable-text/ios/ExpoProTextModule.swift b/modules/expo-selectable-text/ios/ExpoProTextModule.swift deleted file mode 100644 index 5f1fb95d92..0000000000 --- a/modules/expo-selectable-text/ios/ExpoProTextModule.swift +++ /dev/null @@ -1,26 +0,0 @@ -import ExpoModulesCore - -public class ExpoProTextModule: Module { - public func definition() -> ModuleDefinition { - Name("ExpoProText") - - View(ExpoProTextView.self) { - Events("onTextPress", "onTextLongPress") - - Prop("segments") { (view: ExpoProTextView, prop: String) in - // Convert the JSON to segments - if let data = prop.data(using: .utf8) { - print("yes") - if let segments = try? JSONDecoder().decode(TextSegments.self, from: data) { - print("yes2") - view.segments = segments.segments - } - } - } - - Prop("selectable") { (view: ExpoProTextView, prop: Bool) in - view.textView.isSelectable = prop - } - } - } -} diff --git a/modules/expo-selectable-text/ios/ExpoProText.podspec b/modules/expo-selectable-text/ios/ExpoSelectableText.podspec similarity index 92% rename from modules/expo-selectable-text/ios/ExpoProText.podspec rename to modules/expo-selectable-text/ios/ExpoSelectableText.podspec index 0564384ee1..7590adda60 100644 --- a/modules/expo-selectable-text/ios/ExpoProText.podspec +++ b/modules/expo-selectable-text/ios/ExpoSelectableText.podspec @@ -1,5 +1,5 @@ Pod::Spec.new do |s| - s.name = 'ExpoProText' + s.name = 'ExpoSelectableText' s.version = '1.0.0' s.summary = 'A sample project summary' s.description = 'A sample project description' @@ -16,6 +16,6 @@ Pod::Spec.new do |s| 'DEFINES_MODULE' => 'YES', 'SWIFT_COMPILATION_MODE' => 'wholemodule' } - + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" end diff --git a/modules/expo-selectable-text/ios/ExpoSelectableTextModule.swift b/modules/expo-selectable-text/ios/ExpoSelectableTextModule.swift new file mode 100644 index 0000000000..af92453238 --- /dev/null +++ b/modules/expo-selectable-text/ios/ExpoSelectableTextModule.swift @@ -0,0 +1,32 @@ +import ExpoModulesCore + +public class ExpoSelectableTextModule: Module { + public func definition() -> ModuleDefinition { + Name("ExpoSelectableText") + + View(ExpoSelectableTextView.self) { + Events("onTextPress", "onTextLongPress", "onTextLayout") + + Prop("segments") { (view: ExpoSelectableTextView, prop: String) in + // Convert the JSON to segments + if let data = prop.data(using: .utf8) { + if let segments = try? JSONDecoder().decode(TextSegments.self, from: data) { + view.segments = segments.segments + } + } + } + + Prop("selectable") { (view: ExpoSelectableTextView, prop: Bool) in + view.textView.isSelectable = prop + } + + Prop("rootStyle") { (view: ExpoSelectableTextView, prop: String) in + if let data = prop.data(using: .utf8) { + if let style = try? JSONDecoder().decode(TextStyle.self, from: data) { + view.style = style + } + } + } + } + } +} diff --git a/modules/expo-selectable-text/ios/ExpoProTextUtil.swift b/modules/expo-selectable-text/ios/ExpoSelectableTextUtil.swift similarity index 99% rename from modules/expo-selectable-text/ios/ExpoProTextUtil.swift rename to modules/expo-selectable-text/ios/ExpoSelectableTextUtil.swift index 59936adc1f..ad06434639 100644 --- a/modules/expo-selectable-text/ios/ExpoProTextUtil.swift +++ b/modules/expo-selectable-text/ios/ExpoSelectableTextUtil.swift @@ -1,4 +1,4 @@ -public class ExpoProTextUtil { +public class ExpoSelectableTextUtil { public static func hexToUIColor(hex: String?) -> UIColor { guard let hex else { return UIColor.black diff --git a/modules/expo-selectable-text/ios/ExpoProTextView.swift b/modules/expo-selectable-text/ios/ExpoSelectableTextView.swift similarity index 90% rename from modules/expo-selectable-text/ios/ExpoProTextView.swift rename to modules/expo-selectable-text/ios/ExpoSelectableTextView.swift index 25ae0a74cb..681ad5b53f 100644 --- a/modules/expo-selectable-text/ios/ExpoProTextView.swift +++ b/modules/expo-selectable-text/ios/ExpoSelectableTextView.swift @@ -1,13 +1,18 @@ import Foundation import ExpoModulesCore -class ExpoProTextView: ExpoView { +class ExpoSelectableTextView: ExpoView { let textView: UITextView = UITextView() var segments: Array = [] { didSet { setText() } } + var style: TextStyle? { + didSet { + self.setNeedsLayout() + } + } let onTextPress = EventDispatcher() let onTextLongPress = EventDispatcher() @@ -22,6 +27,8 @@ class ExpoProTextView: ExpoView { textView.isScrollEnabled = false textView.backgroundColor = .clear + clipsToBounds = true + // Add the text view to the root view addSubview(textView) @@ -71,8 +78,8 @@ class ExpoProTextView: ExpoView { segments.forEach { segment in // Set some generic attributes that don't need ranges let attributes: [NSAttributedString.Key:Any] = [ - .font: UIFont.systemFont(ofSize: segment.style?.fontSize ?? 12.0, weight: segment.style?.fontWeight?.toFontWeight() ?? .regular), - .foregroundColor: ExpoProTextUtil.hexToUIColor(hex: segment.style?.color), + .font: UIFont.systemFont(ofSize: segment.style?.fontSize ?? self.style?.fontSize ?? 12.0, weight: segment.style?.fontWeight?.toFontWeight() ?? self.style?.fontWeight?.toFontWeight() ?? .regular), + .foregroundColor: ExpoSelectableTextUtil.hexToUIColor(hex: segment.style?.color), ] // Create the attributed string with the generic attributes @@ -81,7 +88,8 @@ class ExpoProTextView: ExpoView { // Set the paragraph style attributes if necessary if let lineHeight = segment.style?.lineHeight { let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.lineSpacing = lineHeight + paragraphStyle.minimumLineHeight = lineHeight + paragraphStyle.maximumLineHeight = lineHeight string.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, string.length)) } diff --git a/modules/expo-selectable-text/ios/TextStyle.swift b/modules/expo-selectable-text/ios/TextStyle.swift index 4de16d4a75..e3ec6a01fa 100644 --- a/modules/expo-selectable-text/ios/TextStyle.swift +++ b/modules/expo-selectable-text/ios/TextStyle.swift @@ -7,11 +7,21 @@ struct TextStyle: Decodable { var textAlign: String? = "auto" var lineHeight: Double? var textDecorationLine: TextDecorationLine? + var flex: Int? } enum FontWeight: String, Decodable { case bold case normal + case one = "100" + case two = "200" + case three = "300" + case four = "400" + case five = "500" + case six = "600" + case seven = "700" + case eight = "800" + case nine = "900" func toFontWeight() -> UIFont.Weight { switch self { @@ -19,6 +29,24 @@ enum FontWeight: String, Decodable { return .bold case .normal: return .regular + case .one: + return .ultraLight + case .two: + return .ultraLight + case .three: + return .light + case .four: + return .regular + case .five: + return .medium + case .six: + return .semibold + case .seven: + return .semibold + case .eight: + return .bold + case .nine: + return .heavy } } } @@ -27,5 +55,6 @@ enum TextDecorationLine: String, Decodable { case underline case lineThrough = "line-through" case underlineLineThrough = "underline line-through" + case normal } diff --git a/modules/expo-selectable-text/src/ExpoProText.types.ts b/modules/expo-selectable-text/src/ExpoSelectableText.types.ts similarity index 90% rename from modules/expo-selectable-text/src/ExpoProText.types.ts rename to modules/expo-selectable-text/src/ExpoSelectableText.types.ts index 80fa156651..878207a94b 100644 --- a/modules/expo-selectable-text/src/ExpoProText.types.ts +++ b/modules/expo-selectable-text/src/ExpoSelectableText.types.ts @@ -1,5 +1,5 @@ import React from 'react' -import { ColorValue } from 'react-native' +import {ColorValue, TextStyle, ViewStyle} from 'react-native' interface ExpoProTextViewCommonProps { selectable?: boolean @@ -7,7 +7,7 @@ interface ExpoProTextViewCommonProps { export interface ExpoProTextViewProps extends ExpoProTextViewCommonProps { children: React.ReactNode - style?: ExpoProTextStyle + style?: TextStyle onPress?: () => void onLongPress?: () => void } @@ -19,6 +19,8 @@ export interface ExpoProTextNativeViewProps extends ExpoProTextViewCommonProps { onTextLongPress?: (event: ExpoProTextPressEvent) => void onTextLayout?: (event: ExpoProTextLayoutEvent) => void disableLongPress: boolean + style: ViewStyle + rootStyle?: string } interface ExpoProTextStyle { diff --git a/modules/expo-selectable-text/src/ExpoProTextView.web.tsx b/modules/expo-selectable-text/src/ExpoSelectableText.web.tsx similarity index 73% rename from modules/expo-selectable-text/src/ExpoProTextView.web.tsx rename to modules/expo-selectable-text/src/ExpoSelectableText.web.tsx index f3319886ae..698399cc65 100644 --- a/modules/expo-selectable-text/src/ExpoProTextView.web.tsx +++ b/modules/expo-selectable-text/src/ExpoSelectableText.web.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { ExpoProTextViewProps } from './ExpoProText.types' +import {ExpoProTextViewProps} from './ExpoSelectableText.types' export default function ExpoProTextView(props: ExpoProTextViewProps) { return ( diff --git a/modules/expo-selectable-text/src/ExpoProTextModule.ts b/modules/expo-selectable-text/src/ExpoSelectableTextModule.ts similarity index 100% rename from modules/expo-selectable-text/src/ExpoProTextModule.ts rename to modules/expo-selectable-text/src/ExpoSelectableTextModule.ts diff --git a/modules/expo-selectable-text/src/ExpoProTextModule.web.ts b/modules/expo-selectable-text/src/ExpoSelectableTextModule.web.ts similarity index 100% rename from modules/expo-selectable-text/src/ExpoProTextModule.web.ts rename to modules/expo-selectable-text/src/ExpoSelectableTextModule.web.ts diff --git a/modules/expo-selectable-text/src/ExpoProTextView.tsx b/modules/expo-selectable-text/src/ExpoSelectableTextView.tsx similarity index 73% rename from modules/expo-selectable-text/src/ExpoProTextView.tsx rename to modules/expo-selectable-text/src/ExpoSelectableTextView.tsx index e800ffafd4..d5a7a3c414 100644 --- a/modules/expo-selectable-text/src/ExpoProTextView.tsx +++ b/modules/expo-selectable-text/src/ExpoSelectableTextView.tsx @@ -1,4 +1,4 @@ -import { requireNativeViewManager } from 'expo-modules-core' +import {requireNativeViewManager} from 'expo-modules-core' import * as React from 'react' import { @@ -7,37 +7,37 @@ import { ExpoProTextPressEvent, ExpoProTextSegment, ExpoProTextViewProps, -} from './ExpoProText.types' -import { Text, View } from 'react-native' +} from './ExpoSelectableText.types' +import {StyleSheet, Text, View} from 'react-native' const NativeView: React.ComponentType = - requireNativeViewManager('ExpoProText') + requireNativeViewManager('ExpoSelectableText') -export default function ExpoProTextView({ +export default function ExpoSelectableTextView({ style, children, selectable = false, onPress, onLongPress, }: ExpoProTextViewProps) { - const [dims, setDims] = React.useState({ height: 0 }) + const [dims, setDims] = React.useState({height: 0}) const segmentPressCallbacks = React.useRef< - Array<{ index: number; onPress: () => void }> + Array<{index: number; onPress: () => void}> >([]) const segmentLongPressCallbacks = React.useRef< - Array<{ index: number, onLongPress: () => void, }> + Array<{index: number; onLongPress: () => void}> >([]) const onTextLayout = React.useCallback((e: ExpoProTextLayoutEvent) => { + console.log('layout') setDims({ height: e.nativeEvent.height, }) }, []) const onTextPress = React.useCallback((e: ExpoProTextPressEvent) => { - console.log(e.nativeEvent) const onPressSegment = segmentPressCallbacks.current.find( - (s) => s.index === e.nativeEvent.index, + s => s.index === e.nativeEvent.index, ) onPressSegment?.onPress() }, []) @@ -58,7 +58,7 @@ export default function ExpoProTextView({ React.isValidElement(child) && (child as React.ReactElement).type === Text ) { - const { onPress, onLongPress, children, style } = child.props + const {onPress, onLongPress, children, style} = child.props segments.push({ index, @@ -88,21 +88,25 @@ export default function ExpoProTextView({ }, [children]) const segmentsJson = React.useMemo(() => { - const json = JSON.stringify({ segments: textSegments }) - console.log(json) + const json = JSON.stringify({segments: textSegments}) return json }, [textSegments]) + const rootStyle = React.useMemo(() => { + return style ? JSON.stringify(style) : undefined + }, [style]) + return ( - + ) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index bb3c7e2ccf..27bdab141e 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -369,6 +369,7 @@ let PostThreadItemLoaded = ({ richText={richText} lineHeight={1.3} style={s.flex1} + selectable /> ) : undefined} diff --git a/src/view/com/util/text/RichText.tsx b/src/view/com/util/text/RichText.tsx index 99062e8485..6a784a2860 100644 --- a/src/view/com/util/text/RichText.tsx +++ b/src/view/com/util/text/RichText.tsx @@ -17,6 +17,7 @@ export function RichText({ lineHeight = 1.2, style, numberOfLines, + selectable, }: { testID?: string type?: TypographyVariant @@ -24,6 +25,7 @@ export function RichText({ lineHeight?: number style?: StyleProp numberOfLines?: number + selectable?: boolean }) { const theme = useTheme() const pal = usePalette('default') @@ -54,7 +56,8 @@ export function RichText({ style={[style, pal.text, lineHeightStyle]} numberOfLines={numberOfLines} // @ts-ignore web only -prf - dataSet={WORD_WRAP}> + dataSet={WORD_WRAP} + selectable={selectable}> {text} ) diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx index ea97d59fe0..8b00d2e593 100644 --- a/src/view/com/util/text/Text.tsx +++ b/src/view/com/util/text/Text.tsx @@ -1,13 +1,17 @@ import React from 'react' -import {Text as RNText, TextProps} from 'react-native' +import {StyleSheet, Text as RNText, TextProps} from 'react-native' import {s, lh} from 'lib/styles' import {useTheme, TypographyVariant} from 'lib/ThemeContext' +import {SelectableText} from '../../../../../modules/expo-selectable-text' +import {isIOS} from 'platform/detection' +import {fontSize} from '#/alf/tokens' export type CustomTextProps = TextProps & { type?: TypographyVariant lineHeight?: number title?: string dataSet?: Record + selectable?: boolean } export function Text({ @@ -17,11 +21,30 @@ export function Text({ style, title, dataSet, + selectable, ...props }: React.PropsWithChildren) { const theme = useTheme() const typography = theme.typography[type] const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined + + // {"color":"#000000","fontSize":20,"letterSpacing":0.2,"fontWeight":"400","flex":1,"lineHeight":26} + + if (selectable && isIOS) { + return ( + + {children} + + ) + } + return (