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
16 changes: 8 additions & 8 deletions shared/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBu
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'

android {
ndkVersion rootProject.ext.ndkVersion
ndkVersion = rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion

namespace "io.keybase.ossifrage"
namespace = "io.keybase.ossifrage"
defaultConfig {
applicationId "io.keybase.ossifrage"
minSdkVersion rootProject.ext.minSdkVersion
Expand All @@ -105,7 +105,7 @@ android {
versionName VERSION_NAME
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
// KB added
multiDexEnabled true
multiDexEnabled = true
}

signingConfigs {
Expand All @@ -124,14 +124,14 @@ android {
}
}
release {
signingConfig signingConfigs.release
signingConfig = signingConfigs.release
minifyEnabled enableMinifyInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
releaseUnsigned.initWith(buildTypes.release)
releaseUnsigned {
applicationIdSuffix ".unsigned"
signingConfig buildTypes.debug.signingConfig
signingConfig = buildTypes.debug.signingConfig
matchingFallbacks = ['release']
manifestPlaceholders["usesCleartextTraffic"] = "false"
}
Expand Down Expand Up @@ -165,11 +165,11 @@ dependencies {
implementation jscFlavor
}

implementation 'androidx.work:work-runtime:2.11.1'
implementation 'androidx.work:work-runtime:2.11.2'
implementation 'androidx.multidex:multidex:2.0.1'
implementation "com.google.firebase:firebase-messaging:25.0.1"
implementation "com.google.firebase:firebase-messaging:25.0.2"
implementation "com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}"
implementation 'org.msgpack:msgpack-core:0.9.10'
implementation 'org.msgpack:msgpack-core:0.9.12'
implementation project(':keybaselib')
implementation 'com.android.installreferrer:installreferrer:2.2'
implementation "androidx.lifecycle:lifecycle-common-java8:2.10.0"
Expand Down
8 changes: 4 additions & 4 deletions shared/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@ allprojects {
repositories {
maven {
// expo-camera bundles a custom com.google.android:cameraview
url "$rootDir/../node_modules/expo-camera/android/maven"
url = "$rootDir/../node_modules/expo-camera/android/maven"
}

maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(reactNativeAndroidDir)
url = reactNativeAndroidDir
}
maven {
// Android JSC is installed from npm
url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist'))
url = new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist')
}

google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
maven { url = 'https://www.jitpack.io' }
}
}

Expand Down
7 changes: 5 additions & 2 deletions shared/app/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as React from 'react'
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'
import {AppRegistry, AppState, Appearance, Keyboard, Platform} from 'react-native'
import {PortalProvider} from '@/common-adapters/portal.native'
import {SafeAreaProvider, initialWindowMetrics} from 'react-native-safe-area-context'
import {makeEngine} from '../engine'
Expand All @@ -31,7 +31,10 @@ setServiceDecoration(ServiceDecoration)
// SDWebImage (used by expo-image) flushes its memory cache on iOS memory warnings, but
// the simulator never sends memory warnings. Cap the cache so loading hundreds of chat
// images doesn't exhaust VM in the simulator. On a real device this is a safety net only.
ExpoImage.configureCache({maxMemoryCost: 100 * 1024 * 1024})
// configureCache is iOS-only native (no Android impl) so calling it on Android throws.
if (Platform.OS === 'ios') {
ExpoImage.configureCache({maxMemoryCost: 100 * 1024 * 1024})
}

module.hot?.accept(() => {
console.log('accepted update in shared/index.native')
Expand Down
2 changes: 1 addition & 1 deletion shared/fs/banner/conflict-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ConnectedBanner = (ownProps: OwnProps) => {
C.ignorePromise(f())
}
const onGoToSamePathInDifferentTlf = (tlfPath: T.FS.Path) => {
C.Router2.navigateAppend({name: 'fsRoot', params: {path: FS.rebasePathToDifferentTlf(path, tlfPath)}})
C.Router2.navigateAppend({name: 'fsBrowse', params: {path: FS.rebasePathToDifferentTlf(path, tlfPath)}})
}
const onHelp = () => {
void openUrl('https://book.keybase.io/docs/files/details#conflict-resolution')
Expand Down
11 changes: 8 additions & 3 deletions shared/fs/banner/public-reminder.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react'
import * as Kb from '@/common-adapters'
import * as T from '@/constants/types'
import {navigateAppend} from '@/constants/router'
import {getVisibleScreen, navigateAppend} from '@/constants/router'
import {useFsPathItem} from '@/fs/common'
import * as FS from '@/constants/fs'

Expand All @@ -22,8 +22,13 @@ const PublicBanner = (props: Props) => {
const isWritable = useFsPathItem(path).writable
const lastPublicBannerClosedTlf = props.lastClosedTlf ?? ''
const setLastPublicBannerClosedTlf = React.useCallback(
(tlf: string) =>
navigateAppend({name: 'fsRoot', params: {lastClosedPublicBannerTlf: tlf, path}}, true),
(tlf: string) => {
// Dismiss = update the param on the screen we're on. The public folder may
// be the Files tab root (fsRoot) or a pushed folder (fsBrowse); replace
// only collapses to setParams when the name matches the current route.
const name = getVisibleScreen()?.name === 'fsBrowse' ? 'fsBrowse' : 'fsRoot'
navigateAppend({name, params: {lastClosedPublicBannerTlf: tlf, path}}, true)
},
[path]
)

Expand Down
3 changes: 1 addition & 2 deletions shared/fs/banner/reset-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {folderNameWithoutUsers} from '@/util/kbfs'
import * as Kb from '@/common-adapters'
import * as RowTypes from '@/fs/browser/rows/types'
import {useFsErrorActionOrThrow, useFsTlf} from '@/fs/common'
import * as FS from '@/constants/fs'
import {navToProfile} from '@/constants/router'

type OwnProps = {path: T.FS.Path}
Expand All @@ -25,7 +24,7 @@ const ConnectedBanner = (ownProps: OwnProps) => {
}, {})
const filteredPathName = folderNameWithoutUsers(pathElems[2] ?? '', users)
const filteredPath = T.FS.stringToPath(['', pathElems[0], pathElems[1], filteredPathName].join('/'))
FS.navToPath(filteredPath)
C.Router2.navigateAppend({name: 'fsBrowse', params: {path: filteredPath}})
}
const onReAddToTeam = (username: string) => () => {
if (!tlf.teamId) return
Expand Down
4 changes: 2 additions & 2 deletions shared/fs/browser/destination-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ const ConnectedDestinationPicker = (ownProps: OwnProps) => {
? () => {
moveOrCopy('copy')
clearModals()
nav.safeNavigateAppend({name: 'fsRoot', params: {path: parentPath}})
nav.safeNavigateAppend({name: 'fsBrowse', params: {path: parentPath}})
}
: undefined
const onMoveHere = isMovable
? () => {
moveOrCopy('move')
clearModals()
nav.safeNavigateAppend({name: 'fsRoot', params: {path: parentPath}})
nav.safeNavigateAppend({name: 'fsBrowse', params: {path: parentPath}})
}
: undefined
const onNewFolder =
Expand Down
2 changes: 1 addition & 1 deletion shared/fs/common/daemon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type FsDaemonActions = {
onlineStatusChanged: (onlineStatus: T.RPCGen.KbfsOnlineStatus) => void
}

const fsRouteNames: ReadonlyArray<string> = ['fsRoot', 'fsFilePreview']
const fsRouteNames: ReadonlyArray<string> = ['fsRoot', 'fsBrowse', 'fsFilePreview']

const emptyFsDaemonActions: FsDaemonActions = {
checkKbfsDaemonRpcStatus: () => {},
Expand Down
2 changes: 1 addition & 1 deletion shared/fs/common/use-open.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const useOpen = (props: Props) => {
})
} else {
nav.safeNavigateAppend({
name: 'fsRoot',
name: 'fsBrowse',
params: {initialLastModifiedTimestamp: knownTimestamp, initialPathType: knownType, path: props.path},
})
}
Expand Down
43 changes: 26 additions & 17 deletions shared/fs/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ const destPickerDesktopHeaderStyle = Kb.Styles.padding(
)
const noShrinkStyle = {flexShrink: 0} as const

// Options shared by fsRoot (the Files tab root) and fsBrowse (a folder pushed on
// top). They render the same screen; the only difference is where each lives in
// the navigator tree (see fsBrowse below).
const fsFolderGetOptions = (ownProps?: {route: {params?: {path?: T.FS.Path}}}) => {
// strange edge case where the root can actually have no params
const params = ownProps?.route.params
const path = params?.path ?? FS.defaultPath
return isMobile
? {
header: () => <MobileHeader path={path} />,
}
: {
headerRightActions: () => <Actions path={path} onTriggerFilterMobile={() => {}} />,
headerTitle: () => <Title path={path} />,
subHeader: MainBanner,
title: path === FS.defaultPath ? 'Files' : T.FS.getPathName(path),
}
}

export const newRoutes = defineRouteMap({
fsFilePreview: C.makeScreen(FsFilePreview, {
getOptions: (ownProps?) => {
Expand All @@ -84,23 +103,13 @@ export const newRoutes = defineRouteMap({
}
},
}),
fsRoot: C.makeScreen(FsRoot, {
getOptions: (ownProps?) => {
// strange edge case where the root can actually have no params
const params = ownProps?.route.params
const path = params?.path ?? FS.defaultPath
return isMobile
? {
header: () => <MobileHeader path={path} />,
}
: {
headerRightActions: () => <Actions path={path} onTriggerFilterMobile={() => {}} />,
headerTitle: () => <Title path={path} />,
subHeader: MainBanner,
title: path === FS.defaultPath ? 'Files' : T.FS.getPathName(path),
}
},
}),
fsRoot: C.makeScreen(FsRoot, {getOptions: fsFolderGetOptions}),
// Same screen as fsRoot, but used when drilling into a folder. fsRoot is the
// Files tab root (lives inside the tab stack); fsBrowse is not a tab root, so
// on phones it lands in the app root stack and renders above the bottom tab
// bar — hiding it on push, matching every other tab. Open a folder via
// fsBrowse; jump into the Files tab from elsewhere via fsRoot.
fsBrowse: C.makeScreen(FsRoot, {getOptions: fsFolderGetOptions}),
})

export const newModalRoutes = defineRouteMap({
Expand Down
2 changes: 1 addition & 1 deletion shared/fs/simple-screens/oops.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const NonExistent = (props: Props) => (
const Oops = (props: OwnProps) => {
const nav = useSafeNavigation()
const openParent = () =>
nav.safeNavigateAppend({name: 'fsRoot', params: {path: T.FS.getPathParent(props.path)}})
nav.safeNavigateAppend({name: 'fsBrowse', params: {path: T.FS.getPathParent(props.path)}})
switch (props.reason) {
case T.FS.SoftError.NoAccess:
return <NoAccess {...props} openParent={openParent} />
Expand Down
2 changes: 1 addition & 1 deletion shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"android:bundle:prod": "curl -o ~/Desktop/bundle.prod.js 'http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=io.keybase.ossifrage&modulesOnly=false&runModule=true'",
"android:bundle:profile": "NODE_OPTIONS=--no-experimental-fetch npx react-native profile-hermes ~/Desktop/",
"android:clean": "cd android; rm -rf build; rm -rf app/.cxx ; ./gradlew clean",
"android:debug": "NODE_ENV=development npx expo run:android --variant debug",
"android:debug": "NODE_ENV=development ./scripts/android-debug.sh",
"android:gobuild": "./react-native/gobuild.sh android && yarn postinstall",
"android:jsbundle": "mkdir -p android/dist && react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/dist/main.jsbundle --sourcemap-output android/dist/main.jsbundle.sourcemap",
"android:logs:clear": "adb logcat -b all -c",
Expand Down
21 changes: 16 additions & 5 deletions shared/router-v2/screen-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,22 @@ const TabScreenWrapper = ({children}: {children: React.ReactNode}) => {
)
}

const StackScreenWrapper = ({children}: {children: React.ReactNode}) => (
<Kb.Box2 direction="vertical" fullWidth={true} style={styles.tabScreen}>
{children}
</Kb.Box2>
)
const StackScreenWrapper = ({children}: {children: React.ReactNode}) => {
// Android targets SDK 35+ which enforces edge-to-edge, so content draws under
// the system nav bar unless we apply the bottom inset ourselves.
if (isAndroid) {
return (
<RNScreensSafeAreaView edges={{bottom: true}} style={styles.tabScreen}>
{children}
</RNScreensSafeAreaView>
)
}
return (
<Kb.Box2 direction="vertical" fullWidth={true} style={styles.tabScreen}>
{children}
</Kb.Box2>
)
}

const desktopMakeLayout = (
isModal: boolean,
Expand Down
35 changes: 35 additions & 0 deletions shared/scripts/android-debug.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Build + install + launch the Android debug app, skipping `expo run:android`'s
# dev-client deep link (keybase://expo-development-client/?url=...). That link is
# only handled by expo-dev-client, which this app does not bundle, so it 404s.
# A plain debug build connects to Metro at localhost:8081 via `adb reverse`, so
# we launch MainActivity directly instead.
set -euo pipefail

cd "$(dirname "$0")/.." # shared/

PKG=io.keybase.ossifrage
APK=android/app/build/outputs/apk/debug/app-debug.apk

# Build debug APK.
(cd android && ./gradlew :app:assembleDebug)

# Install on the connected device/emulator.
adb install -r -d "$APK"

# Route the device's localhost:8081 to the host Metro server.
adb reverse tcp:8081 tcp:8081

# Start Metro in the background; kill it when this script exits.
npx expo start &
METRO_PID=$!
trap 'kill "$METRO_PID" 2>/dev/null || true' EXIT

# Wait for Metro to answer before launching, else the app red-screens.
until curl -fs http://localhost:8081/status >/dev/null 2>&1; do sleep 1; done

# Launch the app directly (no deep link).
adb shell am start -n "$PKG/.MainActivity"

# Keep Metro in the foreground.
wait "$METRO_PID"