diff --git a/packages/core-mobile/ios/Podfile.lock b/packages/core-mobile/ios/Podfile.lock index 805796c21e..cfaf2d9b1d 100644 --- a/packages/core-mobile/ios/Podfile.lock +++ b/packages/core-mobile/ios/Podfile.lock @@ -1288,7 +1288,7 @@ PODS: - React-Core - RNFS (2.20.0): - React-Core - - RNGestureHandler (2.20.0): + - RNGestureHandler (2.14.1): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1843,7 +1843,7 @@ SPEC CHECKSUMS: RNFBMessaging: 2dd7ef3e3eca8ec62599f0f11b739e6f75c57cd2 RNFlashList: 83a272ae1c35b08a02490f4d1503631fb64b3dd8 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: bb81850add626ddd265294323310fec6e861c96b + RNGestureHandler: 15c6ef51acba34c49ff03003806cf5dd6098f383 RNGoogleSignin: 9e68b9bcc3888219357924e32ee563624745647d RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333 diff --git a/packages/core-mobile/package.json b/packages/core-mobile/package.json index 86a1a1020b..ea2d7f4df5 100644 --- a/packages/core-mobile/package.json +++ b/packages/core-mobile/package.json @@ -137,7 +137,7 @@ "react-native-device-info": "13.0.0", "react-native-fast-image": "8.6.3", "react-native-fs": "2.20.0", - "react-native-gesture-handler": "2.20.0", + "react-native-gesture-handler": "2.14.1", "react-native-graph": "1.1.0", "react-native-haptic-feedback": "2.0.3", "react-native-inappbrowser-reborn": "3.7.0", diff --git a/packages/k2-alpine/package.json b/packages/k2-alpine/package.json index 0b58f61eea..1aaaeb0cfb 100644 --- a/packages/k2-alpine/package.json +++ b/packages/k2-alpine/package.json @@ -17,17 +17,21 @@ "postinstall": "node_modules/.bin/patch-package" }, "dependencies": { + "@react-native-masked-view/masked-view": "0.3.0", "dripsy": "4.3.7", "expo": "50.0.21", "expo-blur": "12.9.2", "expo-font": "11.10.3", + "expo-image": "1.10.6", "expo-linear-gradient": "12.7.2", "expo-splash-screen": "0.27.6", "expo-status-bar": "1.12.1", "react": "18.3.1", "react-native": "0.73.7", "react-native-dialog": "9.3.0", + "react-native-gesture-handler": "2.14.1", "react-native-reanimated": "3.6.2", + "react-native-reanimated-carousel": "v4.0.0-canary.22", "react-native-svg": "15.7.1" }, "peerDependencies": { diff --git a/packages/k2-alpine/src/assets/avatars/avatar-1.jpeg b/packages/k2-alpine/src/assets/avatars/avatar-1.jpeg new file mode 100644 index 0000000000..4a0d38d0fb Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-1.jpeg differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-2.jpeg b/packages/k2-alpine/src/assets/avatars/avatar-2.jpeg new file mode 100644 index 0000000000..b054d9ad3e Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-2.jpeg differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-3.jpeg b/packages/k2-alpine/src/assets/avatars/avatar-3.jpeg new file mode 100644 index 0000000000..ab3ed1be9e Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-3.jpeg differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-4.jpeg b/packages/k2-alpine/src/assets/avatars/avatar-4.jpeg new file mode 100644 index 0000000000..f1e63907bc Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-4.jpeg differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-5.jpeg b/packages/k2-alpine/src/assets/avatars/avatar-5.jpeg new file mode 100644 index 0000000000..ba4e4e02dd Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-5.jpeg differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-6.png b/packages/k2-alpine/src/assets/avatars/avatar-6.png new file mode 100644 index 0000000000..35afc7e6eb Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-6.png differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-7.png b/packages/k2-alpine/src/assets/avatars/avatar-7.png new file mode 100644 index 0000000000..84b544529a Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-7.png differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-8.png b/packages/k2-alpine/src/assets/avatars/avatar-8.png new file mode 100644 index 0000000000..530a2c8912 Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-8.png differ diff --git a/packages/k2-alpine/src/assets/avatars/avatar-9.jpeg b/packages/k2-alpine/src/assets/avatars/avatar-9.jpeg new file mode 100644 index 0000000000..d2652f9d25 Binary files /dev/null and b/packages/k2-alpine/src/assets/avatars/avatar-9.jpeg differ diff --git a/packages/k2-alpine/src/components/Avatar/Avatar.stories.tsx b/packages/k2-alpine/src/components/Avatar/Avatar.stories.tsx new file mode 100644 index 0000000000..cf7fc4c1c4 --- /dev/null +++ b/packages/k2-alpine/src/components/Avatar/Avatar.stories.tsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react' +import { Switch } from 'react-native' +import { ScrollView, Text, View } from '../Primitives' +import { useTheme } from '../..' +import AvatarSelector from './AvatarSelector' +import { Avatar } from './Avatar' + +export default { + title: 'Avatar' +} + +export const All = (): JSX.Element => { + const { theme } = useTheme() + + const [hasBlur, setHasBlur] = useState(true) + + const AVATARS = [ + require('../../assets/avatars/avatar-1.jpeg'), + require('../../assets/avatars/avatar-2.jpeg'), + require('../../assets/avatars/avatar-3.jpeg'), + require('../../assets/avatars/avatar-4.jpeg'), + require('../../assets/avatars/avatar-5.jpeg'), + require('../../assets/avatars/avatar-6.png'), + require('../../assets/avatars/avatar-7.png'), + require('../../assets/avatars/avatar-8.png'), + require('../../assets/avatars/avatar-9.jpeg'), + { + uri: 'https://miro.medium.com/v2/resize:fit:1256/format:webp/1*xm2-adeU3YD4MsZikpc5UQ.png' + }, + { + uri: 'https://www.cnet.com/a/img/resize/7589227193923c006f9a7fd904b77bc898e105ff/hub/2021/11/29/f566750f-79b6-4be9-9c32-8402f58ba0ef/richerd.png?auto=webp&width=768' + }, + { + uri: 'https://i.seadn.io/s/raw/files/a9cb8c2298a64819a3036083818d0447.jpg?auto=format&dpr=1&w=1000' + }, + { + uri: 'https://i.seadn.io/gcs/files/441e674e79460fc975d976465bb3634d.png?auto=format&dpr=1&w=1000' + }, + { + uri: 'https://www.svgrepo.com/show/19461/url-link.svg' + } + ].map((avatar, index) => { + return { id: index.toString(), source: avatar } + }) + + const [selectedAvatarId, setSelectedAvatarId] = useState( + AVATARS[0]?.id + ) + + const handleSelect = (id: string): void => { + setSelectedAvatarId(id) + } + + const backgroundColor = theme.colors.$surfacePrimary + + return ( + + + + Blur on + + + + + avatar.id === selectedAvatarId)?.source + } + size="large" + hasBlur={hasBlur} + /> + + + + + ) +} diff --git a/packages/k2-alpine/src/components/Avatar/Avatar.tsx b/packages/k2-alpine/src/components/Avatar/Avatar.tsx new file mode 100644 index 0000000000..e979cf49dc --- /dev/null +++ b/packages/k2-alpine/src/components/Avatar/Avatar.tsx @@ -0,0 +1,115 @@ +import React, { useEffect } from 'react' +import { ImageSourcePropType, Platform, ViewStyle } from 'react-native' +import Animated, { + Easing, + useAnimatedStyle, + useSharedValue, + withTiming +} from 'react-native-reanimated' +import { BlurView } from 'expo-blur' +import { View } from '../Primitives' +import { useTheme } from '../..' +import { HexagonImageView, HexagonBorder } from './HexagonImageView' + +export const Avatar = ({ + source, + size, + isSelected, + isPressed, + hasBlur, + style, + backgroundColor +}: { + source: ImageSourcePropType + size: number | 'small' | 'large' + backgroundColor: string + isSelected?: boolean + isPressed?: boolean + hasBlur?: boolean + style?: ViewStyle +}): JSX.Element => { + const { theme } = useTheme() + + const height = typeof size === 'number' ? size : size === 'small' ? 90 : 150 + + const pressedAnimation = useSharedValue(1) + const pressedAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{ scale: pressedAnimation.value }] + })) + // to cancel out the blur effect on the backgroundColor, we need to use a darker background color for the blur view + const surfacePrimaryBlurBgMap = theme.isDark + ? { + [theme.colors.$surfacePrimary]: + Platform.OS === 'ios' ? '#050506' : '#0a0a0b', + [theme.colors.$surfaceSecondary]: + Platform.OS === 'ios' ? '#37373f' : '#373743', + [theme.colors.$surfaceTertiary]: + Platform.OS === 'ios' ? '#1A1A1C' : '#1C1C1F' + } + : { + [theme.colors.$surfacePrimary]: undefined, + [theme.colors.$surfaceSecondary]: undefined, + [theme.colors.$surfaceTertiary]: + Platform.OS === 'ios' ? '#8b8b8c' : '#79797c' + } + + useEffect(() => { + pressedAnimation.value = withTiming(isPressed ? 0.95 : 1, { + duration: 150, + easing: Easing.inOut(Easing.ease) + }) + }, [isPressed, pressedAnimation]) + + return ( + + {hasBlur === true && ( + + + + + )} + + + + ) +} + +const BLURAREA_INSET = 50 diff --git a/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx b/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx new file mode 100644 index 0000000000..e3e266897d --- /dev/null +++ b/packages/k2-alpine/src/components/Avatar/AvatarSelector.tsx @@ -0,0 +1,96 @@ +import { Dimensions, ImageSourcePropType } from 'react-native' +import React, { useMemo, useState } from 'react' +import Carousel from 'react-native-reanimated-carousel' +import { Pressable } from '../Primitives' +import { Avatar } from './Avatar' + +const AvatarSelector = ({ + avatars, + selectedId, + onSelect, + backgroundColor +}: { + avatars: { id: string; source: ImageSourcePropType }[] + selectedId?: string + onSelect?: (id: string) => void + backgroundColor: string +}): JSX.Element => { + const data = useMemo(() => { + // we should always have an even number of avatars, due to infinite scrolling + two avatars per column + if (avatars.length % 2 === 0) { + return avatars + } else { + return [...avatars, ...avatars] + } + }, [avatars]) + const [pressedIndex, setPressedIndex] = useState() + + const handlePressIn = (index: number): void => { + setPressedIndex(index) + } + + const handlePressOut = (index: number): void => { + if (pressedIndex === index) { + setPressedIndex(undefined) + } + } + + const handleSelect = (index: number): void => { + if (data[index]?.id === undefined) { + return + } + + onSelect?.(data[index].id) + } + + const renderItem = ({ + item, + index + }: { + item: { id: string; source: ImageSourcePropType } + index: number + }): JSX.Element => { + return ( + handlePressIn(index)} + onPressOut={() => handlePressOut(index)} + onPress={() => handleSelect(index)}> + + + ) + } + + return ( + + ) +} + +const configuration = { + avatarWidth: 90, + spacing: 6 +} + +const SCREEN_WIDTH = Dimensions.get('window').width + +export default AvatarSelector diff --git a/packages/k2-alpine/src/components/Avatar/HexagonImageView.tsx b/packages/k2-alpine/src/components/Avatar/HexagonImageView.tsx new file mode 100644 index 0000000000..dc3ec13883 --- /dev/null +++ b/packages/k2-alpine/src/components/Avatar/HexagonImageView.tsx @@ -0,0 +1,213 @@ +import MaskedView from '@react-native-masked-view/masked-view' +import React, { useEffect, useState } from 'react' +import { ImageSourcePropType, Platform, ViewStyle } from 'react-native' +import Animated, { + Easing, + interpolateColor, + useAnimatedProps, + useAnimatedStyle, + useSharedValue, + withDelay, + withRepeat, + withTiming +} from 'react-native-reanimated' +import Svg, { Path } from 'react-native-svg' +import { Image } from 'expo-image' +import { alpha } from '../../utils' +import { + colors, + darkModeColors, + lightModeColors +} from '../../theme/tokens/colors' +import { useTheme } from '../..' + +export const HexagonImageView = ({ + source, + height, + backgroundColor, + isSelected, + hasLoading = false +}: { + source: ImageSourcePropType + height: number + backgroundColor: string + isSelected?: boolean + hasLoading?: boolean +}): JSX.Element => { + const { theme } = useTheme() + const selectedAnimation = useSharedValue(0) + const selectedAnimatedStyle = useAnimatedStyle(() => ({ + opacity: selectedAnimation.value + })) + const [isLoading, setIsLoading] = useState(false) + + const handleLoadStart = (): void => { + setIsLoading(true) + } + + const handleLoadEnd = (): void => { + setIsLoading(false) + } + + useEffect(() => { + selectedAnimation.value = withTiming(isSelected ? 1 : 0, { + duration: 200, + easing: Easing.inOut(Easing.ease) + }) + }, [isSelected, selectedAnimation]) + + return ( + + + + }> + + {isLoading && ( + + )} + + + + + ) +} + +export const HexagonBorder = ({ height }: { height: number }): JSX.Element => { + const { theme } = useTheme() + + return ( + + + + ) +} + +const Arrow = ({ isSelected }: { isSelected?: boolean }): JSX.Element => { + const { theme } = useTheme() + const arrowAnimation = useSharedValue( + Platform.OS === 'ios' ? arrowPath.length : 0 + ) + const arrowAnimatedProps = useAnimatedProps(() => ({ + strokeDashoffset: arrowAnimation.value + })) + + useEffect(() => { + // arrowAnimimation doesn't work on Android + if (Platform.OS === 'ios') { + arrowAnimation.value = isSelected + ? withTiming(0, { + duration: 400, + easing: Easing.inOut(Easing.ease) + }) + : arrowPath.length + } + }, [isSelected, arrowAnimation]) + + return ( + + + + ) +} + +const AnimatedPath = Animated.createAnimatedComponent(Path) + +const arrowPath = { + path: 'M2 10L9.5 17.5L25.5 1.5', + length: 34, + viewBox: '0 0 27 19', + width: 27, + height: 19 +} + +const hexagonPath = { + path: ` + M53 3.9282C60.4256 -0.358983 69.5744 -0.358984 77 3.9282L117.952 27.5718C125.378 31.859 129.952 39.782 129.952 48.3564V95.6436C129.952 104.218 125.378 112.141 117.952 116.428L77 140.072C69.5744 144.359 60.4256 144.359 53 140.072L12.0481 116.428C4.62247 112.141 0.0480957 104.218 0.0480957 95.6436V48.3564C0.0480957 39.782 4.62247 31.859 12.0481 27.5718L53 3.9282Z +`, + viewBox: '0 0 130 144' +} + +const LoadingView = ({ style }: { style: ViewStyle }): JSX.Element => { + const backgroundAnimation = useSharedValue(0) + const { theme } = useTheme() + + useEffect(() => { + backgroundAnimation.value = withDelay( + Math.random() * 1000, + withRepeat(withTiming(1, { duration: 1000 }), -1, true) + ) + }, [backgroundAnimation]) + + const animatedStyle = useAnimatedStyle(() => { + const backgroundColor = interpolateColor( + backgroundAnimation.value, + [0, 1], + [theme.colors.$surfacePrimary, theme.colors.$surfaceSecondary] + ) + + return { + backgroundColor + } + }) + + return +} diff --git a/yarn.lock b/yarn.lock index 7c3fd613ef..7fae12b622 100644 --- a/yarn.lock +++ b/yarn.lock @@ -343,7 +343,7 @@ __metadata: react-native-device-info: 13.0.0 react-native-fast-image: 8.6.3 react-native-fs: 2.20.0 - react-native-gesture-handler: 2.20.0 + react-native-gesture-handler: 2.14.1 react-native-graph: 1.1.0 react-native-haptic-feedback: 2.0.3 react-native-inappbrowser-reborn: 3.7.0 @@ -519,6 +519,7 @@ __metadata: "@react-native-async-storage/async-storage": 1.24.0 "@react-native-community/datetimepicker": 8.2.0 "@react-native-community/slider": 4.5.3 + "@react-native-masked-view/masked-view": 0.3.0 "@rushstack/eslint-patch": 1.5.1 "@storybook/addon-ondevice-actions": 7.6.20 "@storybook/addon-ondevice-backgrounds": 7.6.20 @@ -533,6 +534,7 @@ __metadata: expo: 50.0.21 expo-blur: 12.9.2 expo-font: 11.10.3 + expo-image: 1.10.6 expo-linear-gradient: 12.7.2 expo-splash-screen: 0.27.6 expo-status-bar: 1.12.1 @@ -542,7 +544,9 @@ __metadata: react-dom: 18.3.1 react-native: 0.73.7 react-native-dialog: 9.3.0 + react-native-gesture-handler: 2.14.1 react-native-reanimated: 3.6.2 + react-native-reanimated-carousel: v4.0.0-canary.22 react-native-safe-area-context: 4.11.0 react-native-svg: 15.7.1 react-native-svg-transformer: 1.5.0 @@ -8890,6 +8894,16 @@ __metadata: languageName: node linkType: hard +"@react-native-masked-view/masked-view@npm:0.3.0": + version: 0.3.0 + resolution: "@react-native-masked-view/masked-view@npm:0.3.0" + peerDependencies: + react: ">=16" + react-native: ">=0.57" + checksum: 77cf06947640abf5e7bf8c63ba6ee79f7cb409f4a40d3a0242790ff3f61d1b08c4f51373668b0e0e0bb2f60ffe3e4d2e4ea26c7358874f94ac9317841fce1751 + languageName: node + linkType: hard + "@react-native-menu/menu@npm:1.1.3": version: 1.1.3 resolution: "@react-native-menu/menu@npm:1.1.3" @@ -17578,6 +17592,17 @@ __metadata: languageName: node linkType: hard +"expo-image@npm:1.10.6": + version: 1.10.6 + resolution: "expo-image@npm:1.10.6" + dependencies: + "@react-native/assets-registry": ~0.73.1 + peerDependencies: + expo: "*" + checksum: c847caf52e507d75a8d66a2f1f56d0c05669fe565510a9c24e4a5644e521a638e7171cfa67a4af89cc2db20f6d6a9cb25ee75471a9b2a4e167b91eb48d8eed35 + languageName: node + linkType: hard + "expo-keep-awake@npm:~12.8.2": version: 12.8.2 resolution: "expo-keep-awake@npm:12.8.2" @@ -25886,18 +25911,19 @@ __metadata: languageName: node linkType: hard -"react-native-gesture-handler@npm:2.20.0": - version: 2.20.0 - resolution: "react-native-gesture-handler@npm:2.20.0" +"react-native-gesture-handler@npm:2.14.1": + version: 2.14.1 + resolution: "react-native-gesture-handler@npm:2.14.1" dependencies: "@egjs/hammerjs": ^2.0.17 hoist-non-react-statics: ^3.3.0 invariant: ^2.2.4 + lodash: ^4.17.21 prop-types: ^15.7.2 peerDependencies: react: "*" react-native: "*" - checksum: f573bc3717ae0209ff30bf62b95b3c7f11bd97f4797090211bce416c250388f55d1995aac0a7f1bbc99b06223ea64cbeae8d4ef88dcb8c877201b49163ea0e4b + checksum: a037e8c5a88a9fc79c283f3064d7653ec8615cb05fc62622eaccb5f3db489ede9c3a0685b7aad210c7efabfd8f5aa34e4f19204318dfda64c8829266d78e0cae languageName: node linkType: hard @@ -26164,6 +26190,18 @@ __metadata: languageName: node linkType: hard +"react-native-reanimated-carousel@npm:v4.0.0-canary.22": + version: 4.0.0-canary.22 + resolution: "react-native-reanimated-carousel@npm:4.0.0-canary.22" + peerDependencies: + react: ">=18.0.0" + react-native: ">=0.70.3" + react-native-gesture-handler: ">=2.9.0" + react-native-reanimated: ">=3.0.0" + checksum: 52d4f3c66ea46b6309518bb98547b48302d9852d6ffd7db3f470e3edf342cc289d206c015cf9ba0c31b768e1a15b48f5170b4de678bdcb5f7a29791ef948bd0a + languageName: node + linkType: hard + "react-native-reanimated@npm:3.6.2": version: 3.6.2 resolution: "react-native-reanimated@npm:3.6.2"