Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class PerfTextViewManager : SimpleViewManager<PerfTextView>() {
if (sizeSp > 0) v.textSize = sizeSp.toFloat()
}


@ReactProp(name = "lineHeight")
fun setLineHeight(v: PerfTextView, height: Double) {
val targetPx = PixelUtil.toPixelFromDIP(height.toFloat())
Expand All @@ -65,8 +64,6 @@ class PerfTextViewManager : SimpleViewManager<PerfTextView>() {
};
}



@ReactProp(name = "fontFamily")
fun setFontFamily(v: PerfTextView, family: String?) {
v.setFontFamilyCompat(family)
Expand Down Expand Up @@ -138,11 +135,10 @@ class PerfTextViewManager : SimpleViewManager<PerfTextView>() {

view.measure(widthSpec, heightSpec)

val measuredWidthDp = ceil(PixelUtil.toDIPFromPixel(view.measuredWidth.toFloat()).toDouble()).toInt()
val measuredHeightDp = ceil(PixelUtil.toDIPFromPixel(view.measuredHeight.toFloat()).toDouble()).toInt()
val measuredWidthDp = ceil(PixelUtil.toDIPFromPixel(view.measuredWidth.toFloat()))
val measuredHeightDp = ceil(PixelUtil.toDIPFromPixel(view.measuredHeight.toFloat()))

return YogaMeasureOutput.make(measuredWidthDp * 1.5f, measuredHeightDp * 1.0f)

}
}

3 changes: 3 additions & 0 deletions packages/react-native-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@khanacademy/simple-markdown": "^2.1.0",
"@stream-io/state-store": "^1.1.6",
"linkifyjs": "^4.3.2",
"lodash": "4.17.21",
"react-syntax-highlighter": "15.5.0",
Expand All @@ -68,10 +69,12 @@
"@types/react": "19.2.2",
"@types/react-syntax-highlighter": "^15.5.13",
"concurrently": "catalog:",
"expo-image-picker": "^17.0.8",
"react": "19.2.0",
"react-native": "^0.82.1",
"react-native-builder-bob": "0.40.14",
"react-native-gesture-handler": "^2.29.0",
"react-native-image-picker": "^8.2.1",
"react-native-reanimated": "^4.1.3",
"rimraf": "^6.0.1",
"typescript": "catalog:",
Expand Down
191 changes: 191 additions & 0 deletions packages/react-native-sdk/src/components/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React, {
type PropsWithChildren,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
Dimensions,
Modal,
Platform,
Pressable,
StyleSheet,
View,
} from 'react-native';
import {
Gesture,
GestureDetector,
GestureHandlerRootView,
type GestureType,
} from 'react-native-gesture-handler';
import Animated, {
runOnJS,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { useBottomSheetState } from './hooks/useBottomSheetState';
import { closeSheet as closeSheetInternal } from '../store/bottom-sheet-state-store';

type Props = PropsWithChildren;

const { height: SCREEN_H } = Dimensions.get('window');
const SPRING = { duration: 200 };
const MAX_BOTTOM_SHEET_HEIGHT = Math.round(SCREEN_H * 0.4);
const BACKDROP_OPACITY = 0.4;

export function BottomSheet({ children }: Props) {
const { open, height } = useBottomSheetState();
const [bottomSheetModalOpen, setBottomSheetModalOpen] = useState(open);
const maxTranslateY = SCREEN_H;
const translateY = useSharedValue(maxTranslateY);

const bottomSheetHeight = useMemo(
() => Math.min(height, MAX_BOTTOM_SHEET_HEIGHT),
[height],
);
const bottomSheetY = SCREEN_H - bottomSheetHeight;

useEffect(() => {
if (open) {
setBottomSheetModalOpen(true);
} else {
translateY.value = withTiming(maxTranslateY, SPRING, (finished) => {
if (finished) {
runOnJS(setBottomSheetModalOpen)(false);
}
});
}
}, [setBottomSheetModalOpen, maxTranslateY, open, translateY]);

useEffect(() => {
if (bottomSheetModalOpen && open) {
translateY.value = withTiming(bottomSheetY, SPRING);
}
}, [open, bottomSheetModalOpen, bottomSheetY, translateY]);

const dragStartY = useRef(0);
const panRef = useRef<GestureType | undefined>(undefined);
const scrollRef = useRef<GestureType | undefined>(undefined);

const nativeScroll = useMemo(() => Gesture.Native().withRef(scrollRef), []);

const pan = Gesture.Pan()
.withRef(panRef)
.requireExternalGestureToFail(scrollRef)
.onBegin(() => {
dragStartY.current = 0;
})
.onUpdate((e) => {
if (dragStartY.current === 0) dragStartY.current = translateY.value;
const next = dragStartY.current + e.translationY;
const minY = bottomSheetY;
const maxY = maxTranslateY;
const overTop = next < minY;
const overBottom = next > maxY;
translateY.value = overTop
? minY - (minY - next) * 0.2
: overBottom
? maxY + (next - maxY) * 0.2
: next;
})
.onEnd((e) => {
// very basic momentum bias
const projected = e.translationY + e.velocityY * 0.15;
const nearest =
projected >= bottomSheetHeight * 0.5 ? maxTranslateY : bottomSheetY;
translateY.value = withTiming(nearest, SPRING, (finished) => {
if (finished && nearest === maxTranslateY)
runOnJS(closeSheetInternal)();
});
});

const backdropStyle = useAnimatedStyle(() => {
const minY = bottomSheetY;
const t =
1 -
Math.min(
1,
Math.max(0, (translateY.value - minY) / (maxTranslateY - minY)),
);
return {
opacity: t * BACKDROP_OPACITY,
pointerEvents: t > 0.02 ? 'auto' : ('none' as any),
};
});

const sheetStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
}));

