authorgravatar for adityaradhika13@gmail.comAditya Vardhan Sharma <adityaradhika13@gmail.com> 2026-07-30 23:20:33+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-31 00:20:33+02:00
logcb11a71d632e0fb9e7a1fe8c0a95f35583c9cf11
tree4b57fe700f5c5141cb41d226d53141fbde159614
parent41124c726001498bb09e6c1b3eaff71e6ca0b43d
signature Signed by PGP key B5690EEEBB952194

fix(mobile): accept Android clipboard images in composer (#4836)


1 files changed, 61 insertions(+), 56 deletions(-)

apps/mobile/src/native/T3ComposerEditor.native.tsx+61-56
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import { collectComposerInlineTokens } from "@t3tools/shared/composerInlineTokens";1import { collectComposerInlineTokens } from "@t3tools/shared/composerInlineTokens";
2import { requireNativeView } from "expo";2import { requireNativeView } from "expo";
3import { TextInputWrapper } from "expo-paste-input";
3import {4import {
4 useCallback,5 useCallback,
5 useEffect,6 useEffect,
...@@ -9,12 +10,13 @@ import {...@@ -9,12 +10,13 @@ import {
9 useState,10 useState,
10 type Ref,11 type Ref,
11} from "react";12} from "react";
12import type { NativeSyntheticEvent, StyleProp, ViewProps, ViewStyle } from "react-native";13import type { NativeSyntheticEvent, ViewProps } from "react-native";
13import { Image, StyleSheet } from "react-native";14import { Image, StyleSheet } from "react-native";
1415
15import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons";16import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons";
16import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";17import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";
17import { MOBILE_TYPOGRAPHY } from "../lib/typography";18import { MOBILE_TYPOGRAPHY } from "../lib/typography";
19import { useNativePaste } from "../lib/useNativePaste";
18import { useFontFamily } from "../lib/useFontFamily";20import { useFontFamily } from "../lib/useFontFamily";
19import { useThemeColor } from "../lib/useThemeColor";21import { useThemeColor } from "../lib/useThemeColor";
20import {22import {
...@@ -117,6 +119,7 @@ export function ComposerEditor({...@@ -117,6 +119,7 @@ export function ComposerEditor({
117 const skillBorder = useThemeColor("--color-inline-skill-border");119 const skillBorder = useThemeColor("--color-inline-skill-border");
118 const skillText = useThemeColor("--color-inline-skill-foreground");120 const skillText = useThemeColor("--color-inline-skill-foreground");
119 const fileTint = useThemeColor("--color-icon-muted");121 const fileTint = useThemeColor("--color-icon-muted");
122 const handlePaste = useNativePaste((uris) => onPasteImages?.(uris));
120123
121 useImperativeHandle(124 useImperativeHandle(
122 ref,125 ref,
...@@ -221,61 +224,63 @@ export function ComposerEditor({...@@ -221,61 +224,63 @@ export function ComposerEditor({
221 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};224 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};
222 const regularFontFamily = useFontFamily("regular");225 const regularFontFamily = useFontFamily("regular");
223 return (226 return (
224 <NativeView227 <TextInputWrapper onPaste={handlePaste} style={[{ minHeight: 0 }, style]}>
225 ref={nativeRef}228 <NativeView
226 controlledDocumentJson={controlledDocumentJson}229 ref={nativeRef}
227 themeJson={themeJson}230 controlledDocumentJson={controlledDocumentJson}
228 placeholder={props.placeholder ?? ""}231 themeJson={themeJson}
229 fontFamily={232 placeholder={props.placeholder ?? ""}
230 typeof resolvedTextStyle.fontFamily === "string"233 fontFamily={
231 ? resolvedTextStyle.fontFamily234 typeof resolvedTextStyle.fontFamily === "string"
232 : regularFontFamily235 ? resolvedTextStyle.fontFamily
233 }236 : regularFontFamily
234 fontSize={237 }
235 typeof resolvedTextStyle.fontSize === "number"238 fontSize={
236 ? resolvedTextStyle.fontSize239 typeof resolvedTextStyle.fontSize === "number"
237 : MOBILE_TYPOGRAPHY.body.fontSize240 ? resolvedTextStyle.fontSize
238 }241 : MOBILE_TYPOGRAPHY.body.fontSize
239 lineHeight={242 }
240 typeof resolvedTextStyle.lineHeight === "number"243 lineHeight={
241 ? resolvedTextStyle.lineHeight244 typeof resolvedTextStyle.lineHeight === "number"
242 : MOBILE_TYPOGRAPHY.body.lineHeight245 ? resolvedTextStyle.lineHeight
243 }246 : MOBILE_TYPOGRAPHY.body.lineHeight
244 contentInsetVertical={contentInsetVertical}247 }
245 singleLineCentered={props.singleLineCentered ?? false}248 contentInsetVertical={contentInsetVertical}
246 editable={props.editable ?? true}249 singleLineCentered={props.singleLineCentered ?? false}
247 scrollEnabled={props.scrollEnabled ?? true}250 editable={props.editable ?? true}
248 autoFocus={props.autoFocus ?? false}251 scrollEnabled={props.scrollEnabled ?? true}
249 autoCorrect={props.autoCorrect ?? true}252 autoFocus={props.autoFocus ?? false}
250 spellCheck={props.spellCheck ?? true}253 autoCorrect={props.autoCorrect ?? true}
251 style={style as StyleProp<ViewStyle>}254 spellCheck={props.spellCheck ?? true}
252 onComposerChange={(event) => {255 style={{ flex: 1, minHeight: 0 }}
253 const acknowledgedEventCount = acceptNativeEvent(256 onComposerChange={(event) => {
254 event.nativeEvent.eventCount,257 const acknowledgedEventCount = acceptNativeEvent(
255 event.nativeEvent.value,258 event.nativeEvent.eventCount,
256 event.nativeEvent.selection,259 event.nativeEvent.value,
257 );260 event.nativeEvent.selection,
258 if (acknowledgedEventCount === false) return;261 );
259 onChangeText(event.nativeEvent.value);262 if (acknowledgedEventCount === false) return;
260 onSelectionChange?.(event.nativeEvent.selection);263 onChangeText(event.nativeEvent.value);
261 setMostRecentEventCount(acknowledgedEventCount);264 onSelectionChange?.(event.nativeEvent.selection);
262 setNativeEventSequence((sequence) => sequence + 1);265 setMostRecentEventCount(acknowledgedEventCount);
263 }}266 setNativeEventSequence((sequence) => sequence + 1);
264 onComposerSelectionChange={(event) => {267 }}
265 const acknowledgedEventCount = acceptNativeEvent(268 onComposerSelectionChange={(event) => {
266 event.nativeEvent.eventCount,269 const acknowledgedEventCount = acceptNativeEvent(
267 event.nativeEvent.value,270 event.nativeEvent.eventCount,
268 event.nativeEvent.selection,271 event.nativeEvent.value,
269 );272 event.nativeEvent.selection,
270 if (acknowledgedEventCount === false) return;273 );
271 onSelectionChange?.(event.nativeEvent.selection);274 if (acknowledgedEventCount === false) return;
272 setMostRecentEventCount(acknowledgedEventCount);275 onSelectionChange?.(event.nativeEvent.selection);
273 setNativeEventSequence((sequence) => sequence + 1);276 setMostRecentEventCount(acknowledgedEventCount);
274 }}277 setNativeEventSequence((sequence) => sequence + 1);
275 onComposerPasteImages={(event) => onPasteImages?.(event.nativeEvent.uris)}278 }}
276 onComposerFocus={onFocus}279 onComposerPasteImages={(event) => onPasteImages?.(event.nativeEvent.uris)}
277 onComposerBlur={onBlur}280 onComposerFocus={onFocus}
278 />281 onComposerBlur={onBlur}
282 />
283 </TextInputWrapper>
279 );284 );
280}285}
281286