From 9aff2597cf6735960299d98bf2b4d650aac9cb35 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Fri, 6 Oct 2023 18:02:22 -0700 Subject: [PATCH 01/21] Bump build revision --- app.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.config.js b/app.config.js index a1477a8aea..82d0d5a0ca 100644 --- a/app.config.js +++ b/app.config.js @@ -19,7 +19,7 @@ module.exports = function () { backgroundColor: '#ffffff', }, ios: { - buildNumber: '1', + buildNumber: '2', supportsTablet: false, bundleIdentifier: 'xyz.blueskyweb.app', config: { @@ -43,7 +43,7 @@ module.exports = function () { backgroundColor: '#ffffff', }, android: { - versionCode: 40, + versionCode: 41, adaptiveIcon: { foregroundImage: './assets/adaptive-icon.png', backgroundColor: '#ffffff', From c43a0d816000ce732b7b7732de9838ea78599f70 Mon Sep 17 00:00:00 2001 From: Toni Alatalo Date: Mon, 9 Oct 2023 21:48:57 +0300 Subject: [PATCH 02/21] Typo fix in README.md: "small about" -> "small amount" (#1639) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08e7aba28f..d53a3eb2f8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Get the app itself: This is a [React Native](https://reactnative.dev/) application, written in the TypeScript programming language. It builds on the `atproto` TypeScript packages (like [`@atproto/api`](https://www.npmjs.com/package/@atproto/api)), code for which is also on open source, but in [a different git repository](https://github.com/bluesky-social/atproto). -There is a small about of Go language source code (in `./bskyweb/`), for a web service that returns the React Native Web application. +There is a small amount of Go language source code (in `./bskyweb/`), for a web service that returns the React Native Web application. The [Build Instructions](./docs/builds.md) are a good place to get started with the app itself. From d85bbc1a2814d3982b5e23b1ff0790ab6d288b3b Mon Sep 17 00:00:00 2001 From: Raku <42936741+rakutek@users.noreply.github.com> Date: Tue, 10 Oct 2023 03:55:15 +0900 Subject: [PATCH 03/21] fix typo README.md (#1631) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d53a3eb2f8..4f7d00ebba 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This is a [React Native](https://reactnative.dev/) application, written in the T There is a small amount of Go language source code (in `./bskyweb/`), for a web service that returns the React Native Web application. -The [Build Instructions](./docs/builds.md) are a good place to get started with the app itself. +The [Build Instructions](./docs/build.md) are a good place to get started with the app itself. The Authenticated Transfer Protocol ("AT Protocol" or "atproto") is a decentralized social media protocol. You don't *need* to understand AT Protocol to work with this application, but it can help. Learn more at: From b12cfbe90b69d0ec1327c92a1e898be629ebf06b Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Tue, 10 Oct 2023 04:29:16 +0900 Subject: [PATCH 04/21] Fix typo in image.ts (#1638) --- src/state/models/media/image.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/models/media/image.ts b/src/state/models/media/image.ts index 10aef0ff48..c26f9b87c6 100644 --- a/src/state/models/media/image.ts +++ b/src/state/models/media/image.ts @@ -166,7 +166,7 @@ export class ImageModel implements Omit { async crop() { try { // NOTE - // on ios, react-native-image-cropper gives really bad quality + // on ios, react-native-image-crop-picker gives really bad quality // without specifying width and height. on android, however, the // crop stretches incorrectly if you do specify it. these are // both separate bugs in the library. we deal with that by From ce0f79600870d373bd8289c13f4efb44b253ffb1 Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 9 Oct 2023 22:29:56 +0100 Subject: [PATCH 05/21] Remove unnecessary opacity logic (#1646) --- src/view/com/lightbox/ImageViewing/index.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index bc2a8a4480..b6b321c4a9 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -71,7 +71,6 @@ function ImageViewing({ const imageList = useRef>(null) const [isScaled, setIsScaled] = useState(false) const [isDragging, setIsDragging] = useState(false) - const [opacity, setOpacity] = useState(1) const [currentImageIndex, setImageIndex] = useState(imageIndex) const [headerTranslate] = useState( () => new Animated.ValueXY(INITIAL_POSITION), @@ -100,12 +99,6 @@ function ImageViewing({ } } - const onRequestCloseEnhanced = () => { - setOpacity(0) - onRequestClose() - setTimeout(() => setOpacity(1), 0) - } - const onScroll = (event: NativeSyntheticEvent) => { const { nativeEvent: { @@ -162,14 +155,14 @@ function ImageViewing({ aria-modal accessibilityViewIsModal> - + {typeof HeaderComponent !== 'undefined' ? ( React.createElement(HeaderComponent, { imageIndex: currentImageIndex, }) ) : ( - + )} From 832b05b64a01319454ddcc3436496a492e4b6cef Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 9 Oct 2023 15:24:23 -0700 Subject: [PATCH 06/21] Bump package.json to 1.52 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a886bfcd01..cb334507ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky.app", - "version": "1.51.0", + "version": "1.52.0", "private": true, "scripts": { "prepare": "is-ci || husky install", From f452ce74f4fe5ed5aa215b4b18302623f435d0d2 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 10 Oct 2023 10:04:38 +0100 Subject: [PATCH 07/21] Refactor iOS lightbox to Reanimated (#1645) * Remove unnecessary transform logic * Switch iOS swipe-to-dimiss to Reanimated --- .../components/ImageItem/ImageItem.ios.tsx | 165 ++++++------------ 1 file changed, 56 insertions(+), 109 deletions(-) diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx index 598b18ed29..75e8b0e821 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx @@ -6,20 +6,25 @@ * */ -import React, {MutableRefObject, useCallback, useRef, useState} from 'react' +import React, {MutableRefObject, useCallback, useState} from 'react' import { - Animated, Dimensions, - ScrollView, StyleSheet, View, - NativeScrollEvent, NativeSyntheticEvent, NativeTouchEvent, TouchableWithoutFeedback, } from 'react-native' import {Image} from 'expo-image' +import Animated, { + interpolate, + runOnJS, + useAnimatedRef, + useAnimatedScrollHandler, + useAnimatedStyle, + useSharedValue, +} from 'react-native-reanimated' import {GestureType} from 'react-native-gesture-handler' import useImageDimensions from '../../hooks/useImageDimensions' @@ -31,10 +36,8 @@ const DOUBLE_TAP_DELAY = 300 const SWIPE_CLOSE_OFFSET = 75 const SWIPE_CLOSE_VELOCITY = 1 const SCREEN = Dimensions.get('screen') -const SCREEN_WIDTH = SCREEN.width -const SCREEN_HEIGHT = SCREEN.height -const MIN_ZOOM = 2 -const MAX_SCALE = 2 +const MAX_ORIGINAL_IMAGE_ZOOM = 2 +const MIN_DOUBLE_TAP_SCALE = 2 type Props = { imageSrc: ImageSource @@ -49,44 +52,42 @@ const AnimatedImage = Animated.createAnimatedComponent(Image) let lastTapTS: number | null = null const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => { - const scrollViewRef = useRef(null) + const scrollViewRef = useAnimatedRef() + const translationY = useSharedValue(0) const [loaded, setLoaded] = useState(false) const [scaled, setScaled] = useState(false) const imageDimensions = useImageDimensions(imageSrc) - const [translate, scale] = getImageTransform(imageDimensions, SCREEN) - const [scrollValueY] = useState(() => new Animated.Value(0)) - const maxScrollViewZoom = MAX_SCALE / (scale || 1) + const maxZoomScale = imageDimensions + ? (imageDimensions.width / SCREEN.width) * MAX_ORIGINAL_IMAGE_ZOOM + : 1 - const imageOpacity = scrollValueY.interpolate({ - inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], - outputRange: [0.5, 1, 0.5], + const animatedStyle = useAnimatedStyle(() => { + return { + opacity: interpolate( + translationY.value, + [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], + [0.5, 1, 0.5], + ), + } }) - const imagesStyles = getImageStyles(imageDimensions, translate, scale || 1) - const imageStylesWithOpacity = {...imagesStyles, opacity: imageOpacity} - const onScrollEndDrag = useCallback( - ({nativeEvent}: NativeSyntheticEvent) => { - const velocityY = nativeEvent?.velocity?.y ?? 0 - const currentScaled = nativeEvent?.zoomScale > 1 - - onZoom(currentScaled) - setScaled(currentScaled) - - if (!currentScaled && Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) { - onRequestClose() + const scrollHandler = useAnimatedScrollHandler({ + onScroll(e) { + translationY.value = e.zoomScale > 1 ? 0 : e.contentOffset.y + }, + onEndDrag(e) { + const velocityY = e.velocity?.y ?? 0 + const nextIsScaled = e.zoomScale > 1 + runOnJS(handleZoom)(nextIsScaled) + if (!nextIsScaled && Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) { + runOnJS(onRequestClose)() } }, - [onRequestClose, onZoom], - ) + }) - const onScroll = ({nativeEvent}: NativeSyntheticEvent) => { - const offsetY = nativeEvent?.contentOffset?.y ?? 0 - - if (nativeEvent?.zoomScale > 1) { - return - } - - scrollValueY.setValue(offsetY) + function handleZoom(nextIsScaled: boolean) { + onZoom(nextIsScaled) + setScaled(nextIsScaled) } const handleDoubleTap = useCallback( @@ -121,23 +122,21 @@ const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => { lastTapTS = nowTS } }, - [imageDimensions, scaled], + [imageDimensions, scaled, scrollViewRef], ) return ( - + onScroll={scrollHandler}> {(!loaded || !imageDimensions) && } { accessibilityLabel={imageSrc.alt} accessibilityHint=""> setLoaded(true)} /> - + ) } const styles = StyleSheet.create({ - listItem: { - width: SCREEN_WIDTH, - height: SCREEN_HEIGHT, - }, imageScrollContainer: { - height: SCREEN_HEIGHT, + height: SCREEN.height, + }, + listItem: { + width: SCREEN.width, + height: SCREEN.height, + }, + image: { + width: SCREEN.width, + height: SCREEN.height, }, }) @@ -191,7 +195,7 @@ const getZoomRectAfterDoubleTap = ( const zoom = Math.max( imageAspect / screenAspect, screenAspect / imageAspect, - MIN_ZOOM, + MIN_DOUBLE_TAP_SCALE, ) // Unlike in the Android version, we don't constrain the *max* zoom level here. // Instead, this is done in the ScrollView props so that it constraints pinch too. @@ -253,61 +257,4 @@ const getZoomRectAfterDoubleTap = ( } } -const getImageStyles = ( - image: ImageDimensions | null, - translate: {readonly x: number; readonly y: number} | undefined, - scale?: number, -) => { - if (!image?.width || !image?.height) { - return {width: 0, height: 0} - } - const transform = [] - if (translate) { - transform.push({translateX: translate.x}) - transform.push({translateY: translate.y}) - } - if (scale) { - // @ts-ignore TODO - is scale incorrect? might need to remove -prf - transform.push({scale}, {perspective: new Animated.Value(1000)}) - } - return { - width: image.width, - height: image.height, - transform, - } -} - -const getImageTransform = ( - image: ImageDimensions | null, - screen: ImageDimensions, -) => { - if (!image?.width || !image?.height) { - return [] as const - } - - const wScale = screen.width / image.width - const hScale = screen.height / image.height - const scale = Math.min(wScale, hScale) - const {x, y} = getImageTranslate(image, screen) - - return [{x, y}, scale] as const -} - -const getImageTranslate = ( - image: ImageDimensions, - screen: ImageDimensions, -): {x: number; y: number} => { - const getTranslateForAxis = (axis: 'x' | 'y'): number => { - const imageSize = axis === 'x' ? image.width : image.height - const screenSize = axis === 'x' ? screen.width : screen.height - - return (screenSize - imageSize) / 2 - } - - return { - x: getTranslateForAxis('x'), - y: getTranslateForAxis('y'), - } -} - export default React.memo(ImageItem) From 098f4b526ebbd62fd2517a55e46f1419f277f904 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 10 Oct 2023 10:08:04 -0700 Subject: [PATCH 08/21] Revert "Fix invite codes flash on desktop, use loading placeholder (#1591)" (#1656) This reverts commit 9278822088d212c9bee6a40a6a8b773bc482242d. --- src/state/models/me.ts | 8 ++-- src/view/com/modals/InviteCodes.tsx | 27 ----------- src/view/screens/Settings.tsx | 70 +++++++++++++--------------- src/view/shell/Drawer.tsx | 54 +++++++++++----------- src/view/shell/desktop/RightNav.tsx | 72 ++++++++++++----------------- 5 files changed, 89 insertions(+), 142 deletions(-) diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 8a7a4c851e..186e61cf6b 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -25,13 +25,13 @@ export class MeModel { savedFeeds: SavedFeedsModel notifications: NotificationsFeedModel follows: MyFollowsCache - invites: ComAtprotoServerDefs.InviteCode[] | null = [] + invites: ComAtprotoServerDefs.InviteCode[] = [] appPasswords: ComAtprotoServerListAppPasswords.AppPassword[] = [] lastProfileStateUpdate = Date.now() lastNotifsUpdate = Date.now() get invitesAvailable() { - return this.invites?.filter(isInviteAvailable).length || null + return this.invites.filter(isInviteAvailable).length } constructor(public rootStore: RootStoreModel) { @@ -180,9 +180,7 @@ export class MeModel { } catch (e) { this.rootStore.log.error('Failed to fetch user invite codes', e) } - if (this.invites) { - await this.rootStore.invitedUsers.fetch(this.invites) - } + await this.rootStore.invitedUsers.fetch(this.invites) } } diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx index 0cb0c56aae..09cfd4de79 100644 --- a/src/view/com/modals/InviteCodes.tsx +++ b/src/view/com/modals/InviteCodes.tsx @@ -26,33 +26,6 @@ export function Component({}: {}) { store.shell.closeModal() }, [store]) - if (store.me.invites === null) { - return ( - - - Error - - - An error occurred while loading invite codes. - - - -