Skip to content

Commit

Permalink
Performance optimization (#1676)
Browse files Browse the repository at this point in the history
* upgrade sentry to support profiling monitoring

* remove console logs in production builds

* feeds tab bar and bottom bar animation centralized

* refactor FeedPage out of Home

* add script to start in production mode

* move FAB inner to reanimated

* move FABInner back to `Animated` RN animation

* add perf commands

* add testing with Maestro and perf with Flashlight

* fix merge conflicts

* fix resourceClass name in eas.json

* fix onEndReachedThreshold in Feed

* memoize styles

* go back to old styling for LoadLatestBtn

* remove reanimated code from useMinimalShellMode

* move shell animations to hook/reanimated for perf

* fix empty state issue

* make shell animation feel smoother

* make shell animation more smooth

* run animation with autorun

* specify keys for tab bar properly

* remove comments

* remove already imported dep

* fix lint

* add testing instructions

* mock sentry-expo for jest

* fix jest mocks

* Fix the load-latest button on desktop and tablet

* Fix: don't move the FAB in tablet mode

* Fix type error

* Fix tabs bar positioning on tablet

* Fix types

---------

Co-authored-by: Paul Frazee <[email protected]>
  • Loading branch information
ansh and pfrazee authored Oct 14, 2023
1 parent 9042f50 commit 8e9cf18
Show file tree
Hide file tree
Showing 24 changed files with 584 additions and 374 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,7 @@ ios/
.env.*

# Firebase (Android) Google services
google-services.json
google-services.json

# Performance results (Flashlight)
.perf/
77 changes: 77 additions & 0 deletions __e2e__/maestro/scroll.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# flow.yaml

appId: xyz.blueskyweb.app
---
- launchApp
# Login
# - runFlow:
# when:
# - tapOn: "Sign In"
# - tapOn: "Username or email address"
# - inputText: "ansh.bsky.team"
# - tapOn: "Password"
# - inputText: "PASSWORd"
# - tapOn: "Next"
# Allow notifications if popup is visible
# - runFlow:
# when:
# visible: "Notifications"
# commands:
# - tapOn: "Allow"
# Scroll in main feed
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- "scroll"
# Swipe between feeds
- swipe:
direction: "LEFT"
- swipe:
direction: "LEFT"
- swipe:
direction: "LEFT"
- swipe:
direction: "RIGHT"
- swipe:
direction: "RIGHT"
- swipe:
direction: "RIGHT"
# Go to Notifications
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn: "Notifications"
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- swipe:
direction: "DOWN" # Make header visible
# Go to Feeds tab
- tapOn:
id: "viewHeaderDrawerBtn"
- tapOn: "Feeds"
- scrollUntilVisible:
element: "Discover"
direction: UP
- tapOn: "Discover"
- waitForAnimationToEnd
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- "scroll"
# Click on post
- tapOn:
id: "postText"
index: 0
- "scroll"
- "scroll"
- "scroll"
- "scroll"
- "scroll"

10 changes: 10 additions & 0 deletions __mocks__/sentry-expo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
jest.mock('sentry-expo', () => ({
init: () => jest.fn(),
Native: {
ReactNativeTracing: jest.fn().mockImplementation(() => ({
start: jest.fn(),
stop: jest.fn(),
})),
ReactNavigationInstrumentation: jest.fn(),
},
}))
5 changes: 5 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ module.exports = function (api) {
],
'react-native-reanimated/plugin', // NOTE: this plugin MUST be last
],
env: {
production: {
plugins: ['transform-remove-console'],
},
},
}
}
14 changes: 14 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Testing instructions

