diff --git a/shared/app/index.native.tsx b/shared/app/index.native.tsx index 7858e811c81a..35ee779518d0 100644 --- a/shared/app/index.native.tsx +++ b/shared/app/index.native.tsx @@ -4,7 +4,7 @@ import {useConfigState} from '@/stores/config' import {useShellState} from '@/stores/shell' import * as Kb from '@/common-adapters' import * as React from 'react' -import Main from './main.native' +import Main from './main' import {KeyboardProvider} from 'react-native-keyboard-controller' import Animated, {ReducedMotionConfig, ReduceMotion} from 'react-native-reanimated' import {AppRegistry, AppState, Appearance, Keyboard} from 'react-native' @@ -20,7 +20,7 @@ import {useUnmountAll} from '@/util/debug-react' import {darkModeSupported, guiConfig} from 'react-native-kb' import {install} from 'react-native-kb' import * as DarkMode from '@/stores/darkmode' -import {initPlatformListener, onEngineConnected, onEngineDisconnected, onEngineIncoming} from '@/constants/init/index.native' +import {initPlatformListener, onEngineConnected, onEngineDisconnected, onEngineIncoming} from '@/constants/init/index' import logger from '@/logger' logger.info('INIT App index module load') diff --git a/shared/app/main.desktop.tsx b/shared/app/main.desktop.tsx deleted file mode 100644 index 64e506f443fa..000000000000 --- a/shared/app/main.desktop.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import Router from '@/router-v2/router' -import ResetModal from '../login/reset/modal' -import GlobalError from './global-errors' -import OutOfDate from './out-of-date' -import RemoteProxies from '../desktop/remote/proxies.desktop' -import {FsStatusProvider} from '@/fs/common/status' -import {SystemFileManagerIntegrationProvider} from '@/fs/common/sfmi' - -const Main = function Main() { - return ( - - - - - - - - - - ) -} -// get focus so react doesn't hold onto old divs - -export default Main diff --git a/shared/app/main.native.tsx b/shared/app/main.tsx similarity index 64% rename from shared/app/main.native.tsx rename to shared/app/main.tsx index c5f107199f63..62aff1061068 100644 --- a/shared/app/main.native.tsx +++ b/shared/app/main.tsx @@ -1,14 +1,29 @@ import Router from '@/router-v2/router' -import {PortalHost} from '@/common-adapters/portal.native' import ResetModal from '../login/reset/modal' import GlobalError from './global-errors' import OutOfDate from './out-of-date' -import RuntimeStats from './runtime-stats' -import {BottomSheetModalProvider} from '@gorhom/bottom-sheet' import {FsStatusProvider} from '@/fs/common/status' import {SystemFileManagerIntegrationProvider} from '@/fs/common/sfmi' +import RemoteProxies from '../desktop/remote/proxies.desktop' +import {PortalHost} from '@/common-adapters/portal.native' +import RuntimeStats from './runtime-stats' +import {BottomSheetModalProvider} from '@gorhom/bottom-sheet' + +const DesktopMain = function DesktopMain() { + return ( + + + + + + + + + + ) +} -const Main = () => { +const NativeMain = () => { return ( @@ -25,4 +40,4 @@ const Main = () => { ) } -export default Main +export default isMobile ? NativeMain : DesktopMain diff --git a/shared/chat/audio/audio-video.tsx b/shared/chat/audio/audio-video.tsx index ea8247f2f57e..14dca8d7b226 100644 --- a/shared/chat/audio/audio-video.tsx +++ b/shared/chat/audio/audio-video.tsx @@ -1,14 +1,9 @@ import * as React from 'react' -import type * as ExpoAudioModule from 'expo-audio' -import type * as ExpoModule from 'expo' +import {useAudioPlayer} from 'expo-audio' +import {useEventListener} from 'expo' import type {Props} from './audio-video.shared' const MobileAudioVideo = (props: Props) => { - - const {useAudioPlayer} = require('expo-audio') as typeof ExpoAudioModule - - const {useEventListener} = require('expo') as typeof ExpoModule - const {url, paused, onPositionUpdated, onEnded} = props const player = useAudioPlayer(url) @@ -55,9 +50,9 @@ const DesktopAudioVideo = (props: Props) => { } }, [paused]) - const onTimeUpdate = (e: React.SyntheticEvent<{currentTime: number; duration: number}>) => { - const ct = e.currentTarget.currentTime - const dur = e.currentTarget.duration + const onTimeUpdate = () => { + const ct = vidRef.current?.currentTime ?? 0 + const dur = vidRef.current?.duration ?? 0 if (dur === 0) return onPositionUpdated(ct / dur) } diff --git a/shared/chat/conversation/attachment-fullscreen/index.tsx b/shared/chat/conversation/attachment-fullscreen/index.tsx index cdbe6b0ae1a5..98e842d3cfd6 100644 --- a/shared/chat/conversation/attachment-fullscreen/index.tsx +++ b/shared/chat/conversation/attachment-fullscreen/index.tsx @@ -5,6 +5,11 @@ import logger from '@/logger' import type {Props} from './index.shared' import {useData, usePreviewFallback} from './hooks' import type {StyleOverride} from '@/common-adapters/markdown' +import {ShowToastAfterSaving} from '../messages/attachment/shared' +import {Animated, View} from 'react-native' +import {useSafeAreaFrame} from 'react-native-safe-area-context' +import {Image} from 'expo-image' +import {useVideoPlayer, VideoView} from 'expo-video' // Stub type to avoid DOM lib dependency in native tsconfig type VideoRef = {pause?: () => void} @@ -168,10 +173,6 @@ const DesktopFullscreen = (p: Props) => { type GestureEvent = { nativeEvent: {touches: Array; pageX: number} } -type AnimatedValue = { - addListener?: (cb: (v: {value: number}) => void) => string - removeAllListeners?: () => void -} const NativeFullscreenVideo = (p: { path: string @@ -182,29 +183,6 @@ const NativeFullscreenVideo = (p: { }) => { const {path, previewHeight, onTouchStart, onTouchEnd, onLoaded} = p - const {useVideoPlayer, VideoView} = require('expo-video') as { - useVideoPlayer: (uri: string) => { - addListener: ( - event: string, - cb: (e: {status?: string; error?: unknown}) => void - ) => {remove: () => void} - } - VideoView: React.ComponentType<{ - player: unknown - nativeControls?: boolean - contentFit?: string - style?: Kb.Styles.StylesCrossPlatform - }> - } - const {View} = require('react-native') as { - View: React.ComponentType<{ - style?: Kb.Styles.StylesCrossPlatform - onTouchStart?: (e: GestureEvent) => void - onTouchEnd?: (e: GestureEvent) => void - children?: React.ReactNode - }> - } - const sourceUri = `${path}&contentforce=true` const player = useVideoPlayer(sourceUri) @@ -243,36 +221,13 @@ const NativeFullscreen = (p: Props) => { const {hasMessageID} = data const [loaded, setLoaded] = React.useState(false) const [showHeader, setShowHeader] = React.useState(_showHeader) - const fadeAnimRef = React.useRef(null) - const [fadeAnim, setFadeAnim] = React.useState(null) - - const {ShowToastAfterSaving} = require('../messages/attachment/shared') as { - ShowToastAfterSaving: React.ComponentType<{transferState: unknown}> - } - const {Animated} = require('react-native') as { - Animated: { - Value: new (v: number) => AnimatedValue - View: React.ComponentType<{ - style?: Array - children?: React.ReactNode - }> - timing: ( - val: AnimatedValue, - opts: {toValue: number; duration: number; useNativeDriver: boolean} - ) => {start: () => void} - } - } - const {useSafeAreaFrame} = require('react-native-safe-area-context') as { - useSafeAreaFrame: () => {width: number; height: number} - } - const {Image} = require('expo-image') as { - Image: {prefetch: (path: string) => Promise} - } + const fadeAnimRef = React.useRef(null) + const [fadeAnim, setFadeAnim] = React.useState(null) React.useEffect(() => { fadeAnimRef.current = new Animated.Value(1) setFadeAnim(fadeAnimRef.current) - }, [Animated]) + }, []) React.useEffect(() => { if (fadeAnim) { @@ -282,7 +237,7 @@ const NativeFullscreen = (p: Props) => { useNativeDriver: true, }).start() } - }, [showHeader, fadeAnim, Animated]) + }, [showHeader, fadeAnim]) const preload = (src: string, onLoad: () => void, onError: () => void) => { const f = async () => { diff --git a/shared/chat/conversation/giphy/index.native.tsx b/shared/chat/conversation/giphy/index.native.tsx deleted file mode 100644 index db191d9a09b7..000000000000 --- a/shared/chat/conversation/giphy/index.native.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as Kb from '@/common-adapters' -import {colors, darkColors} from '@/styles/colors' -import {WebView} from 'react-native-webview' -import noop from 'lodash/noop' -import {useHooks} from './hooks' -import {useColorScheme} from 'react-native' - -const GiphySearch = () => { - const p = useHooks() - const source = {uri: p.galleryURL} - const darkMode = useColorScheme() === 'dark' - const injectedJavaScript = ` -(function() { - window.document.querySelector("body").style.backgroundColor = "${ - darkMode ? darkColors.white : colors.white - }"; -})(); -` - - return ( - - {p.previews ? ( - - ) : ( - - - - )} - - ) -} - -const styles = Kb.Styles.styleSheetCreate( - () => - ({ - container: {height: 80}, - }) as const -) - -export default GiphySearch diff --git a/shared/chat/conversation/giphy/index.desktop.tsx b/shared/chat/conversation/giphy/index.tsx similarity index 71% rename from shared/chat/conversation/giphy/index.desktop.tsx rename to shared/chat/conversation/giphy/index.tsx index 807d8e8fc73c..51746eab4c35 100644 --- a/shared/chat/conversation/giphy/index.desktop.tsx +++ b/shared/chat/conversation/giphy/index.tsx @@ -1,21 +1,31 @@ -import * as React from 'react' import * as Kb from '@/common-adapters' -import UnfurlImage from '../messages/text/unfurl/unfurl-list/image' -import {getMargins, scaledWidth} from './width' +import * as React from 'react' import {useHooks} from './hooks' +import {getMargins, scaledWidth} from './width' +import UnfurlImage from '../messages/text/unfurl/unfurl-list/image' +import {colors, darkColors} from '@/styles/colors' +import {WebView} from 'react-native-webview' +import {useColorScheme} from 'react-native' +import noop from 'lodash/noop' -const gridHeight = 100 +// Stub type to avoid dom lib dependency in native tsconfig +type DivRef = { + getBoundingClientRect: () => DOMRect + clientWidth: number +} -const GiphySearch = () => { +const DesktopGiphySearch = () => { + const gridHeight = 100 const props = useHooks() const [width, setWidth] = React.useState(undefined) - const divRef = React.useRef(null) + const divRef = React.useRef(null) const learnMoreUrlProps = Kb.useClickURL('https://keybase.io/docs/chat/linkpreviews') React.useEffect(() => { if (!divRef.current) return - const cs = getComputedStyle(divRef.current) - setWidth(divRef.current.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight)) + const gc = (globalThis as {getComputedStyle?: (el: unknown) => {paddingLeft: string; paddingRight: string}}).getComputedStyle + const cs = gc?.(divRef.current) + setWidth(divRef.current.clientWidth - parseFloat(cs?.paddingLeft ?? '0') - parseFloat(cs?.paddingRight ?? '0')) }, []) let margins: Array = [] @@ -32,7 +42,7 @@ const GiphySearch = () => { } style={Kb.Styles.collapseStyles([ styles.scrollContainer, Kb.Styles.platformStyles({isElectron: {overflowY: width ? 'auto' : 'scroll'}}), @@ -89,6 +99,38 @@ const GiphySearch = () => { ) } +const NativeGiphySearch = () => { + const p = useHooks() + const source = {uri: p.galleryURL} + const darkMode = useColorScheme() === 'dark' + const injectedJavaScript = ` +(function() { + window.document.querySelector("body").style.backgroundColor = "${ + darkMode ? darkColors['white'] : colors['white'] + }"; +})(); +` + + return ( + + {p.previews ? ( + + ) : ( + + + + )} + + ) +} + const styles = Kb.Styles.styleSheetCreate( () => ({ @@ -116,7 +158,6 @@ const styles = Kb.Styles.styleSheetCreate( lineHeight: 17, }, }), - loadingContainer: { minHeight: 200, }, @@ -144,4 +185,11 @@ const styles = Kb.Styles.styleSheetCreate( }) as const ) -export default GiphySearch +const nativeStyles = Kb.Styles.styleSheetCreate( + () => + ({ + container: {height: 80}, + }) as const +) + +export default isMobile ? NativeGiphySearch : DesktopGiphySearch diff --git a/shared/chat/conversation/input-area/location-popup.tsx b/shared/chat/conversation/input-area/location-popup.tsx index 3fdfb6b4d242..a5d25b63dabd 100644 --- a/shared/chat/conversation/input-area/location-popup.tsx +++ b/shared/chat/conversation/input-area/location-popup.tsx @@ -7,7 +7,7 @@ import * as T from '@/constants/types' import LocationMap from '@/chat/location-map' import {useCurrentUserState} from '@/stores/current-user' import {requestLocationPermission} from '@/util/platform-specific' -import type * as ExpoLocationModule from 'expo-location' +import * as ExpoLocation from 'expo-location' import {ignorePromise} from '@/constants/utils' import {openAppSettings} from '@/util/storeless-actions' import {setThreadInputCommandStatus} from '@/constants/router' @@ -63,13 +63,12 @@ const useWatchPosition = ( logger.info('[location] perms check due to map') const f = async () => { try { - const ExpoLocation = require('expo-location') as typeof ExpoLocationModule await (requestLocationPermission as (mode?: T.RPCChat.UIWatchPositionPerm) => Promise)( T.RPCChat.UIWatchPositionPerm.base ) const sub = await ExpoLocation.watchPositionAsync( {accuracy: ExpoLocation.LocationAccuracy.Highest}, - (location: ExpoLocationModule.LocationObject) => { + (location: ExpoLocation.LocationObject) => { const coord = { accuracy: Math.floor(location.coords.accuracy ?? 0), lat: location.coords.latitude, diff --git a/shared/chat/conversation/input-area/normal/input.desktop.tsx b/shared/chat/conversation/input-area/normal/input.desktop.tsx deleted file mode 100644 index 60b6fe6569b2..000000000000 --- a/shared/chat/conversation/input-area/normal/input.desktop.tsx +++ /dev/null @@ -1,680 +0,0 @@ -import * as C from '@/constants' -import * as T from '@/constants/types' -import * as Kb from '@/common-adapters' -import * as React from 'react' -import * as InputState from '../input-state' -import SetExplodingMessagePopup from './set-explode-popup' -import Typing from './typing' -import type {Props as InputLowLevelProps, TextInfo, RefType} from './input.shared' -import type {PlatformInputProps as Props} from './input.shared' -export type {Selection, RefType, TextInfo, PlatformInputProps} from './input.shared' -import {EmojiPickerDesktop} from '@/chat/emoji-picker/container' -import {KeyEventHandler} from '@/common-adapters/key-event-handler.desktop' -import {formatDurationShort} from '@/util/timestamp' -import {useSuggestors} from '../suggestors' -import {ScrollContext} from '@/chat/conversation/normal/context' -import {getTextStyle} from '@/common-adapters/text.styles' -import {useColorScheme} from 'react-native' -import KB2 from '@/util/electron.desktop' -import {useConversationThreadID} from '../../thread-context' - -const {getPathForFile} = KB2.functions - -const maybeParseInt = (input: string | number, radix: number): number => - typeof input === 'string' ? parseInt(input, radix) : input - -export function Input(p: InputLowLevelProps) { - const {style: _style, onChangeText: _onChangeText, multiline, ref} = p - const {textType = 'Body', rowsMax, rowsMin, padding, placeholder, onKeyUp: _onKeyUp} = p - const {allowKeyboardEvents, className, disabled, autoFocus, onKeyDown: _onKeyDown, onEnterKeyDown} = p - - const isDarkMode = useColorScheme() === 'dark' - - const [value, setValue] = React.useState('') - // this isn't a value react can set on the input, so we need to drive it manually - const selectionRef = React.useRef({end: 0, start: 0}) - const inputSingleRef = React.useRef(null) - const inputMultiRef = React.useRef(null) - - const onChangeTextRef = React.useRef(_onChangeText) - React.useEffect(() => { - onChangeTextRef.current = _onChangeText - }, [_onChangeText]) - const [onChange] = React.useState(() => (e: {target: HTMLInputElement | HTMLTextAreaElement}) => { - const s = e.target.value - setValue(s) - onChangeTextRef.current?.(s) - }) - const onSelect = (e: {currentTarget: HTMLInputElement | HTMLTextAreaElement}) => { - selectionRef.current = { - end: e.currentTarget.selectionEnd || 0, - start: e.currentTarget.selectionStart || 0, - } - } - - React.useImperativeHandle(ref, () => { - const i = multiline ? inputMultiRef.current : inputSingleRef.current - return { - blur: () => { - i?.blur() - }, - clear: () => { - if (i) { - i.value = '' - onChange({target: i}) - } - }, - focus: () => { - i?.focus() - }, - getBoundingClientRect: () => { - return i?.getBoundingClientRect() - }, - getSelection: () => { - return selectionRef.current - }, - isFocused: () => !!i && document.activeElement === i, - transformText: (fn: (textInfo: TextInfo) => TextInfo, reflectChange: boolean): void => { - const ti = fn({selection: selectionRef.current, text: value}) - // defer since we can do this in other renders - setTimeout(() => { - setValue(ti.text) - selectionRef.current = {end: ti.selection?.end ?? 0, start: ti.selection?.start ?? 0} - // defer this else we'll get onSelect called and wipe it out - setTimeout(() => { - if (i && ti.selection) { - if (typeof ti.selection.start === 'number') { - i.selectionStart = ti.selection.start - } - if (typeof ti.selection.end === 'number') { - i.selectionEnd = ti.selection.end - } - } - }, 10) - if (reflectChange) { - setTimeout(() => { - if (!i) return - onChange({target: i}) - }, 100) - } - }, 0) - }, - value, - } - }, [value, multiline, onChange]) - - const rows = multiline ? rowsMin || Math.min(2, rowsMax || 2) : 0 - const style = (() => { - const textStyle = getTextStyle(textType, isDarkMode) - if (multiline) { - const heightStyles: {minHeight: number; maxHeight?: number} = { - minHeight: - rows * (textStyle.lineHeight === undefined ? 20 : maybeParseInt(textStyle.lineHeight, 10) || 20) + - (padding ? Kb.Styles.globalMargins[padding] * 2 : 0), - } - - if (rowsMax) { - heightStyles.maxHeight = - rowsMax * (textStyle.lineHeight === undefined ? 20 : maybeParseInt(textStyle.lineHeight, 10) || 20) - } - - const paddingStyles = padding ? Kb.Styles.padding(Kb.Styles.globalMargins[padding]) : {} - - return Kb.Styles.collapseStyles([ - inputLowLevelStyles.noChrome, // noChrome comes before because we want lineHeight set in multiline - textStyle, - inputLowLevelStyles.multiline, - heightStyles, - paddingStyles, - _style, - ]) - } else { - return Kb.Styles.collapseStyles([ - textStyle, - inputLowLevelStyles.noChrome, // noChrome comes after to unset lineHeight in singleline - _style, - ]) - } - })() - - const isComposingIMERef = React.useRef(false) - - const onCompositionStart = () => { - isComposingIMERef.current = true - } - - const onCompositionEnd = () => { - isComposingIMERef.current = false - } - - const onKeyDown = (e: React.KeyboardEvent) => { - if (isComposingIMERef.current) { - return - } - _onKeyDown?.(e) - if (onEnterKeyDown && e.key === 'Enter' && !(e.shiftKey || e.ctrlKey || e.altKey)) { - onEnterKeyDown(e) - } - } - - const onKeyUp = (e: React.KeyboardEvent) => { - if (isComposingIMERef.current) { - return - } - _onKeyUp?.(e) - } - - const commonProps = { - autoFocus, - className, - onChange, - onCompositionEnd, - onCompositionStart, - onKeyDown, - onKeyUp, - onSelect, - placeholder, - value, - ...(disabled ? {readOnly: true} : {}), - ...((allowKeyboardEvents ?? true) ? {'data-allow-keyboard-shortcuts': 'true'} : {}), - } - - return multiline ? ( -