const sheetContentStyle = useMemo(
() => ({ height: bottomSheetHeight }),
[bottomSheetHeight],
);

return (
<Modal visible={bottomSheetModalOpen} transparent={true}>
<GestureHandlerRootView style={styles.gestureHandlerRootView}>
<View style={StyleSheet.absoluteFill} pointerEvents={'box-none'}>
<Animated.View
style={[StyleSheet.absoluteFill, styles.backdrop, backdropStyle]}
>
<Pressable
style={StyleSheet.absoluteFill}
onPress={closeSheetInternal}
/>
</Animated.View>

<GestureDetector gesture={pan}>
<Animated.View style={[styles.sheet, sheetStyle]}>
<View style={styles.handleContainer} pointerEvents={'none'}>
<View style={styles.handle} />
</View>

<View style={[styles.content, sheetContentStyle]}>
<View style={styles.contentContainer}>
{open ? (
<GestureDetector gesture={nativeScroll}>
{children}
</GestureDetector>
) : null}
</View>
</View>
</Animated.View>
</GestureDetector>
</View>
</GestureHandlerRootView>
</Modal>
);
}

const styles = StyleSheet.create({
gestureHandlerRootView: { flex: 1 },
backdrop: { backgroundColor: '#000' },
sheet: {
position: 'absolute',
overflow: 'hidden',
left: 0,
right: 0,
top: 0,
height: SCREEN_H,
transform: [{ translateY: SCREEN_H }],
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
backgroundColor: 'white',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOpacity: 0.2,
shadowOffset: { width: 0, height: -4 },
shadowRadius: 12,
},
android: { elevation: 18 },
}),
},
handleContainer: { alignItems: 'center', paddingVertical: 8 },
handle: { width: 40, height: 4, borderRadius: 2, backgroundColor: '#444' },
content: { flexGrow: 0 },
contentContainer: { flex: 1, minHeight: 0 },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
type BottomSheetState,
closeSheet,
openSheet,
store,
} from '../../store/bottom-sheet-state-store';
import { useStateStore } from '@stream-io/state-store/react-bindings';
import { useStableCallback } from '../../internal/hooks/useStableCallback';

const selector = ({ open, height }: BottomSheetState) => ({
open,
height,
});

export const useBottomSheetState = () => {
const data = useStateStore(store, selector);

const openSheetInternal = useStableCallback(() => {
openSheet();
});

const closeSheetInternal = useStableCallback(() => {
closeSheet();
});

return {
...data,
openSheet: openSheetInternal,
closeSheet: closeSheetInternal,
};
};
6 changes: 5 additions & 1 deletion packages/react-native-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
export * from './markdown';
export * from './syntax-highlighting';
export * from './charts';
export * from './message-composer';
export * from './MarkdownRichText';

// components
export * from './components';
export * from './MarkdownRichText';

// services
export * from './services';
12 changes: 12 additions & 0 deletions packages/react-native-sdk/src/internal/icons/Camera.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { type IconProps, RootPath, RootSvg } from './utils/base';

export const Camera = (props: IconProps) => (
<RootSvg {...props}>
<RootPath
d="M15 3H9L7 5H3a1 1 0 00-1 1v14a1 1 0 001 1h18a1 1 0 001-1V6a1 1 0 00-1-1h-4l-2-2zM7.828 7l2-2h4.344l2 2H20v12H4V7h3.828zM12 18a5.5 5.5 0 110-11 5.5 5.5 0 010 11zm3.5-5.5a3.5 3.5 0 11-7 0 3.5 3.5 0 017 0z"
{...props}
/>
</RootSvg>
);
12 changes: 12 additions & 0 deletions packages/react-native-sdk/src/internal/icons/Close.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { type IconProps, RootPath, RootSvg } from './utils/base';