### Using Maestro E2E tests
1. Install Maestro by following [these instuctions](https://maestro.mobile.dev/getting-started/installing-maestro). This will help us run the E2E tests.
2. You can write Maestro tests in `__e2e__/maestro` directory by creating a new `.yaml` file or by modifying an existing one.
3. You can also use [Maestro Studio](https://maestro.mobile.dev/getting-started/maestro-studio) which automatically generates commands by recording your actions on the app. Therefore, you can create realistic tests without having to manually write any code. Use the `maestro studio` command to start recording your actions.


### Using Flashlight for Performance Testing
1. Make sure Maestro is installed (optional: only for auomated testing) by following the instructions above
2. Install Flashlight by following [these instructions](https://docs.flashlight.dev/)
3. The simplest way to get started is by running `yarn perf:measure` which will run a live preview of the performance test results. You can [see a demo here](https://github.com/bamlab/flashlight/assets/4534323/4038a342-f145-4c3b-8cde-17949bf52612)
4. The `yarn perf:test:measure` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml` and give the results in `.perf/results.json` which can be viewed by running `yarn:perf:results`
5. You can also run your own tests by running `yarn perf:test <path_to_test>` where `<path_to_test>` is the path to your test file. For example, `yarn perf:test __e2e__/maestro/scroll.yaml` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml`.
8 changes: 4 additions & 4 deletions eas.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@
"distribution": "internal",
"ios": {
"simulator": true,
"resourceClass": "m-large"
"resourceClass": "large"
},
"channel": "development"
},
"development-device": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"resourceClass": "m-large"
"resourceClass": "large"
},
"channel": "development"
},
"preview": {
"distribution": "internal",
"ios": {
"resourceClass": "m-large"
"resourceClass": "large"
},
"channel": "preview"
},
"production": {
"ios": {
"resourceClass": "m-large"
"resourceClass": "large"
},
"channel": "production"
},
Expand Down
11 changes: 11 additions & 0 deletions jest/jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,14 @@ jest.mock('lande', () => ({
__esModule: true, // this property makes it work
default: jest.fn().mockReturnValue([['eng']]),
}))

jest.mock('sentry-expo', () => ({
init: () => jest.fn(),
Native: {
ReactNativeTracing: jest.fn().mockImplementation(() => ({
start: jest.fn(),
stop: jest.fn(),
})),
ReactNavigationInstrumentation: jest.fn(),
},
}))
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"web": "expo start --web",
"build-web": "expo export:web && node ./scripts/post-web-build.js && cp --verbose ./web-build/static/js/*.* ./bskyweb/static/js/",
"start": "expo start --dev-client",
"start:prod": "expo start --dev-client --no-dev --minify",
"clean-cache": "rm -rf node_modules/.cache/babel-loader/*",
"test": "jest --forceExit --testTimeout=20000 --bail",
"test-watch": "jest --watchAll",
Expand All @@ -22,6 +23,11 @@
"e2e:metro": "RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
"e2e:build": "detox build -c ios.sim.debug",
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all",
"perf:test": "maestro test",
"perf:test:run": "maestro test __e2e__/maestro/scroll.yaml",
"perf:test:measure": "flashlight test --bundleId xyz.blueskyweb.app --testCommand 'yarn perf:test' --duration 150000 --resultsFilePath .perf/results.json",
"perf:test:results": "flashlight report .perf/results.json",
"perf:measure": "flashlight measure",
"build:apk": "eas build -p android --profile dev-android-apk"
},
"dependencies": {
Expand Down Expand Up @@ -53,7 +59,7 @@
"@segment/analytics-react": "^1.0.0-rc1",
"@segment/analytics-react-native": "^2.10.1",
"@segment/sovran-react-native": "^0.4.5",
"@sentry/react-native": "5.5.0",
"@sentry/react-native": "5.10.0",
"@tanstack/react-query": "^4.33.0",
"@tiptap/core": "^2.0.0-beta.220",
"@tiptap/extension-document": "^2.0.0-beta.220",
Expand All @@ -71,6 +77,7 @@
"@zxing/text-encoding": "^0.9.0",
"array.prototype.findlast": "^1.2.3",
"await-lock": "^2.2.2",
"babel-plugin-transform-remove-console": "^6.9.4",
"base64-js": "^1.5.1",
"bcp-47-match": "^2.0.3",
"email-validator": "^2.0.4",
Expand Down Expand Up @@ -148,7 +155,7 @@
"react-native-web-linear-gradient": "^1.1.2",
"react-responsive": "^9.0.2",
"rn-fetch-blob": "^0.12.0",
"sentry-expo": "~7.0.0",
"sentry-expo": "~7.0.1",
"tippy.js": "^6.3.7",
"tlds": "^1.234.0",
"zeego": "^1.6.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js b/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js
index 7e0b4cd..3fd7406 100644
index 7e0b4cd..177454c 100644
--- a/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js
+++ b/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js
@@ -3,6 +3,8 @@ import { LogBox } from 'react-native';
Expand All @@ -12,3 +12,4 @@ index 7e0b4cd..3fd7406 100644
+ } catch (e) {}
}
//# sourceMappingURL=ignorerequirecyclelogs.js.map
\ No newline at end of file
66 changes: 45 additions & 21 deletions src/lib/hooks/useMinimalShellMode.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,60 @@
import React from 'react'
import {autorun} from 'mobx'
import {useStores} from 'state/index'
import {Animated} from 'react-native'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {
Easing,
interpolate,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'

export function useMinimalShellMode() {
const store = useStores()
const minimalShellInterp = useAnimatedValue(0)
const footerMinimalShellTransform = {
opacity: Animated.subtract(1, minimalShellInterp),
transform: [{translateY: Animated.multiply(minimalShellInterp, 50)}],
}
const minimalShellInterp = useSharedValue(0)
const footerMinimalShellTransform = useAnimatedStyle(() => {
return {
opacity: interpolate(minimalShellInterp.value, [0, 1], [1, 0]),
transform: [
{translateY: interpolate(minimalShellInterp.value, [0, 1], [0, 25])},
],
}
})
const headerMinimalShellTransform = useAnimatedStyle(() => {
return {
opacity: interpolate(minimalShellInterp.value, [0, 1], [1, 0]),
transform: [
{translateY: interpolate(minimalShellInterp.value, [0, 1], [0, -25])},
],
}
})
const fabMinimalShellTransform = useAnimatedStyle(() => {
return {
transform: [
{translateY: interpolate(minimalShellInterp.value, [0, 1], [-44, 0])},
],
}
})

React.useEffect(() => {
return autorun(() => {
if (store.shell.minimalShellMode) {
Animated.timing(minimalShellInterp, {
toValue: 1,
duration: 150,
useNativeDriver: true,
isInteraction: false,
}).start()
minimalShellInterp.value = withTiming(1, {
duration: 125,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
})
} else {
Animated.timing(minimalShellInterp, {
toValue: 0,
duration: 150,
useNativeDriver: true,
isInteraction: false,
}).start()
minimalShellInterp.value = withTiming(0, {
duration: 125,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
})
}
})
}, [minimalShellInterp, store])
}, [minimalShellInterp, store.shell.minimalShellMode])

This comment has been minimized.

Copy link
@gaearon

gaearon Nov 7, 2023

Collaborator

Why did we change this back to store.shell.minimalShellMode? I think this might have undone my fix from #1691.


return {footerMinimalShellTransform}
return {
footerMinimalShellTransform,
headerMinimalShellTransform,
fabMinimalShellTransform,
}
}
Loading

0 comments on commit 8e9cf18

Please sign in to comment.