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 001ccc5024c9..0b33e9998688 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 7558f7a2f281..bb90f05acb7a 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 6270162cbfd9..6a7e1bdd0354 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 60b0d63ad843..446ed9a1bea4 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 be74d4d57d3a..3052afaf3825 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 415a4afe74eb..626bd879d6b0 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/chat/conversation/messages/message-popup/attachment.tsx b/shared/chat/conversation/messages/message-popup/attachment.tsx index d190343b6b02..f4a41eafa07e 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 65cfd8613e60..fb3f74358c52 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/index.tsx b/shared/constants/index.tsx index fb1cf59cbe50..5597c7f8f653 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.d.ts b/shared/constants/platform-specific/index.d.ts index 7eefb9fc101e..1f7d091d554e 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 c2c8c6711e30..d76d39881309 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 a5a33004d00f..c30563f6f87b 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' @@ -76,7 +78,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 @@ -414,6 +427,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 @@ -429,17 +452,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 fc3a4a2cc306..a7d7ea15eaa3 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) } diff --git a/shared/todo.txt b/shared/todo.txt index 24908d01ec71..a590584b9773 100644 --- a/shared/todo.txt +++ b/shared/todo.txt @@ -1,3 +1,10 @@ +chat file save flows +click on pdf insta saves with a quick blink of the save overlay? +kbfs save flows + + + + TEMP disabling new arch on android due to this android can't click on stack header area in new arch: https://github.com/software-mansion/react-native-screens/pull/2466