export const Close = (props: IconProps) => (
<RootSvg {...props}>
<RootPath
d='M7.05 7.05a1 1 0 000 1.414L10.586 12 7.05 15.536a1 1 0 101.414 1.414L12 13.414l3.536 3.536a1 1 0 001.414-1.414L13.414 12l3.536-3.536a1 1 0 00-1.414-1.414L12 10.586 8.464 7.05a1 1 0 00-1.414 0z'
{...props}
/>
</RootSvg>
);
12 changes: 12 additions & 0 deletions packages/react-native-sdk/src/internal/icons/Folder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { type IconProps, RootPath, RootSvg } from './utils/base';

export const Folder = (props: IconProps) => (
<RootSvg {...props}>
<RootPath
d="M21 5h-8.586l-2-2H3a1 1 0 00-1 1v16a1 1 0 001 1h18a1 1 0 001-1V6a1 1 0 00-1-1zM4 19V7h16v12H4z"
{...props}
/>
</RootSvg>
);
20 changes: 20 additions & 0 deletions packages/react-native-sdk/src/internal/icons/Mic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

import { G, Path, Svg } from 'react-native-svg';

import type { IconProps } from './utils/base';

type Props = IconProps & {
size: number;
};

export const Mic = ({ size, ...rest }: Props) => (
<Svg height={size} viewBox={`0 0 ${size} ${size}`} width={size} {...rest}>
<G id="24 / mic_iOS">
<Path
d="M11.5234 13.9922C11.5234 16.8047 13.375 18.7734 16 18.7734C18.6367 18.7734 20.4766 16.8047 20.4766 13.9922V6.69141C20.4766 3.86719 18.6367 1.89844 16 1.89844C13.375 1.89844 11.5234 3.86719 11.5234 6.69141V13.9922ZM13.7383 14.0273V6.64453C13.7383 5.08594 14.6172 4.01953 16 4.01953C17.3828 4.01953 18.2617 5.08594 18.2617 6.64453V14.0273C18.2617 15.5977 17.3828 16.6523 16 16.6523C14.6172 16.6523 13.7383 15.5977 13.7383 14.0273ZM7.10547 14.5078C7.10547 19.3242 10.3047 22.6289 14.9336 23.0742V25.4766H10.6211C10.0234 25.4766 9.51953 25.9688 9.51953 26.5781C9.51953 27.1758 10.0234 27.668 10.6211 27.668H21.3789C21.9883 27.668 22.4922 27.1758 22.4922 26.5781C22.4922 25.9688 21.9883 25.4766 21.3789 25.4766H17.0781V23.0742C21.6953 22.6289 24.9062 19.3242 24.9062 14.5078V12.1875C24.9062 11.5781 24.4141 11.0977 23.8047 11.0977C23.1953 11.0977 22.6914 11.5781 22.6914 12.1875V14.4258C22.6914 18.4102 19.9961 21.0469 16 21.0469C12.0039 21.0469 9.30859 18.4102 9.30859 14.4258V12.1875C9.30859 11.5781 8.81641 11.0977 8.20703 11.0977C7.59766 11.0977 7.10547 11.5781 7.10547 12.1875V14.5078Z"
{...rest}
/>
</G>
</Svg>
);
16 changes: 16 additions & 0 deletions packages/react-native-sdk/src/internal/icons/Picture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

import { type IconProps, RootPath, RootSvg } from './utils/base';

export const Picture = (props: IconProps) => (
<RootSvg {...props}>
<RootPath
d="M2 8a3 3 0 013-3h14a3 3 0 013 3v8a3 3 0 01-3 3H5a3 3 0 01-3-3V8zm3-1a1 1 0 00-1 1v8a1 1 0 001 1h14a1 1 0 001-1V8a1 1 0 00-1-1H5z"
{...props}
/>
<RootPath
d="M15.99 9a1 1 0 01.778.36l5 6a1 1 0 11-1.536 1.28l-4.216-5.059-3.235 4.044a1 1 0 01-1.381.175l-3.306-2.48-3.387 3.387a1 1 0 01-1.414-1.414l4-4A1 1 0 018.6 11.2l3.225 2.418 3.394-4.243A1 1 0 0115.99 9z"
{...props}
/>
</RootSvg>
);
27 changes: 27 additions & 0 deletions packages/react-native-sdk/src/internal/icons/SendUp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import Svg, { Circle, Path } from 'react-native-svg';

import type { IconProps } from './utils/base';

type Props = IconProps & {
size: number;
};

export const SendUp = ({ size, ...rest }: Props) => (
<Svg
height={size}
viewBox={`0 0 ${size} ${size}`}
width={size}
{...rest}
testID="send-up"
>
{/*<Circle cx={size / 2} cy={size / 2} r={size / 2} {...rest} />*/}
<Path
clipRule="evenodd"
d="M14.6673 16V22.6667H17.334V16H22.6673L16.0007 9.33337L9.33398 16H14.6673Z"
fill={'white'}
fillRule="evenodd"
/>
</Svg>
);
Loading