From 6aa9fb6ff477a0ee2b5966765451b72167767e88 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Mon, 6 Jan 2025 17:08:55 -0500 Subject: [PATCH 1/3] WIP --- shared/constants/platform-specific/index.native.tsx | 13 ++++++++++++- shared/todo.txt | 9 +++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/shared/constants/platform-specific/index.native.tsx b/shared/constants/platform-specific/index.native.tsx index a5a33004d00..69c69204344 100644 --- a/shared/constants/platform-specific/index.native.tsx +++ b/shared/constants/platform-specific/index.native.tsx @@ -76,7 +76,18 @@ export async function saveAttachmentToCameraRoll(filePath: string, mimeType: str await requestPermissionsToWrite() } catch {} logger.info(logPrefix + `Attempting to save as ${saveType}`) - await MediaLibrary.saveToLibraryAsync(fileURL) + if (isIOS) { + await MediaLibrary.saveToLibraryAsync(fileURL) + } else { + const asset = await MediaLibrary.createAssetAsync(fileURL) + const albumName = 'Keybase' + const _album = await MediaLibrary.getAlbumAsync(albumName) + let album = _album as typeof _album | null + if (!album) { + album = await MediaLibrary.createAlbumAsync(albumName, asset, false) + } + await MediaLibrary.addAssetsToAlbumAsync([asset], album, false) + } logger.info(logPrefix + 'Success') } catch (e) { // This can fail if the user backgrounds too quickly, so throw up a local notification diff --git a/shared/todo.txt b/shared/todo.txt index 6f0160fbe94..f19ad74a8ac 100644 --- a/shared/todo.txt +++ b/shared/todo.txt @@ -1,3 +1,12 @@ +chat file save flows +click on pdf insta saves with a quick blink of the save overlay? +kbfs save flows + + + + + + crash on dark mode change in fs tab? expo-av to expo-video / expo-audio (not ready yet can't get video size...) inbox scroll perf issues From 30122e4402db9ff634da09caeea7df287d290ed9 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Wed, 8 Jan 2025 16:38:31 -0500 Subject: [PATCH 2/3] make old/new arch build on android unified. set old arch to work around header issue. fix cachedir not getting set --- .../main/java/com/reactnativekb/KbModule.kt | 35 ++++++++----------- .../android/src/oldarch/KbSpec.kt | 4 +-- rnmodules/react-native-kb/src/NativeKb.ts | 28 ++++++++------- rnmodules/react-native-kb/src/index.tsx | 24 +++++++------ shared/android/gradle.properties | 3 +- shared/app/index.native.tsx | 27 +++++++------- shared/constants/index.tsx | 9 ++--- .../platform-specific/index.native.tsx | 21 ++++++----- shared/engine/index-impl.tsx | 1 + 9 files changed, 77 insertions(+), 75 deletions(-) diff --git a/rnmodules/react-native-kb/android/src/main/java/com/reactnativekb/KbModule.kt b/rnmodules/react-native-kb/android/src/main/java/com/reactnativekb/KbModule.kt index 001ccc5024c..0b33e999868 100644 --- a/rnmodules/react-native-kb/android/src/main/java/com/reactnativekb/KbModule.kt +++ b/rnmodules/react-native-kb/android/src/main/java/com/reactnativekb/KbModule.kt @@ -109,14 +109,8 @@ internal class KbModule(reactContext: ReactApplicationContext?) : KbSpec(reactCo return GuiConfig.getInstance(reactContext.getFilesDir())?.asString() } - - // only old arch, uncomment - // override fun getConstants(): MutableMap? { - // return getTypedExportedConstants() - // } - - // newarch @Override - override fun getTypedExportedConstants(): MutableMap { + @ReactMethod(isBlockingSynchronousMethod = true) + override fun getTypedConstants(): WritableMap { val versionCode: String = getBuildConfigValue("VERSION_CODE").toString() val versionName: String = getBuildConfigValue("VERSION_NAME").toString() var isDeviceSecure = false @@ -146,18 +140,19 @@ internal class KbModule(reactContext: ReactApplicationContext?) : KbSpec(reactCo downloadDir = dir.getAbsolutePath() } } - val constants: MutableMap = HashMap() - constants.put("androidIsDeviceSecure", isDeviceSecure) - constants.put("androidIsTestDevice", misTestDevice) - constants.put("appVersionCode", versionCode) - constants.put("appVersionName", versionName) - constants.put("darkModeSupported", false) - constants.put("fsCacheDir", cacheDir) - constants.put("fsDownloadDir", downloadDir) - constants.put("guiConfig", readGuiConfig() as Any) - constants.put("serverConfig", serverConfig) - constants.put("uses24HourClock", DateFormat.is24HourFormat(reactContext)) - constants.put("version", version()) + + val constants: WritableMap = Arguments.createMap() + constants.putBoolean("androidIsDeviceSecure", isDeviceSecure) + constants.putBoolean("androidIsTestDevice", misTestDevice) + constants.putString("appVersionCode", versionCode) + constants.putString("appVersionName", versionName) + constants.putBoolean("darkModeSupported", false) + constants.putString("fsCacheDir", cacheDir) + constants.putString("fsDownloadDir", downloadDir) + constants.putString("guiConfig", readGuiConfig()) + constants.putString("serverConfig", serverConfig) + constants.putBoolean("uses24HourClock", DateFormat.is24HourFormat(reactContext)) + constants.putString("version", version()) return constants } diff --git a/rnmodules/react-native-kb/android/src/oldarch/KbSpec.kt b/rnmodules/react-native-kb/android/src/oldarch/KbSpec.kt index 7558f7a2f28..bb90f05acb7 100644 --- a/rnmodules/react-native-kb/android/src/oldarch/KbSpec.kt +++ b/rnmodules/react-native-kb/android/src/oldarch/KbSpec.kt @@ -4,11 +4,11 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReadableMap -import java.util.Map +import com.facebook.react.bridge.WritableMap abstract class KbSpec(context: ReactApplicationContext?) : ReactContextBaseJavaModule(context) { - abstract fun getTypedExportedConstants(): MutableMap + abstract fun getTypedConstants(): WritableMap abstract fun install(): Boolean abstract fun getDefaultCountryCode(promise: Promise) abstract fun logSend(status: String, feedback: String, sendLogs: Boolean, sendMaxBytes: Boolean, traceDir: String, cpuProfileDir: String, promise: Promise) diff --git a/rnmodules/react-native-kb/src/NativeKb.ts b/rnmodules/react-native-kb/src/NativeKb.ts index 6270162cbfd..6a7e1bdd035 100644 --- a/rnmodules/react-native-kb/src/NativeKb.ts +++ b/rnmodules/react-native-kb/src/NativeKb.ts @@ -4,18 +4,22 @@ export interface Spec extends TurboModule { install: () => boolean addListener: (eventType: string) => void removeListeners: (count: number) => void - getConstants(): { - androidIsDeviceSecure: boolean - androidIsTestDevice: boolean - appVersionCode: string - appVersionName: string - darkModeSupported: boolean - fsCacheDir: string - fsDownloadDir: string - guiConfig: string - serverConfig: string - uses24HourClock: boolean - version: string + // not used directly, just used to get constants which are individually pulled out, see ./index.tsx + getTypedConstants: () => { + [key: string]: unknown + /* + androidIsDeviceSecure: boolean + androidIsTestDevice: boolean + appVersionCode: string + appVersionName: string + darkModeSupported: boolean + fsCacheDir: string + fsDownloadDir: string + guiConfig: string + serverConfig: string + uses24HourClock: boolean + version: string + */ } getDefaultCountryCode(): Promise logSend( diff --git a/rnmodules/react-native-kb/src/index.tsx b/rnmodules/react-native-kb/src/index.tsx index 60b0d63ad84..446ed9a1bea 100644 --- a/rnmodules/react-native-kb/src/index.tsx +++ b/rnmodules/react-native-kb/src/index.tsx @@ -145,14 +145,16 @@ export const getNativeEmitter = () => { return new NativeEventEmitter(Kb as any) } -export const androidIsDeviceSecure: boolean = Kb.getConstants().androidIsDeviceSecure -export const androidIsTestDevice: boolean = Kb.getConstants().androidIsTestDevice -export const appVersionCode: string = Kb.getConstants().appVersionCode -export const appVersionName: string = Kb.getConstants().appVersionCode -export const darkModeSupported: boolean = Kb.getConstants().darkModeSupported -export const fsCacheDir: string = Kb.getConstants().fsCacheDir -export const fsDownloadDir: string = Kb.getConstants().fsDownloadDir -export const guiConfig: string = Kb.getConstants().guiConfig -export const serverConfig: string = Kb.getConstants().serverConfig -export const uses24HourClock: boolean = Kb.getConstants().uses24HourClock -export const version: string = Kb.getConstants().version +const pc = Kb.getTypedConstants() + +export const androidIsDeviceSecure: boolean = pc.androidIsDeviceSecure +export const androidIsTestDevice: boolean = pc.androidIsTestDevice +export const appVersionCode: string = pc.appVersionCode +export const appVersionName: string = pc.appVersionCode +export const darkModeSupported: boolean = pc.darkModeSupported +export const fsCacheDir: string = pc.fsCacheDir +export const fsDownloadDir: string = pc.fsDownloadDir +export const guiConfig: string = pc.guiConfig +export const serverConfig: string = pc.serverConfig +export const uses24HourClock: boolean = pc.uses24HourClock +export const version: string = pc.version diff --git a/shared/android/gradle.properties b/shared/android/gradle.properties index be74d4d57d3..3052afaf382 100644 --- a/shared/android/gradle.properties +++ b/shared/android/gradle.properties @@ -21,7 +21,8 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # to write custom TurboModules/Fabric components OR use libraries that # are providing them. # RUN A CLEAN if you change this -newArchEnabled=false # TEMP this is just due to https://github.com/software-mansion/react-native-screens/pull/2466 not being merged +# TEMP this is just due to https://github.com/software-mansion/react-native-screens/pull/2466 not being merged +newArchEnabled=false # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. hermesEnabled=true diff --git a/shared/app/index.native.tsx b/shared/app/index.native.tsx index 415a4afe74e..626bd879d6b 100644 --- a/shared/app/index.native.tsx +++ b/shared/app/index.native.tsx @@ -101,19 +101,22 @@ let inited = false const useInit = () => { if (inited) return inited = true - const {batch} = C.useWaitingState.getState().dispatch - const eng = makeEngine(batch, c => { - if (c) { - C.useEngineState.getState().dispatch.onEngineConnected() - } else { - C.useEngineState.getState().dispatch.onEngineDisconnected() - } - }) - C.initListeners() - eng.listenersAreReady() + const f = async () => { + const {batch} = C.useWaitingState.getState().dispatch + const eng = makeEngine(batch, c => { + if (c) { + C.useEngineState.getState().dispatch.onEngineConnected() + } else { + C.useEngineState.getState().dispatch.onEngineDisconnected() + } + }) + await C.initListeners() + eng.listenersAreReady() - // On mobile there is no installer - C.useConfigState.getState().dispatch.installerRan() + // On mobile there is no installer + C.useConfigState.getState().dispatch.installerRan() + } + C.ignorePromise(f()) } // reanimated has issues updating shared values with this on seemingly w/ zoom toolkit diff --git a/shared/constants/index.tsx b/shared/constants/index.tsx index fb1cf59cbe5..5597c7f8f65 100644 --- a/shared/constants/index.tsx +++ b/shared/constants/index.tsx @@ -77,12 +77,9 @@ import {useConfigState_ as useConfigState} from './config' export {default as shallowEqual} from 'shallowequal' export * as PlatformSpecific from './platform-specific' -export const initListeners = () => { - const f = async () => { - await useFSState.getState().dispatch.setupSubscriptions() - useConfigState.getState().dispatch.setupSubscriptions() - } - ignorePromise(f()) +export const initListeners = async () => { + await useFSState.getState().dispatch.setupSubscriptions() + useConfigState.getState().dispatch.setupSubscriptions() } // extracts the payload from pages used in routing diff --git a/shared/constants/platform-specific/index.native.tsx b/shared/constants/platform-specific/index.native.tsx index 69c69204344..5ba5d75fac2 100644 --- a/shared/constants/platform-specific/index.native.tsx +++ b/shared/constants/platform-specific/index.native.tsx @@ -425,6 +425,16 @@ export const initPlatformListener = () => { }) C.useDaemonState.subscribe((s, old) => { + if (isAndroid) { + C.ignorePromise( + T.RPCChat.localConfigureFileAttachmentDownloadLocalRpcPromise({ + // Android's cache dir is (when I tried) [app]/cache but Go side uses + // [app]/.cache by default, which can't be used for sharing to other apps. + cacheDirOverride: fsCacheDir, + downloadDirOverride: fsDownloadDir, + }) + ) + } if (s.handshakeVersion === old.handshakeVersion) return // loadStartupDetails finished already @@ -440,17 +450,6 @@ export const initPlatformListener = () => { } afterStartupDetails(true) } - - if (isAndroid) { - C.ignorePromise( - T.RPCChat.localConfigureFileAttachmentDownloadLocalRpcPromise({ - // Android's cache dir is (when I tried) [app]/cache but Go side uses - // [app]/.cache by default, which can't be used for sharing to other apps. - cacheDirOverride: fsCacheDir, - downloadDirOverride: fsDownloadDir, - }) - ) - } }) C.useConfigState.setState(s => { diff --git a/shared/engine/index-impl.tsx b/shared/engine/index-impl.tsx index fc3a4a2cc30..a7d7ea15eaa 100644 --- a/shared/engine/index-impl.tsx +++ b/shared/engine/index-impl.tsx @@ -115,6 +115,7 @@ class Engine { // We proxy the stuff over the mainWindowDispatch _onConnected() { this._hasConnected = true + this._onConnectedCB(true) } From ccdf3bbd7253026c16f73e9bf69a9c33c4d49b28 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Fri, 10 Jan 2025 09:10:37 -0500 Subject: [PATCH 3/3] WIP --- .../conversation/messages/message-popup/attachment.tsx | 2 +- shared/constants/chat2/convostate.tsx | 7 ++++++- shared/constants/platform-specific/index.d.ts | 1 + shared/constants/platform-specific/index.desktop.tsx | 4 ++++ shared/constants/platform-specific/index.native.tsx | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/shared/chat/conversation/messages/message-popup/attachment.tsx b/shared/chat/conversation/messages/message-popup/attachment.tsx index d190343b6b0..f4a41eafa07 100644 --- a/shared/chat/conversation/messages/message-popup/attachment.tsx +++ b/shared/chat/conversation/messages/message-popup/attachment.tsx @@ -50,7 +50,7 @@ const PopAttach = (ownProps: OwnProps) => { }, [messageAttachmentNativeSave, ordinal]) const onSaveAttachment = - C.isMobile && (attachmentType === 'image' || C.Chat.isImageViewable(message)) + (C.isMobile && (attachmentType === 'image' || C.Chat.isImageViewable(message))) || C.isAndroid ? _onSaveAttachment : undefined diff --git a/shared/constants/chat2/convostate.tsx b/shared/constants/chat2/convostate.tsx index 65cfd8613e6..fb3f74358c5 100644 --- a/shared/constants/chat2/convostate.tsx +++ b/shared/constants/chat2/convostate.tsx @@ -1826,7 +1826,12 @@ const createSlice: Z.ImmerStateCreator = (set, get) => { } }) logger.info('Trying to save chat attachment to camera roll') - await C.PlatformSpecific.saveAttachmentToCameraRoll(fileName, fileType) + const isCamera = fileType.startsWith('image/') || fileType.startsWith('video/') + if (isCamera) { + await C.PlatformSpecific.saveAttachmentToCameraRoll(fileName, fileType) + } else if (C.isAndroid) { + await C.PlatformSpecific.saveAttachmentToCameraRoll(fileName, fileType) + } set(s => { const m3 = s.messageMap.get(ordinal) if (m3?.type === 'attachment') { diff --git a/shared/constants/platform-specific/index.d.ts b/shared/constants/platform-specific/index.d.ts index 7eefb9fc101..1f7d091d554 100644 --- a/shared/constants/platform-specific/index.d.ts +++ b/shared/constants/platform-specific/index.d.ts @@ -11,6 +11,7 @@ export declare function showShareActionSheet(options: { method: string }> +export declare function saveAttachmentToMobile(fileURL: string, mimeType: string): Promise export declare function saveAttachmentToCameraRoll(fileURL: string, mimeType: string): Promise export declare function requestLocationPermission(mode: T.RPCChat.UIWatchPositionPerm): Promise export declare function watchPositionForMap(conversationIDKey: T.Chat.ConversationIDKey): Promise<() => void> diff --git a/shared/constants/platform-specific/index.desktop.tsx b/shared/constants/platform-specific/index.desktop.tsx index c2c8c6711e3..d76d3988130 100644 --- a/shared/constants/platform-specific/index.desktop.tsx +++ b/shared/constants/platform-specific/index.desktop.tsx @@ -26,6 +26,10 @@ export async function saveAttachmentToCameraRoll() { return Promise.reject(new Error('Save Attachment to camera roll - unsupported on this platform')) } +export async function saveAttachmentToMobile() { + return Promise.reject(new Error('Save Attachment to mobile- unsupported on this platform')) +} + export const requestLocationPermission = async () => Promise.resolve() export const watchPositionForMap = async () => Promise.resolve(() => {}) diff --git a/shared/constants/platform-specific/index.native.tsx b/shared/constants/platform-specific/index.native.tsx index 5ba5d75fac2..c30563f6f87 100644 --- a/shared/constants/platform-specific/index.native.tsx +++ b/shared/constants/platform-specific/index.native.tsx @@ -66,6 +66,8 @@ export const requestLocationPermission = async (mode: T.RPCChat.UIWatchPositionP } } +export async function saveAttachmentToMobile(filePath: string, mimeType: string): Promise {} + export async function saveAttachmentToCameraRoll(filePath: string, mimeType: string): Promise { const fileURL = 'file://' + filePath const saveType: 'video' | 'photo' = mimeType.startsWith('video') ? 'video' : 'photo'