From 3f73c89c5bf29b909f4d16d9fa4e801133f4b67f Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 10 May 2024 10:25:41 +0700 Subject: [PATCH 01/48] add icons --- packages/icons/components/Chair.tsx | 15 +++++++++++++++ packages/icons/components/Settings.tsx | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 packages/icons/components/Chair.tsx create mode 100644 packages/icons/components/Settings.tsx diff --git a/packages/icons/components/Chair.tsx b/packages/icons/components/Chair.tsx new file mode 100644 index 000000000..1024db1c3 --- /dev/null +++ b/packages/icons/components/Chair.tsx @@ -0,0 +1,15 @@ +import type { FC } from 'react'; +import { Path, Svg } from 'react-native-svg'; + +import type { IconProps } from './types'; + +export const Chair: FC = ({ size = 24, color = '#FFFFFF' }) => { + return ( + + + + ); +}; diff --git a/packages/icons/components/Settings.tsx b/packages/icons/components/Settings.tsx new file mode 100644 index 000000000..e5ba259f3 --- /dev/null +++ b/packages/icons/components/Settings.tsx @@ -0,0 +1,19 @@ +import type { FC } from 'react'; +import { Path, Svg } from 'react-native-svg'; + +import type { IconProps } from './types'; + +export const Settings: FC = ({ size = 24, color = '#FFFFFF' }) => { + return ( + + + + ); +}; + +export default Settings; From eb4106329e6945185864736fe2699a9105b89e0a Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 10 May 2024 10:26:22 +0700 Subject: [PATCH 02/48] adjust explorer --- apps/wallet/src/features/Explorer/Header.tsx | 68 +++++++++++ .../src/features/Explorer/HighlightItem.tsx | 22 ++++ .../src/features/Explorer/Highlights.tsx | 14 +++ .../src/features/Explorer/MissionItem.tsx | 81 +++++++++++++ apps/wallet/src/features/Explorer/index.tsx | 107 +++++------------- apps/wallet/src/features/Explorer/internal.ts | 26 +++++ packages/icons/index.ts | 2 + 7 files changed, 243 insertions(+), 77 deletions(-) create mode 100644 apps/wallet/src/features/Explorer/Header.tsx create mode 100644 apps/wallet/src/features/Explorer/HighlightItem.tsx create mode 100644 apps/wallet/src/features/Explorer/Highlights.tsx create mode 100644 apps/wallet/src/features/Explorer/MissionItem.tsx create mode 100644 apps/wallet/src/features/Explorer/internal.ts diff --git a/apps/wallet/src/features/Explorer/Header.tsx b/apps/wallet/src/features/Explorer/Header.tsx new file mode 100644 index 000000000..813d3ad38 --- /dev/null +++ b/apps/wallet/src/features/Explorer/Header.tsx @@ -0,0 +1,68 @@ +import { StyleSheet } from 'react-native'; +import { Hoverable, Text, View } from '@walless/gui'; +import { Eye, EyeOff, Search, Settings } from '@walless/icons'; +import { appState } from 'state/app'; +import { useTokens } from 'utils/hooks'; +import { useSnapshot } from 'valtio'; + +const Header = () => { + const { config } = useSnapshot(appState); + const { valuation } = useTokens(); + + return ( + + + Hi👋, your balance today: + + + {config.hideBalance ? ( + + ) : ( + + )} + + ${valuation.toFixed(2)} + + + + + + + + + + + + + ); +}; + +export default Header; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 10, + }, + tokenValuationContainer: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + tokenValuation: { + fontSize: 18, + color: '#ffffff', + }, + buttonContainer: { + flexDirection: 'row', + gap: 8, + }, + button: { + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#23303C', + borderRadius: 6, + paddingHorizontal: 10, + }, +}); diff --git a/apps/wallet/src/features/Explorer/HighlightItem.tsx b/apps/wallet/src/features/Explorer/HighlightItem.tsx new file mode 100644 index 000000000..7f55effe9 --- /dev/null +++ b/apps/wallet/src/features/Explorer/HighlightItem.tsx @@ -0,0 +1,22 @@ +import { View } from '@walless/gui'; +import { Image, StyleSheet } from 'react-native'; + +const HighlightItem = () => { + return ( + + + + ); +}; + +export default HighlightItem; + +const styles = StyleSheet.create({ + image: { + flex: 1, + width: '100%', + }, +}); diff --git a/apps/wallet/src/features/Explorer/Highlights.tsx b/apps/wallet/src/features/Explorer/Highlights.tsx new file mode 100644 index 000000000..5deb922d5 --- /dev/null +++ b/apps/wallet/src/features/Explorer/Highlights.tsx @@ -0,0 +1,14 @@ +import { Text, View } from '@walless/gui'; + +import HighlightItem from './HighlightItem'; + +const Highlights = () => { + return ( + + Today's Highlights + + + ); +}; + +export default Highlights; diff --git a/apps/wallet/src/features/Explorer/MissionItem.tsx b/apps/wallet/src/features/Explorer/MissionItem.tsx new file mode 100644 index 000000000..2f6c7d96b --- /dev/null +++ b/apps/wallet/src/features/Explorer/MissionItem.tsx @@ -0,0 +1,81 @@ +import type { FC } from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { + useAnimatedStyle, + withSpring, +} from 'react-native-reanimated'; +import { Button, Text, View } from '@walless/gui'; +import { Chair, InfoIcon, MissionBackground } from '@walless/icons'; + +interface MissionProps { + title: string; + colors?: string[]; + onPress?: () => void; +} + +const MissionItem: FC = ({ title, onPress, colors }) => { + return ( + + + + + + + + + + + + {title} + + + ); +}; + +export default MissionItem; + +const styles = StyleSheet.create({ + container: { + width: 120, + height: 105, + borderRadius: 10, + justifyContent: 'space-between', + paddingHorizontal: 6, + paddingVertical: 9, + marginHorizontal: 5, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + }, + iconContainer: { + backgroundColor: '#FFFFFF', + justifyContent: 'center', + alignItems: 'center', + width: 25, + height: 25, + borderRadius: 25, + }, + button: { + width: 60, + height: 25, + borderRadius: 6, + backgroundColor: '#ffffff', + }, + text: { + color: '#ffffff', + }, + textButton: { + color: '#23303C', + fontSize: 12, + }, + misstionBackground: { + position: 'absolute', + top: 0, + left: 0, + zIndex: -1, + }, +}); diff --git a/apps/wallet/src/features/Explorer/index.tsx b/apps/wallet/src/features/Explorer/index.tsx index 895109bd4..3738d77a6 100644 --- a/apps/wallet/src/features/Explorer/index.tsx +++ b/apps/wallet/src/features/Explorer/index.tsx @@ -1,19 +1,13 @@ import type { FC } from 'react'; -import { useState } from 'react'; import type { StyleProp, ViewStyle } from 'react-native'; -import { StyleSheet } from 'react-native'; -import Animated, { - useAnimatedRef, - useScrollViewOffset, -} from 'react-native-reanimated'; +import { FlatList, StyleSheet } from 'react-native'; import { View } from '@walless/gui'; import type { WidgetDocument } from '@walless/store'; -import { StackHeader } from 'components/StackContainer'; -import { mockWidgets } from 'state/widget'; -import { useSafeAreaInsets, useWidgets } from 'utils/hooks'; -import ExplorerSearchBar from './SearchBar'; -import ExplorerWidgetItem from './WidgetItem'; +import Header from './Header'; +import Highlights from './Highlights'; +import { missions } from './internal'; +import MissionItem from './MissionItem'; interface Props { style?: StyleProp; @@ -22,66 +16,27 @@ interface Props { onToggleDrawer?: () => void; } -export const ExplorerFeature: FC = ({ - style, - widgets = mockWidgets, - isHeaderActive = false, - onToggleDrawer, -}) => { - const insets = useSafeAreaInsets(); - const scrollRef = useAnimatedRef(); - const scrollOffset = useScrollViewOffset(scrollRef); - const [searchString, setSearchString] = useState(''); - const contentContainerStyle: ViewStyle = { - paddingBottom: insets.bottom, - }; - - const activeWidgets = useWidgets().map((widget) => widget._id); - - const onChangeSearch = (query: string) => { - setSearchString(query.toLowerCase()); - }; - - const filterWidgetsByName = (name: string) => { - if (!searchString.length) return true; - return name.toLowerCase().includes(searchString); - }; - +export const ExplorerFeature: FC = ({ style }) => { return ( - - {isHeaderActive && ( - - )} - +
+ { + let colors = ['#EC74A2', '#F4B999']; + if (index % 3 === 1) { + colors = ['#8253FF', '#D73EFF']; + } else if (index % 3 === 2) { + colors = ['#3263FF', '#45CFFF']; + } + + return ; + }} + horizontal showsVerticalScrollIndicator={false} - stickyHeaderIndices={[0]} - > - - - - {widgets - .filter((widget) => filterWidgetsByName(widget.name)) - .map((widget) => ( - - ))} - + /> + ); }; @@ -89,16 +44,14 @@ export const ExplorerFeature: FC = ({ export default ExplorerFeature; const styles = StyleSheet.create({ - searchBar: { - backgroundColor: '#19232c', - marginBottom: 8, - }, - searchInput: { - bottom: -8, - marginTop: 4, - marginHorizontal: 16, + container: { + gap: 16, + paddingHorizontal: 16, }, widgetItem: { marginHorizontal: 16, }, + flatList: { + flexGrow: 0, + }, }); diff --git a/apps/wallet/src/features/Explorer/internal.ts b/apps/wallet/src/features/Explorer/internal.ts new file mode 100644 index 000000000..319981c95 --- /dev/null +++ b/apps/wallet/src/features/Explorer/internal.ts @@ -0,0 +1,26 @@ +export const missions = [ + { + title: 'Add SMB widget', + point: 100, + }, + { + title: 'Invite 5 friends', + point: 100, + }, + { + title: 'Follow Twitter', + point: 100, + }, + { + title: 'Add SMB widget', + point: 100, + }, + { + title: 'Invite 5 friends', + point: 100, + }, + { + title: 'Follow Twitter', + point: 100, + }, +]; diff --git a/packages/icons/index.ts b/packages/icons/index.ts index 1a2de4df9..680203fb5 100644 --- a/packages/icons/index.ts +++ b/packages/icons/index.ts @@ -10,6 +10,7 @@ export { BlogCategory } from './components/BlogCategory'; export { Book } from './components/Book'; export { Bookmark } from './components/Bookmark'; export { Cart } from './components/Cart'; +export { Chair } from './components/Chair'; export { Chart } from './components/Chart'; export { Check } from './components/Check'; export { CheckCircle } from './components/CheckCircle'; @@ -49,6 +50,7 @@ export { QuestionMark } from './components/QuestionMark'; export { ScanFace } from './components/ScanFace'; export { Search } from './components/Search'; export { Setting } from './components/Setting'; +export { Settings } from './components/Settings'; export { Shield } from './components/Shield'; export { Star } from './components/Star'; export { Swap } from './components/Swap'; From 2161cf917739c34aa1fd0cd2ad34ef660d28a124 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 09:03:08 +0700 Subject: [PATCH 03/48] highlight indicator component --- .../features/Explorer/HighlightIndicator.tsx | 71 +++++++++++++++++++ .../src/features/Explorer/IndicatorDot.tsx | 67 +++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 apps/wallet/src/features/Explorer/HighlightIndicator.tsx create mode 100644 apps/wallet/src/features/Explorer/IndicatorDot.tsx diff --git a/apps/wallet/src/features/Explorer/HighlightIndicator.tsx b/apps/wallet/src/features/Explorer/HighlightIndicator.tsx new file mode 100644 index 000000000..ca0f67183 --- /dev/null +++ b/apps/wallet/src/features/Explorer/HighlightIndicator.tsx @@ -0,0 +1,71 @@ +import { type FC, useEffect, useRef } from 'react'; +import { Animated } from 'react-native'; +import { FlatList, StyleSheet } from 'react-native'; +import { View } from '@walless/gui'; + +import IndicatorDot from './IndicatorDot'; + +interface HighlightIndicatorProps { + scrollXIndex: Animated.Value; + dataLength: number; + setActiveIndex: (index: number) => void; +} + +const HighlightIndicator: FC = ({ + dataLength, + setActiveIndex, + scrollXIndex, +}) => { + const scrollXAnimated = useRef(new Animated.Value(0)).current; + const data = Array.from({ length: dataLength }, (_, i) => i); + + const backgroundColor = '#566674'; + + const height = 6; + + const inputRange = data; + const outputRange = Array.from({ length: dataLength }, () => ({ + height, + backgroundColor, + })); + + useEffect(() => { + Animated.timing(scrollXAnimated, { + toValue: scrollXIndex, + duration: 200, + useNativeDriver: true, + }).start(); + }, []); + + return ( + + index.toString()} + data={data} + contentContainerStyle={styles.flatList} + renderItem={({ item }) => { + return ( + + ); + }} + /> + + ); +}; + +export default HighlightIndicator; + +const styles = StyleSheet.create({ + container: { + alignSelf: 'center', + }, + flatList: { + gap: 6, + }, +}); diff --git a/apps/wallet/src/features/Explorer/IndicatorDot.tsx b/apps/wallet/src/features/Explorer/IndicatorDot.tsx new file mode 100644 index 000000000..0e1ab14f3 --- /dev/null +++ b/apps/wallet/src/features/Explorer/IndicatorDot.tsx @@ -0,0 +1,67 @@ +import type { FC } from 'react'; +import type { ViewStyle } from 'react-native'; +import { Animated, StyleSheet, TouchableOpacity } from 'react-native'; + +const AnimatedHoverable = Animated.createAnimatedComponent(TouchableOpacity); + +type IndicatorDotStyleProps = ViewStyle; + +interface IndicatorDotProps { + index: number; + setActiveIndex: (index: number) => void; + scrollXAnimated: Animated.Value; + inputRange: number[]; + outputRange: IndicatorDotStyleProps[]; +} + +const IndicatorDot: FC = ({ + index, + setActiveIndex, + scrollXAnimated, + inputRange, + outputRange, +}) => { + const activeBackgroundColor = '#0694D3'; + const activeHeight = 40; + + const heightOutputRange = outputRange.map((output) => output.height); + heightOutputRange[index] = activeHeight; + + const backgroundColorOutputRange = outputRange.map( + (output) => output.backgroundColor, + ); + backgroundColorOutputRange[index] = activeBackgroundColor; + + const height = scrollXAnimated.interpolate({ + inputRange, + outputRange: heightOutputRange as number[], + }); + const backgroundColor = scrollXAnimated.interpolate({ + inputRange, + outputRange: backgroundColorOutputRange as string[], + }); + + const animatedStyle = { + height, + backgroundColor, + }; + + return ( + setActiveIndex(index)} + /> + ); +}; + +export default IndicatorDot; + +const styles = StyleSheet.create({ + indicator: { + backgroundColor: '#566674', + width: 6, + height: 6, + borderRadius: 6, + }, +}); From 0b66e6b97625c4440840bf9d437eeabd9a0c58e9 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 09:03:31 +0700 Subject: [PATCH 04/48] explorer header component --- apps/wallet/src/features/Explorer/Header.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Header.tsx b/apps/wallet/src/features/Explorer/Header.tsx index 813d3ad38..886200c9b 100644 --- a/apps/wallet/src/features/Explorer/Header.tsx +++ b/apps/wallet/src/features/Explorer/Header.tsx @@ -27,10 +27,10 @@ const Header = () => { - + - + @@ -56,6 +56,7 @@ const styles = StyleSheet.create({ }, buttonContainer: { flexDirection: 'row', + alignSelf: 'flex-start', gap: 8, }, button: { @@ -63,6 +64,6 @@ const styles = StyleSheet.create({ alignItems: 'center', backgroundColor: '#23303C', borderRadius: 6, - paddingHorizontal: 10, + padding: 8, }, }); From 2d656345f9b594cb3358d0d4c192b5cbe2b16634 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 09:26:12 +0700 Subject: [PATCH 05/48] adjust highlight, move them to a specific folder --- .../src/features/Explorer/HighlightItem.tsx | 22 --- .../src/features/Explorer/Highlights.tsx | 14 -- .../{ => Highlights}/HighlightIndicator.tsx | 0 .../Explorer/Highlights/HighlightItem.tsx | 166 ++++++++++++++++++ .../{ => Highlights}/IndicatorDot.tsx | 0 .../Explorer/SwipableHighlightItems.tsx | 117 ++++++++++++ 6 files changed, 283 insertions(+), 36 deletions(-) delete mode 100644 apps/wallet/src/features/Explorer/HighlightItem.tsx delete mode 100644 apps/wallet/src/features/Explorer/Highlights.tsx rename apps/wallet/src/features/Explorer/{ => Highlights}/HighlightIndicator.tsx (100%) create mode 100644 apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx rename apps/wallet/src/features/Explorer/{ => Highlights}/IndicatorDot.tsx (100%) create mode 100644 apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx diff --git a/apps/wallet/src/features/Explorer/HighlightItem.tsx b/apps/wallet/src/features/Explorer/HighlightItem.tsx deleted file mode 100644 index 7f55effe9..000000000 --- a/apps/wallet/src/features/Explorer/HighlightItem.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { View } from '@walless/gui'; -import { Image, StyleSheet } from 'react-native'; - -const HighlightItem = () => { - return ( - - - - ); -}; - -export default HighlightItem; - -const styles = StyleSheet.create({ - image: { - flex: 1, - width: '100%', - }, -}); diff --git a/apps/wallet/src/features/Explorer/Highlights.tsx b/apps/wallet/src/features/Explorer/Highlights.tsx deleted file mode 100644 index 5deb922d5..000000000 --- a/apps/wallet/src/features/Explorer/Highlights.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Text, View } from '@walless/gui'; - -import HighlightItem from './HighlightItem'; - -const Highlights = () => { - return ( - - Today's Highlights - - - ); -}; - -export default Highlights; diff --git a/apps/wallet/src/features/Explorer/HighlightIndicator.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx similarity index 100% rename from apps/wallet/src/features/Explorer/HighlightIndicator.tsx rename to apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx new file mode 100644 index 000000000..7e04f2a3b --- /dev/null +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx @@ -0,0 +1,166 @@ +import type { FC } from 'react'; +import type { ImageSourcePropType } from 'react-native'; +import { Animated, Image, StyleSheet, TouchableOpacity } from 'react-native'; +import { Hoverable, Text, View } from '@walless/gui'; +import { Heart, Plus } from '@walless/icons'; + +const ITEM_WIDTH = 273; +interface AnimationFlatListProps { + index: number; + scrollXAnimated: Animated.Value; + maxItems: number; +} +interface HighlightItemProps { + coverUri: ImageSourcePropType; + iconUri: ImageSourcePropType; + title: string; + description: string; + loveCount: number; + activeCount: number; + animation?: AnimationFlatListProps; + onPress?: () => void; +} + +const AnimatedHoverable = Animated.createAnimatedComponent(TouchableOpacity); + +const HighlightItem: FC = ({ + coverUri, + iconUri, + title, + description, + loveCount, + activeCount, + animation, + onPress, +}) => { + const { index, scrollXAnimated, maxItems } = + animation as AnimationFlatListProps; + const inputRange = [index - 1, index, index + 1]; + const translateX = scrollXAnimated.interpolate({ + inputRange, + outputRange: [40, 0, -100], + }); + + const scale = scrollXAnimated.interpolate({ + inputRange, + outputRange: [0.8, 1, 0.8], + }); + + const opacity = scrollXAnimated.interpolate({ + inputRange, + outputRange: [1 - 1 / maxItems, 1, 0], + }); + + const animatedStyle = { + transform: [{ translateX }, { scale }], + opacity, + }; + + return ( + + + + + + + {title} + + + {description} + + + + + {loveCount} + + + + {activeCount} + + + + + + + + + ); +}; + +export default HighlightItem; + +const styles = StyleSheet.create({ + container: { + width: ITEM_WIDTH, + borderRadius: 10, + overflow: 'hidden', + backgroundColor: '#23303C', + position: 'absolute', + left: -ITEM_WIDTH / 2 - 16, + }, + coverImage: { + width: ITEM_WIDTH, + height: 143, + }, + infoContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + padding: 11, + gap: 10, + }, + iconImage: { + width: 43, + height: 43, + borderRadius: 10, + }, + middlePart: { + flex: 1, + gap: 4, + }, + title: { + color: '#ffffff', + fontSize: 16, + fontWeight: '500', + }, + loveAndActiveContainer: { + flexDirection: 'row', + gap: 10, + }, + loveAndActiveDisplay: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + activeIcon: { + width: 10, + height: 10, + borderRadius: 6, + backgroundColor: '#37B681', + }, + activeText: { + color: '#37B681', + fontSize: 10, + }, + loveText: { + color: '#4E5C69', + fontSize: 10, + }, + addBtn: { + alignSelf: 'center', + backgroundColor: '#19A3E1', + padding: 6, + borderRadius: 6, + }, + description: { + color: '#798997', + fontSize: 12, + }, +}); diff --git a/apps/wallet/src/features/Explorer/IndicatorDot.tsx b/apps/wallet/src/features/Explorer/Highlights/IndicatorDot.tsx similarity index 100% rename from apps/wallet/src/features/Explorer/IndicatorDot.tsx rename to apps/wallet/src/features/Explorer/Highlights/IndicatorDot.tsx diff --git a/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx b/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx new file mode 100644 index 000000000..d4f327e9b --- /dev/null +++ b/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx @@ -0,0 +1,117 @@ +import type { FC } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; +import { Animated, FlatList, StyleSheet } from 'react-native'; +import type { + FlingGestureHandlerEventPayload, + HandlerStateChangeEvent, +} from 'react-native-gesture-handler'; +import { + Directions, + FlingGestureHandler, + State, +} from 'react-native-gesture-handler'; +import { View } from '@walless/gui'; +import type { ExtensionDocument } from '@walless/store'; + +import HighlightItem from './Highlights/HighlightItem'; + +interface SwipableHighlightItemsProps { + setActiveIndex: (activeIndex: number) => void; + activeIndex: number; + data: ExtensionDocument[]; + scrollXIndex: Animated.Value; +} + +const SwipableHighlightItems: FC = ({ + setActiveIndex, + activeIndex, + data, + scrollXIndex, +}) => { + const scrollXAnimated = useRef(new Animated.Value(0)).current; + + useEffect(() => { + Animated.spring(scrollXAnimated, { + toValue: scrollXIndex, + useNativeDriver: true, + }).start(); + }, []); + + const handleSwipeLeft = useCallback( + (event: HandlerStateChangeEvent) => { + if (event.nativeEvent.state === State.END) { + if (activeIndex === data.length - 1) return; + setActiveIndex(activeIndex + 1); + } + }, + [], + ); + + const handleSwipeRight = useCallback( + (event: HandlerStateChangeEvent) => { + if (event.nativeEvent.state === State.END) { + if (activeIndex === 0) return; + setActiveIndex(activeIndex - 1); + } + }, + [], + ); + + return ( + + + index.toString()} + data={data} + horizontal + inverted + contentContainerStyle={styles.container} + CellRendererComponent={({ children, index, style, ...props }) => { + const newStyle = [style, { zIndex: data.length - index }]; + return ( + + {children} + + ); + }} + renderItem={({ item, index }) => { + const { name, storeMeta, networkMeta } = item as ExtensionDocument; + return ( + setActiveIndex(index)} + /> + ); + }} + /> + + + ); +}; + +export default SwipableHighlightItems; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + width: 300, + }, +}); From 6da0bac9e2543e0a76cd9b955130295725575cef Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 15:27:28 +0700 Subject: [PATCH 06/48] adjust alignment in header --- apps/wallet/src/features/Explorer/Header.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Header.tsx b/apps/wallet/src/features/Explorer/Header.tsx index 886200c9b..df166455d 100644 --- a/apps/wallet/src/features/Explorer/Header.tsx +++ b/apps/wallet/src/features/Explorer/Header.tsx @@ -11,7 +11,7 @@ const Header = () => { return ( - + Hi👋, your balance today: @@ -45,6 +45,9 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', paddingVertical: 10, }, + balanceContainer: { + gap: 4, + }, tokenValuationContainer: { flexDirection: 'row', alignItems: 'center', @@ -56,7 +59,7 @@ const styles = StyleSheet.create({ }, buttonContainer: { flexDirection: 'row', - alignSelf: 'flex-start', + alignSelf: 'flex-end', gap: 8, }, button: { From e39fee9953637fd0ff7b0b8ee39b877c1f61abfe Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 15:56:45 +0700 Subject: [PATCH 07/48] update missions, move it to a specific folder --- .../Explorer/{ => Missions}/MissionItem.tsx | 16 +++---- .../src/features/Explorer/Missions/index.tsx | 43 +++++++++++++++++++ 2 files changed, 50 insertions(+), 9 deletions(-) rename apps/wallet/src/features/Explorer/{ => Missions}/MissionItem.tsx (82%) create mode 100644 apps/wallet/src/features/Explorer/Missions/index.tsx diff --git a/apps/wallet/src/features/Explorer/MissionItem.tsx b/apps/wallet/src/features/Explorer/Missions/MissionItem.tsx similarity index 82% rename from apps/wallet/src/features/Explorer/MissionItem.tsx rename to apps/wallet/src/features/Explorer/Missions/MissionItem.tsx index 2f6c7d96b..2311caf0d 100644 --- a/apps/wallet/src/features/Explorer/MissionItem.tsx +++ b/apps/wallet/src/features/Explorer/Missions/MissionItem.tsx @@ -1,23 +1,21 @@ -import type { FC } from 'react'; +import { type FC } from 'react'; import { StyleSheet } from 'react-native'; -import Animated, { - useAnimatedStyle, - withSpring, -} from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import { Button, Text, View } from '@walless/gui'; import { Chair, InfoIcon, MissionBackground } from '@walless/icons'; interface MissionProps { + id: string; title: string; colors?: string[]; onPress?: () => void; } -const MissionItem: FC = ({ title, onPress, colors }) => { +const MissionItem: FC = ({ title, onPress, id, colors }) => { return ( - - + + @@ -72,7 +70,7 @@ const styles = StyleSheet.create({ color: '#23303C', fontSize: 12, }, - misstionBackground: { + missionBackground: { position: 'absolute', top: 0, left: 0, diff --git a/apps/wallet/src/features/Explorer/Missions/index.tsx b/apps/wallet/src/features/Explorer/Missions/index.tsx new file mode 100644 index 000000000..db1a398e3 --- /dev/null +++ b/apps/wallet/src/features/Explorer/Missions/index.tsx @@ -0,0 +1,43 @@ +import { FlatList, StyleSheet } from 'react-native'; +import { View } from '@walless/gui'; + +import { missions } from '../internal'; + +import MissionItem from './MissionItem'; + +const Missions = () => { + return ( + + { + let colors = ['#EC74A2', '#F4B999']; + if (index % 3 === 1) { + colors = ['#8253FF', '#D73EFF']; + } else if (index % 3 === 2) { + colors = ['#3263FF', '#45CFFF']; + } + + return ( + + ); + }} + horizontal + showsVerticalScrollIndicator={false} + /> + + ); +}; + +export default Missions; + +const styles = StyleSheet.create({ + container: { + marginVertical: 8, + paddingLeft: 16, + }, +}); From b84cfffb0699e4dff984e9324073ac19f604d45c Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 15:57:53 +0700 Subject: [PATCH 08/48] refactor: remove redundant stuff of highlights --- .../Highlights/HighlightIndicator.tsx | 4 - .../Explorer/Highlights/HighlightItem.tsx | 2 +- .../Explorer/Highlights/IndicatorDot.tsx | 9 ++- .../features/Explorer/Highlights/index.tsx | 74 +++++++++++++++++++ .../Explorer/SwipableHighlightItems.tsx | 1 + 5 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 apps/wallet/src/features/Explorer/Highlights/index.tsx diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx index ca0f67183..8ff20b3a4 100644 --- a/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx @@ -42,7 +42,6 @@ const HighlightIndicator: FC = ({ index.toString()} data={data} - contentContainerStyle={styles.flatList} renderItem={({ item }) => { return ( = ({ return ( setActiveIndex(index)} - /> + style={styles.container} + > + + ); }; export default IndicatorDot; const styles = StyleSheet.create({ + container: { + padding: 3, + }, indicator: { backgroundColor: '#566674', width: 6, diff --git a/apps/wallet/src/features/Explorer/Highlights/index.tsx b/apps/wallet/src/features/Explorer/Highlights/index.tsx new file mode 100644 index 000000000..39aa5cb96 --- /dev/null +++ b/apps/wallet/src/features/Explorer/Highlights/index.tsx @@ -0,0 +1,74 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { Animated, StyleSheet } from 'react-native'; +import { Text, View } from '@walless/gui'; +import { mockLayoutCards } from 'browser/kernel/utils/mockExtension'; + +import SwipableHighlightItems from '../SwipableHighlightItems'; + +import HighlightIndicator from './HighlightIndicator'; + +const Highlights = () => { + const [index, setIndex] = useState(0); + const scrollXIndex = useRef(new Animated.Value(0)).current; + const scrollXAnimated = useRef(new Animated.Value(0)).current; + const scrollXAnimatedIndicator = useRef(new Animated.Value(0)).current; + + const setActiveIndex = useCallback((activeIndex: number) => { + setIndex(activeIndex); + scrollXIndex.setValue(activeIndex); + }, []); + + useEffect(() => { + Animated.spring(scrollXAnimated, { + toValue: scrollXIndex, + useNativeDriver: true, + }).start(); + + Animated.timing(scrollXAnimatedIndicator, { + toValue: scrollXIndex, + duration: 200, + useNativeDriver: true, + }).start(); + }, []); + + return ( + + Today's Highlights + + + + + + + ); +}; + +export default Highlights; + +const styles = StyleSheet.create({ + container: { + flex: 1, + gap: 16, + minHeight: 270, + marginVertical: 8, + paddingHorizontal: 16, + }, + highlightList: { + flexGrow: 1, + flexDirection: 'row', + }, + title: { + fontSize: 18, + fontWeight: '500', + color: '#ffffff', + }, +}); diff --git a/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx b/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx index d4f327e9b..2962c6204 100644 --- a/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx +++ b/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx @@ -75,6 +75,7 @@ const SwipableHighlightItems: FC = ({ data={data} horizontal inverted + showsHorizontalScrollIndicator={false} contentContainerStyle={styles.container} CellRendererComponent={({ children, index, style, ...props }) => { const newStyle = [style, { zIndex: data.length - index }]; From 6798c89eba130d2f416be8c895395f523e21202f Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 15:58:49 +0700 Subject: [PATCH 09/48] add widgets list to the explorer screen --- .../Explorer/Widgets/CategoryButton.tsx | 78 +++++++++++++ .../Explorer/Widgets/CategoryButtons.tsx | 61 ++++++++++ .../features/Explorer/Widgets/WidgetItem.tsx | 109 ++++++++++++++++++ .../src/features/Explorer/Widgets/index.tsx | 46 ++++++++ 4 files changed, 294 insertions(+) create mode 100644 apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx create mode 100644 apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx create mode 100644 apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx create mode 100644 apps/wallet/src/features/Explorer/Widgets/index.tsx diff --git a/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx b/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx new file mode 100644 index 000000000..61c613a59 --- /dev/null +++ b/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx @@ -0,0 +1,78 @@ +import type { FC } from 'react'; +import { Animated, StyleSheet, TouchableOpacity } from 'react-native'; + +const AnimatedHoverable = Animated.createAnimatedComponent(TouchableOpacity); + +interface AnimatedCategoryButtonProps { + borderColor: string; + color: string; +} + +interface CategoryButtonProps { + index: number; + title: string; + onPress: (index: number) => void; + scrollXAnimated: Animated.Value; + inputRange: number[]; + outputRange: AnimatedCategoryButtonProps[]; +} + +const CategoryButton: FC = ({ + index, + title, + onPress, + inputRange, + outputRange, + scrollXAnimated, +}) => { + const activeColor = '#FFFFFF'; + const activeBorderColor = '#0694D3'; + const colorOutputRange = outputRange.map(({ color }) => color); + const borderColorOutputRange = outputRange.map( + ({ borderColor }) => borderColor, + ); + + colorOutputRange[index] = activeColor; + borderColorOutputRange[index] = activeBorderColor; + + const color = scrollXAnimated.interpolate({ + inputRange, + outputRange: colorOutputRange, + }); + const borderColor = scrollXAnimated.interpolate({ + inputRange, + outputRange: borderColorOutputRange, + }); + + const containerAnimatedStyle = { + borderColor, + }; + const titleAnimatedStyle = { + color, + }; + + return ( + onPress(index)} + > + + {title} + + + ); +}; + +export default CategoryButton; + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: 11, + paddingVertical: 7, + borderWidth: 1, + borderRadius: 7, + }, + title: { + fontSize: 12, + }, +}); diff --git a/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx new file mode 100644 index 000000000..2732d2e99 --- /dev/null +++ b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx @@ -0,0 +1,61 @@ +import { useEffect, useRef } from 'react'; +import { Animated, StyleSheet } from 'react-native'; + +import CategoryButton from './CategoryButton'; + +enum Category { + NETWORK = 'Network', + GAME = 'Game', + DEFI = 'DeFi', + NFT = 'NFT', +} + +const CategoryButtons = () => { + const scrollXIndex = useRef(new Animated.Value(0)).current; + const scrollXAnimated = useRef(new Animated.Value(0)).current; + const categories = Object.values(Category); + + const borderColor = '#23303C'; + const color = '#A4B3C1'; + const inputRange = categories.map((_, index) => index); + const outputRange = categories.map(() => ({ + borderColor, + color, + })); + + const handleCategoryPress = (index: number) => { + scrollXIndex.setValue(index); + }; + + useEffect(() => { + Animated.spring(scrollXAnimated, { + toValue: scrollXIndex, + useNativeDriver: true, + }).start(); + }, []); + + return ( + + {categories.map((category, index) => ( + + ))} + + ); +}; + +export default CategoryButtons; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + gap: 10, + }, +}); diff --git a/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx new file mode 100644 index 000000000..3b52a061f --- /dev/null +++ b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx @@ -0,0 +1,109 @@ +import type { FC } from 'react'; +import { Image, StyleSheet } from 'react-native'; +import { Button, Text, View } from '@walless/gui'; +import { Heart } from '@walless/icons'; + +interface WidgetItemProps { + coverImage: string; + title: string; + description: string; + activeCount: number; + loveCount: number; + onPress: () => void; +} + +const WidgetItem: FC = ({ + coverImage, + title, + description, + activeCount, + loveCount, + onPress, +}) => { + return ( + + + + + {title} + + + {description} + + + + + {loveCount} + + + + {activeCount} + + + + + + ); +}; + +export default WidgetItem; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + gap: 8, + backgroundColor: '#23313C', + padding: 8, + borderRadius: 8, + }, + coverImage: { + width: 75, + height: 50, + borderRadius: 6, + }, + middlePart: { + flex: 1, + gap: 4, + }, + title: { + color: '#ffffff', + fontSize: 16, + fontWeight: '500', + }, + loveAndActiveContainer: { + flexDirection: 'row', + gap: 10, + }, + loveAndActiveDisplay: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + activeIcon: { + width: 10, + height: 10, + borderRadius: 6, + backgroundColor: '#37B681', + }, + activeText: { + color: '#37B681', + fontSize: 10, + }, + loveText: { + color: '#4E5C69', + fontSize: 10, + }, + addBtn: { + alignSelf: 'center', + backgroundColor: '#19A3E1', + borderRadius: 6, + width: 62, + height: 28, + }, + description: { + color: '#798997', + fontSize: 12, + }, +}); diff --git a/apps/wallet/src/features/Explorer/Widgets/index.tsx b/apps/wallet/src/features/Explorer/Widgets/index.tsx new file mode 100644 index 000000000..726d92b1f --- /dev/null +++ b/apps/wallet/src/features/Explorer/Widgets/index.tsx @@ -0,0 +1,46 @@ +import { StyleSheet } from 'react-native'; +import { Text, View } from '@walless/gui'; +import { mockLayoutCards } from 'browser/kernel/utils/mockExtension'; + +import CategoryButtons from './CategoryButtons'; +import WidgetItem from './WidgetItem'; + +const Widgets = () => { + return ( + + Enhance your collection + + + {mockLayoutCards.map((card) => ( + {}} + /> + ))} + + + ); +}; + +export default Widgets; + +const styles = StyleSheet.create({ + container: { + gap: 16, + marginVertical: 8, + paddingHorizontal: 16, + }, + title: { + fontSize: 18, + fontWeight: '500', + color: '#ffffff', + }, + layoutList: { + gap: 10, + }, +}); From 7e55482b4cc1a419deddc8b635f5c35a13836f97 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 15:59:13 +0700 Subject: [PATCH 10/48] refactor: adjust some small details to get closer to the design --- apps/wallet/src/features/Explorer/Header.tsx | 1 + apps/wallet/src/features/Explorer/index.tsx | 36 +++++--------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Header.tsx b/apps/wallet/src/features/Explorer/Header.tsx index df166455d..4a462c232 100644 --- a/apps/wallet/src/features/Explorer/Header.tsx +++ b/apps/wallet/src/features/Explorer/Header.tsx @@ -44,6 +44,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 10, + paddingHorizontal: 16, }, balanceContainer: { gap: 4, diff --git a/apps/wallet/src/features/Explorer/index.tsx b/apps/wallet/src/features/Explorer/index.tsx index 3738d77a6..aec1b0794 100644 --- a/apps/wallet/src/features/Explorer/index.tsx +++ b/apps/wallet/src/features/Explorer/index.tsx @@ -1,13 +1,13 @@ import type { FC } from 'react'; import type { StyleProp, ViewStyle } from 'react-native'; -import { FlatList, StyleSheet } from 'react-native'; +import { ScrollView, StyleSheet } from 'react-native'; import { View } from '@walless/gui'; import type { WidgetDocument } from '@walless/store'; import Header from './Header'; import Highlights from './Highlights'; -import { missions } from './internal'; -import MissionItem from './MissionItem'; +import Missions from './Missions'; +import Widgets from './Widgets'; interface Props { style?: StyleProp; @@ -20,23 +20,11 @@ export const ExplorerFeature: FC = ({ style }) => { return (
- { - let colors = ['#EC74A2', '#F4B999']; - if (index % 3 === 1) { - colors = ['#8253FF', '#D73EFF']; - } else if (index % 3 === 2) { - colors = ['#3263FF', '#45CFFF']; - } - - return ; - }} - horizontal - showsVerticalScrollIndicator={false} - /> - + + + + + ); }; @@ -44,14 +32,8 @@ export const ExplorerFeature: FC = ({ style }) => { export default ExplorerFeature; const styles = StyleSheet.create({ - container: { - gap: 16, - paddingHorizontal: 16, - }, + container: {}, widgetItem: { marginHorizontal: 16, }, - flatList: { - flexGrow: 0, - }, }); From 3e04634e01f10b64f8528a23bbc09982a5eeb712 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 16 May 2024 17:42:30 +0700 Subject: [PATCH 11/48] fix: highlight animation --- .../Explorer/Highlights/HighlightItem.tsx | 13 ++---- .../Explorer/SwipableHighlightItems.tsx | 43 ++++++++++--------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx index 83ac6737d..85f8e6b25 100644 --- a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react'; import type { ImageSourcePropType } from 'react-native'; -import { Animated, Image, StyleSheet, TouchableOpacity } from 'react-native'; +import { Animated, Image, StyleSheet } from 'react-native'; import { Hoverable, Text, View } from '@walless/gui'; import { Heart, Plus } from '@walless/icons'; @@ -18,11 +18,8 @@ interface HighlightItemProps { loveCount: number; activeCount: number; animation?: AnimationFlatListProps; - onPress?: () => void; } -const AnimatedHoverable = Animated.createAnimatedComponent(TouchableOpacity); - const HighlightItem: FC = ({ coverUri, iconUri, @@ -31,7 +28,6 @@ const HighlightItem: FC = ({ loveCount, activeCount, animation, - onPress, }) => { const { index, scrollXAnimated, maxItems } = animation as AnimationFlatListProps; @@ -57,10 +53,7 @@ const HighlightItem: FC = ({ }; return ( - + @@ -90,7 +83,7 @@ const HighlightItem: FC = ({ - + ); }; diff --git a/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx b/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx index 2962c6204..8f681777c 100644 --- a/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx +++ b/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx @@ -3,13 +3,15 @@ import { useCallback, useEffect, useRef } from 'react'; import { Animated, FlatList, StyleSheet } from 'react-native'; import type { FlingGestureHandlerEventPayload, - HandlerStateChangeEvent, + GestureStateChangeEvent, } from 'react-native-gesture-handler'; import { Directions, - FlingGestureHandler, + Gesture, + GestureDetector, State, } from 'react-native-gesture-handler'; +import { Easing } from 'react-native-reanimated'; import { View } from '@walless/gui'; import type { ExtensionDocument } from '@walless/store'; @@ -31,42 +33,44 @@ const SwipableHighlightItems: FC = ({ const scrollXAnimated = useRef(new Animated.Value(0)).current; useEffect(() => { - Animated.spring(scrollXAnimated, { + Animated.timing(scrollXAnimated, { toValue: scrollXIndex, + easing: Easing.inOut(Easing.ease), useNativeDriver: true, }).start(); }, []); const handleSwipeLeft = useCallback( - (event: HandlerStateChangeEvent) => { - if (event.nativeEvent.state === State.END) { + (event: GestureStateChangeEvent) => { + if (event.state === State.END) { if (activeIndex === data.length - 1) return; setActiveIndex(activeIndex + 1); } }, - [], + [activeIndex], ); const handleSwipeRight = useCallback( - (event: HandlerStateChangeEvent) => { - if (event.nativeEvent.state === State.END) { + (event: GestureStateChangeEvent) => { + if (event.state === State.END) { if (activeIndex === 0) return; setActiveIndex(activeIndex - 1); } }, - [], + [activeIndex], ); + const leftFling = Gesture.Fling(); + const rightFling = Gesture.Fling(); + return ( - - = ({ activeCount={storeMeta.activeCount} description={storeMeta.description} animation={{ index, scrollXAnimated, maxItems: 3 }} - onPress={() => setActiveIndex(index)} /> ); }} /> - - + + ); }; From b0b449821cf3bed6da7e22dafc6e39e5e59f9f90 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 17 May 2024 15:56:25 +0700 Subject: [PATCH 12/48] change type of widget type in mock widget list --- apps/wallet/src/state/widget/shared.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/wallet/src/state/widget/shared.ts b/apps/wallet/src/state/widget/shared.ts index b9f19f43e..29a3332e7 100644 --- a/apps/wallet/src/state/widget/shared.ts +++ b/apps/wallet/src/state/widget/shared.ts @@ -1,4 +1,4 @@ -import { Networks } from '@walless/core'; +import { Networks, WidgetType } from '@walless/core'; import type { WidgetDocument } from '@walless/store'; // TODO: this mocked data is for web only @@ -9,7 +9,7 @@ export const mockWidgets: WidgetDocument[] = [ networks: [Networks.solana], version: '0.1.8', type: 'Widget', - widgetType: 'Hybrid', + widgetType: WidgetType.GAME, timestamp: new Date().toISOString(), storeMeta: { iconUri: '/img/explore/logo-pixeverse.png', @@ -35,7 +35,7 @@ export const mockWidgets: WidgetDocument[] = [ networks: [Networks.solana], version: '0.9.1', type: 'Widget', - widgetType: 'Layout', + widgetType: WidgetType.NETWORK, timestamp: new Date().toISOString(), storeMeta: { iconUri: '/img/explore/logo-solana.png', @@ -61,7 +61,7 @@ export const mockWidgets: WidgetDocument[] = [ networks: [Networks.sui], version: '0.0.1', type: 'Widget', - widgetType: 'Layout', + widgetType: WidgetType.NETWORK, timestamp: new Date().toISOString(), storeMeta: { iconUri: '/img/explore/logo-sui.png', @@ -87,7 +87,7 @@ export const mockWidgets: WidgetDocument[] = [ networks: [Networks.sui], version: '0.0.1', type: 'Widget', - widgetType: 'Layout', + widgetType: WidgetType.NETWORK, timestamp: new Date().toISOString(), storeMeta: { iconUri: '/img/network/tezos-icon-sm.png', @@ -113,7 +113,7 @@ export const mockWidgets: WidgetDocument[] = [ networks: [Networks.aptos], version: '0.0.1', type: 'Widget', - widgetType: 'Layout', + widgetType: WidgetType.NETWORK, timestamp: new Date().toISOString(), storeMeta: { iconUri: '/img/explore/logo-aptos.png', @@ -139,7 +139,7 @@ export const mockWidgets: WidgetDocument[] = [ networks: [], version: '0.1.8', type: 'Widget', - widgetType: 'Hybrid', + widgetType: WidgetType.GAME, timestamp: new Date().toISOString(), storeMeta: { iconUri: '/img/t-rex-runner/runner-icon.png', @@ -164,7 +164,7 @@ export const mockWidgets: WidgetDocument[] = [ networks: [], version: '0.0.1', type: 'Widget', - widgetType: 'Hybrid', + widgetType: WidgetType.GAME, timestamp: new Date().toISOString(), storeMeta: { iconUri: '/img/sui-jump/suijump-icon.png', From 3f86c61bcdfed0b99292fa08ab43e3471adea05c Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 17 May 2024 15:58:20 +0700 Subject: [PATCH 13/48] refactor: update import and add function for highlights --- .../Explorer/Highlights/HighlightItem.tsx | 10 ++-- .../SwipableHighlightItems.tsx | 51 ++++++++++++++----- .../features/Explorer/Highlights/index.tsx | 9 ++-- 3 files changed, 49 insertions(+), 21 deletions(-) rename apps/wallet/src/features/Explorer/{ => Highlights}/SwipableHighlightItems.tsx (75%) diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx index 85f8e6b25..45c2235ff 100644 --- a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react'; import type { ImageSourcePropType } from 'react-native'; import { Animated, Image, StyleSheet } from 'react-native'; import { Hoverable, Text, View } from '@walless/gui'; -import { Heart, Plus } from '@walless/icons'; +import { ArrowTopRight, Heart, Plus } from '@walless/icons'; const ITEM_WIDTH = 273; interface AnimationFlatListProps { @@ -17,7 +17,9 @@ interface HighlightItemProps { description: string; loveCount: number; activeCount: number; + isAdded?: boolean; animation?: AnimationFlatListProps; + onPress?: () => void; } const HighlightItem: FC = ({ @@ -27,7 +29,9 @@ const HighlightItem: FC = ({ description, loveCount, activeCount, + isAdded, animation, + onPress, }) => { const { index, scrollXAnimated, maxItems } = animation as AnimationFlatListProps; @@ -79,8 +83,8 @@ const HighlightItem: FC = ({ - - + + {isAdded ? : } diff --git a/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx b/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx similarity index 75% rename from apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx rename to apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx index 8f681777c..90f2faefe 100644 --- a/apps/wallet/src/features/Explorer/SwipableHighlightItems.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx @@ -13,14 +13,17 @@ import { } from 'react-native-gesture-handler'; import { Easing } from 'react-native-reanimated'; import { View } from '@walless/gui'; -import type { ExtensionDocument } from '@walless/store'; +import type { WidgetDocument } from '@walless/store'; +import { useWidgets } from 'utils/hooks'; +import { navigate } from 'utils/navigation'; +import { addWidgetToStorage } from 'utils/storage'; -import HighlightItem from './Highlights/HighlightItem'; +import HighlightItem from './HighlightItem'; interface SwipableHighlightItemsProps { setActiveIndex: (activeIndex: number) => void; activeIndex: number; - data: ExtensionDocument[]; + data: WidgetDocument[]; scrollXIndex: Animated.Value; } @@ -31,14 +34,10 @@ const SwipableHighlightItems: FC = ({ scrollXIndex, }) => { const scrollXAnimated = useRef(new Animated.Value(0)).current; + const activeWidgets = useWidgets().map((widget) => widget._id); - useEffect(() => { - Animated.timing(scrollXAnimated, { - toValue: scrollXIndex, - easing: Easing.inOut(Easing.ease), - useNativeDriver: true, - }).start(); - }, []); + const leftFling = Gesture.Fling(); + const rightFling = Gesture.Fling(); const handleSwipeLeft = useCallback( (event: GestureStateChangeEvent) => { @@ -60,8 +59,25 @@ const SwipableHighlightItems: FC = ({ [activeIndex], ); - const leftFling = Gesture.Fling(); - const rightFling = Gesture.Fling(); + const handleOpenWidget = (id: string) => { + navigate('Dashboard', { + screen: 'Explore', + params: { screen: 'Widget', params: { id } }, + }); + }; + + const handleAddWidget = (widget: WidgetDocument) => { + addWidgetToStorage(widget._id, widget); + handleOpenWidget(widget._id); + }; + + useEffect(() => { + Animated.timing(scrollXAnimated, { + toValue: scrollXIndex, + easing: Easing.inOut(Easing.ease), + useNativeDriver: true, + }).start(); + }, []); return ( = ({ scrollEventThrottle={16} bounces={false} keyExtractor={(_, index) => index.toString()} + scrollEnabled={false} data={data} horizontal inverted @@ -90,7 +107,7 @@ const SwipableHighlightItems: FC = ({ ); }} renderItem={({ item, index }) => { - const { name, storeMeta, networkMeta } = item as ExtensionDocument; + const { name, storeMeta, networkMeta } = item as WidgetDocument; return ( = ({ activeCount={storeMeta.activeCount} description={storeMeta.description} animation={{ index, scrollXAnimated, maxItems: 3 }} + onPress={() => { + if (activeWidgets.includes(item._id)) { + handleOpenWidget(item._id); + return; + } + handleAddWidget(item); + }} + isAdded={activeWidgets.includes(item._id)} /> ); }} diff --git a/apps/wallet/src/features/Explorer/Highlights/index.tsx b/apps/wallet/src/features/Explorer/Highlights/index.tsx index 39aa5cb96..2fc4aa343 100644 --- a/apps/wallet/src/features/Explorer/Highlights/index.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/index.tsx @@ -1,11 +1,10 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Animated, StyleSheet } from 'react-native'; import { Text, View } from '@walless/gui'; -import { mockLayoutCards } from 'browser/kernel/utils/mockExtension'; - -import SwipableHighlightItems from '../SwipableHighlightItems'; +import { mockWidgets } from 'state/widget'; import HighlightIndicator from './HighlightIndicator'; +import SwipableHighlightItems from './SwipableHighlightItems'; const Highlights = () => { const [index, setIndex] = useState(0); @@ -38,12 +37,12 @@ const Highlights = () => { From 60224a1f02ca1c3c58d6d87c674f8f0243535c7b Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 17 May 2024 15:59:31 +0700 Subject: [PATCH 14/48] add and open a widget --- .../Explorer/Widgets/CategoryButton.tsx | 7 ++-- .../Explorer/Widgets/CategoryButtons.tsx | 27 ++++++++----- .../features/Explorer/Widgets/WidgetItem.tsx | 28 +++++++++++-- .../src/features/Explorer/Widgets/index.tsx | 40 +++++++++++++++++-- packages/core/utils/widget.ts | 7 +++- 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx b/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx index 61c613a59..4d22a4fe7 100644 --- a/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react'; import { Animated, StyleSheet, TouchableOpacity } from 'react-native'; +import type { WidgetType } from '@walless/core'; const AnimatedHoverable = Animated.createAnimatedComponent(TouchableOpacity); @@ -10,8 +11,8 @@ interface AnimatedCategoryButtonProps { interface CategoryButtonProps { index: number; - title: string; - onPress: (index: number) => void; + title: WidgetType; + onPress: (index: number, category: WidgetType) => void; scrollXAnimated: Animated.Value; inputRange: number[]; outputRange: AnimatedCategoryButtonProps[]; @@ -54,7 +55,7 @@ const CategoryButton: FC = ({ return ( onPress(index)} + onPress={() => onPress(index, title)} > {title} diff --git a/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx index 2732d2e99..fccdb6fdd 100644 --- a/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx @@ -1,19 +1,20 @@ +import type { FC } from 'react'; import { useEffect, useRef } from 'react'; -import { Animated, StyleSheet } from 'react-native'; +import { Animated, Easing, StyleSheet } from 'react-native'; +import { WidgetType } from '@walless/core'; +import type { WidgetDocument } from '@walless/store'; +import { mockLayoutCards } from 'browser/kernel/utils/mockExtension'; import CategoryButton from './CategoryButton'; -enum Category { - NETWORK = 'Network', - GAME = 'Game', - DEFI = 'DeFi', - NFT = 'NFT', +interface CategoryButtonsProps { + setWidgets: (widgets: WidgetDocument[]) => void; } -const CategoryButtons = () => { +const CategoryButtons: FC = ({ setWidgets }) => { const scrollXIndex = useRef(new Animated.Value(0)).current; const scrollXAnimated = useRef(new Animated.Value(0)).current; - const categories = Object.values(Category); + const categories = Object.values(WidgetType); const borderColor = '#23303C'; const color = '#A4B3C1'; @@ -23,13 +24,19 @@ const CategoryButtons = () => { color, })); - const handleCategoryPress = (index: number) => { + const handleCategoryPress = (index: number, category: WidgetType) => { scrollXIndex.setValue(index); + const filteredLayoutCards = mockLayoutCards.filter( + (item) => item.widgetType === category, + ); + setWidgets(filteredLayoutCards); }; useEffect(() => { - Animated.spring(scrollXAnimated, { + Animated.timing(scrollXAnimated, { toValue: scrollXIndex, + easing: Easing.linear, + duration: 200, useNativeDriver: true, }).start(); }, []); diff --git a/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx index 3b52a061f..6474c41e1 100644 --- a/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx @@ -1,4 +1,4 @@ -import type { FC } from 'react'; +import { type FC } from 'react'; import { Image, StyleSheet } from 'react-native'; import { Button, Text, View } from '@walless/gui'; import { Heart } from '@walless/icons'; @@ -9,6 +9,7 @@ interface WidgetItemProps { description: string; activeCount: number; loveCount: number; + isAdded: boolean; onPress: () => void; } @@ -18,6 +19,7 @@ const WidgetItem: FC = ({ description, activeCount, loveCount, + isAdded, onPress, }) => { return ( @@ -41,8 +43,13 @@ const WidgetItem: FC = ({ - ); @@ -102,6 +109,21 @@ const styles = StyleSheet.create({ width: 62, height: 28, }, + openBtn: { + alignSelf: 'center', + backgroundColor: '#2D3C4A', + borderRadius: 6, + width: 62, + height: 28, + }, + openBtnText: { + color: '#19A3E1', + fontSize: 12, + }, + addBtnText: { + color: '#ffffff', + fontSize: 12, + }, description: { color: '#798997', fontSize: 12, diff --git a/apps/wallet/src/features/Explorer/Widgets/index.tsx b/apps/wallet/src/features/Explorer/Widgets/index.tsx index 726d92b1f..3124eca9b 100644 --- a/apps/wallet/src/features/Explorer/Widgets/index.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/index.tsx @@ -1,17 +1,42 @@ +import { useState } from 'react'; import { StyleSheet } from 'react-native'; +import { WidgetType } from '@walless/core'; import { Text, View } from '@walless/gui'; -import { mockLayoutCards } from 'browser/kernel/utils/mockExtension'; +import type { WidgetDocument } from '@walless/store'; +import { mockWidgets } from 'state/widget'; +import { useWidgets } from 'utils/hooks'; +import { navigate } from 'utils/navigation'; +import { addWidgetToStorage } from 'utils/storage'; import CategoryButtons from './CategoryButtons'; import WidgetItem from './WidgetItem'; const Widgets = () => { + const [widgets, setWidgets] = useState( + mockWidgets.filter((item) => item.widgetType === WidgetType.NETWORK), + ); + + const activeWidgets = useWidgets().map((widget) => widget._id); + + const handleOpenWidget = (id: string) => { + navigate('Dashboard', { + screen: 'Explore', + params: { screen: 'Widget', params: { id } }, + }); + }; + const handleAddWidget = (widget: WidgetDocument) => { + addWidgetToStorage(widget._id, widget); + handleOpenWidget(widget._id); + }; + + console.log('widgets', widgets); + return ( Enhance your collection - + - {mockLayoutCards.map((card) => ( + {widgets.map((card) => ( { description={card.storeMeta.description} activeCount={card.storeMeta.activeCount} loveCount={card.storeMeta.loveCount} - onPress={() => {}} + isAdded={activeWidgets.includes(card._id)} + onPress={() => { + if (activeWidgets.includes(card._id)) { + handleOpenWidget(card._id); + return; + } + handleAddWidget(card); + }} /> ))} diff --git a/packages/core/utils/widget.ts b/packages/core/utils/widget.ts index 7614bdc91..28ba07bfb 100644 --- a/packages/core/utils/widget.ts +++ b/packages/core/utils/widget.ts @@ -19,7 +19,12 @@ export interface WidgetNetworkOptions { iconColor: string; } -export type WidgetType = 'Layout' | 'Hybrid' | 'Native'; +export enum WidgetType { + NETWORK = 'Network', + GAME = 'Game', + DEFI = 'DeFi', + NFT = 'NFT', +} export interface Widget { name: string; From edcbf32d2b3c53ddc5a88ce802f500d2f41018a5 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 17 May 2024 15:59:48 +0700 Subject: [PATCH 15/48] refactor: add gesture for missions --- .../src/features/Explorer/Missions/index.tsx | 77 ++++++++++++++----- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Missions/index.tsx b/apps/wallet/src/features/Explorer/Missions/index.tsx index db1a398e3..e3be06539 100644 --- a/apps/wallet/src/features/Explorer/Missions/index.tsx +++ b/apps/wallet/src/features/Explorer/Missions/index.tsx @@ -1,4 +1,10 @@ +import { useEffect, useRef, useState } from 'react'; import { FlatList, StyleSheet } from 'react-native'; +import { + Directions, + Gesture, + GestureDetector, +} from 'react-native-gesture-handler'; import { View } from '@walless/gui'; import { missions } from '../internal'; @@ -6,29 +12,58 @@ import { missions } from '../internal'; import MissionItem from './MissionItem'; const Missions = () => { + const [currentOffset, setCurrentOffset] = useState(0); + const scrollRef = useRef(null); + + const leftFling = Gesture.Fling(); + const rightFling = Gesture.Fling(); + + useEffect(() => { + scrollRef.current?.scrollToOffset({ + offset: currentOffset, + animated: true, + }); + }, [currentOffset]); + return ( - { - let colors = ['#EC74A2', '#F4B999']; - if (index % 3 === 1) { - colors = ['#8253FF', '#D73EFF']; - } else if (index % 3 === 2) { - colors = ['#3263FF', '#45CFFF']; - } - - return ( - - ); - }} - horizontal - showsVerticalScrollIndicator={false} - /> + { + setCurrentOffset(currentOffset + event.x * 2); + })} + > + { + setCurrentOffset(currentOffset - event.x * 2); + })} + > + { + let colors = ['#EC74A2', '#F4B999']; + if (index % 3 === 1) { + colors = ['#8253FF', '#D73EFF']; + } else if (index % 3 === 2) { + colors = ['#3263FF', '#45CFFF']; + } + + return ( + + ); + }} + horizontal + showsVerticalScrollIndicator={false} + /> + + ); }; From c02a3cfa09bfa1a16c034ce41f57d1cbaec1c440 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 17 May 2024 16:00:49 +0700 Subject: [PATCH 16/48] sync --- apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx index fccdb6fdd..631e5df27 100644 --- a/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'; import { Animated, Easing, StyleSheet } from 'react-native'; import { WidgetType } from '@walless/core'; import type { WidgetDocument } from '@walless/store'; -import { mockLayoutCards } from 'browser/kernel/utils/mockExtension'; +import { mockWidgets } from 'state/widget'; import CategoryButton from './CategoryButton'; @@ -26,7 +26,7 @@ const CategoryButtons: FC = ({ setWidgets }) => { const handleCategoryPress = (index: number, category: WidgetType) => { scrollXIndex.setValue(index); - const filteredLayoutCards = mockLayoutCards.filter( + const filteredLayoutCards = mockWidgets.filter( (item) => item.widgetType === category, ); setWidgets(filteredLayoutCards); From ef2e8ff487371f927e762ef3fe74e346bd76ef78 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Fri, 17 May 2024 16:00:49 +0700 Subject: [PATCH 17/48] refactor: remove redundant log and change some styles --- .../Explorer/Highlights/HighlightIndicator.tsx | 1 - .../features/Explorer/Widgets/CategoryButtons.tsx | 4 ++-- .../src/features/Explorer/Widgets/WidgetItem.tsx | 12 +++++------- apps/wallet/src/features/Explorer/Widgets/index.tsx | 2 -- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx index 8ff20b3a4..275df651b 100644 --- a/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx @@ -20,7 +20,6 @@ const HighlightIndicator: FC = ({ const data = Array.from({ length: dataLength }, (_, i) => i); const backgroundColor = '#566674'; - const height = 6; const inputRange = data; diff --git a/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx index fccdb6fdd..631e5df27 100644 --- a/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/CategoryButtons.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'; import { Animated, Easing, StyleSheet } from 'react-native'; import { WidgetType } from '@walless/core'; import type { WidgetDocument } from '@walless/store'; -import { mockLayoutCards } from 'browser/kernel/utils/mockExtension'; +import { mockWidgets } from 'state/widget'; import CategoryButton from './CategoryButton'; @@ -26,7 +26,7 @@ const CategoryButtons: FC = ({ setWidgets }) => { const handleCategoryPress = (index: number, category: WidgetType) => { scrollXIndex.setValue(index); - const filteredLayoutCards = mockLayoutCards.filter( + const filteredLayoutCards = mockWidgets.filter( (item) => item.widgetType === category, ); setWidgets(filteredLayoutCards); diff --git a/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx index 6474c41e1..af1a3236d 100644 --- a/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx @@ -44,7 +44,7 @@ const WidgetItem: FC = ({ + ); }; diff --git a/apps/wallet/src/features/Explorer/Widgets/index.tsx b/apps/wallet/src/features/Explorer/Widgets/index.tsx index 7c69777d9..59878a865 100644 --- a/apps/wallet/src/features/Explorer/Widgets/index.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/index.tsx @@ -37,7 +37,7 @@ const Widgets = () => { return ( Enhance your collection - + {/* */} {widgets.map((card) => ( Date: Thu, 23 May 2024 13:23:00 +0700 Subject: [PATCH 21/48] sync --- .../Explorer/Highlights/HighlightItem.tsx | 48 ++----------------- .../Highlights/SwipableHighlightItems.tsx | 9 +--- 2 files changed, 7 insertions(+), 50 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx index f6451873b..843c3e8f7 100644 --- a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx @@ -1,20 +1,10 @@ -import { type FC, useCallback } from 'react'; +import { type FC } from 'react'; import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import type { - FlingGestureHandlerEventPayload, - HandlerStateChangeEvent, -} from 'react-native-gesture-handler'; -import { - Directions, - FlingGestureHandler, - State, -} from 'react-native-gesture-handler'; +import type {} from 'react-native-gesture-handler'; import type { SharedValue } from 'react-native-reanimated'; import Animated, { - Easing, interpolate, useAnimatedStyle, - withTiming, } from 'react-native-reanimated'; import { runtime } from '@walless/core'; import { ArrowTopRight, Plus } from '@walless/icons'; @@ -46,15 +36,8 @@ const HighlightItem: FC = ({ onPress, widget, }) => { - const { - index, - currentIndex, - maxItems, - prevIndex, - setActiveIndex, - activeIndex, - animatedValue, - } = animation as AnimationFlatListProps; + const { index, currentIndex, maxItems, animatedValue } = + animation as AnimationFlatListProps; const inputRange = [index - 1, index, index + 1]; const animatedStyle = useAnimatedStyle(() => { @@ -76,7 +59,7 @@ const HighlightItem: FC = ({ transform: [{ translateX }, { scale }], opacity, }; - }, [currentIndex.value]); + }, [currentIndex]); const coverImgResource = runtime.isMobile ? assets.widget[widget._id]?.storeMeta.coverUri @@ -90,27 +73,6 @@ const HighlightItem: FC = ({ zIndex: maxItems - index, }; - const handleSwipeLeft = useCallback( - (event: HandlerStateChangeEvent) => { - if (event.nativeEvent.state === State.END) { - console.log('active: ', widget.name); - setActiveIndex(activeIndex + 1); - animatedValue.value = withTiming(activeIndex + 1); - } - }, - [activeIndex], - ); - - const handleSwipeRight = useCallback( - (event: HandlerStateChangeEvent) => { - if (event.nativeEvent.state === State.END) { - setActiveIndex(activeIndex - 1); - animatedValue.value = withTiming(activeIndex - 1); - } - }, - [activeIndex], - ); - return ( diff --git a/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx b/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx index 2072c3339..cc3ec1971 100644 --- a/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react'; -import { useCallback, useEffect } from 'react'; +import { useCallback } from 'react'; import { StyleSheet } from 'react-native'; -import { View } from 'react-native'; import type { FlingGestureHandlerEventPayload, GestureStateChangeEvent, @@ -14,11 +13,7 @@ import { State, } from 'react-native-gesture-handler'; import type { SharedValue } from 'react-native-reanimated'; -import Animated, { - useSharedValue, - withSpring, - withTiming, -} from 'react-native-reanimated'; +import Animated, { useSharedValue, withSpring } from 'react-native-reanimated'; import type { WidgetDocument } from '@walless/store'; import { mockWidgets } from 'state/widget'; import { useWidgets } from 'utils/hooks'; From 9deeaf28618c8b5ab3e61ed77bf874d5872c1ba6 Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 23 May 2024 17:18:20 +0700 Subject: [PATCH 22/48] adjust highlight stack to work on both web and mobile --- .../Highlights/HighlightIndicator.tsx | 24 +++---- .../Explorer/Highlights/HighlightItem.tsx | 7 +- .../Explorer/Highlights/IndicatorDot.tsx | 49 ++++++++----- .../Highlights/SwipableHighlightItems.tsx | 72 +++++++++---------- .../features/Explorer/Highlights/index.tsx | 27 +++---- apps/wallet/src/stacks/Explorer/index.tsx | 2 +- 6 files changed, 91 insertions(+), 90 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx index bf47a4861..70526902e 100644 --- a/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightIndicator.tsx @@ -1,39 +1,31 @@ -import { type FC, useEffect, useRef } from 'react'; -import { Animated } from 'react-native'; +import type { FC } from 'react'; import { FlatList, StyleSheet } from 'react-native'; +import type { SharedValue } from 'react-native-reanimated'; import { View } from '@walless/gui'; import IndicatorDot from './IndicatorDot'; interface HighlightIndicatorProps { - scrollXIndex: Animated.Value; dataLength: number; setActiveIndex: (index: number) => void; + currentIndex: SharedValue; + animatedValue: SharedValue; } const HighlightIndicator: FC = ({ dataLength, setActiveIndex, - scrollXIndex, + animatedValue, }) => { - const scrollXAnimated = useRef(new Animated.Value(0)).current; const data = Array.from({ length: dataLength }, (_, i) => i); - const backgroundColor = '#566674'; + const height = 6; const inputRange = data; const outputRange = Array.from({ length: dataLength }, () => ({ - backgroundColor, + height, })); - useEffect(() => { - Animated.timing(scrollXAnimated, { - toValue: scrollXIndex, - duration: 200, - useNativeDriver: true, - }).start(); - }, []); - return ( = ({ diff --git a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx index 843c3e8f7..6c22667f0 100644 --- a/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/HighlightItem.tsx @@ -17,7 +17,6 @@ const ITEM_WIDTH = 273; interface AnimationFlatListProps { index: number; activeIndex: number; - currentIndex: SharedValue; setActiveIndex: (activeIndex: number) => void; animatedValue: SharedValue; prevIndex: SharedValue; @@ -36,7 +35,7 @@ const HighlightItem: FC = ({ onPress, widget, }) => { - const { index, currentIndex, maxItems, animatedValue } = + const { index, maxItems, animatedValue } = animation as AnimationFlatListProps; const inputRange = [index - 1, index, index + 1]; @@ -44,7 +43,7 @@ const HighlightItem: FC = ({ const translateX = interpolate( animatedValue.value, inputRange, - [50, 0, -100], + [40, 0, -100], ); const scale = interpolate(animatedValue.value, inputRange, [0.8, 1, 0.8]); @@ -59,7 +58,7 @@ const HighlightItem: FC = ({ transform: [{ translateX }, { scale }], opacity, }; - }, [currentIndex]); + }, [animatedValue]); const coverImgResource = runtime.isMobile ? assets.widget[widget._id]?.storeMeta.coverUri diff --git a/apps/wallet/src/features/Explorer/Highlights/IndicatorDot.tsx b/apps/wallet/src/features/Explorer/Highlights/IndicatorDot.tsx index 4d9f58571..aedd2f16d 100644 --- a/apps/wallet/src/features/Explorer/Highlights/IndicatorDot.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/IndicatorDot.tsx @@ -1,6 +1,12 @@ import type { FC } from 'react'; import type { ViewStyle } from 'react-native'; -import { Animated, StyleSheet, TouchableOpacity } from 'react-native'; +import { StyleSheet, TouchableOpacity } from 'react-native'; +import type { SharedValue } from 'react-native-reanimated'; +import Animated, { + interpolate, + useAnimatedStyle, + withTiming, +} from 'react-native-reanimated'; const AnimatedHoverable = Animated.createAnimatedComponent(TouchableOpacity); @@ -9,38 +15,49 @@ type IndicatorDotStyleProps = ViewStyle; interface IndicatorDotProps { index: number; setActiveIndex: (index: number) => void; - scrollXAnimated: Animated.Value; inputRange: number[]; outputRange: IndicatorDotStyleProps[]; + animatedValue: SharedValue; } const IndicatorDot: FC = ({ index, setActiveIndex, - scrollXAnimated, inputRange, outputRange, + animatedValue, }) => { - const activeBackgroundColor = '#0694D3'; + const heightOutputRange = outputRange.map((output) => output.height); + const opacityOutputRange = outputRange.map(() => 0.2); - const backgroundColorOutputRange = outputRange.map( - (output) => output.backgroundColor, - ); - backgroundColorOutputRange[index] = activeBackgroundColor; + heightOutputRange[index] = 40; + opacityOutputRange[index] = 1; + + const animatedStyle = useAnimatedStyle(() => { + const height = interpolate( + animatedValue.value, + inputRange, + heightOutputRange as number[], + ); + + const opacity = interpolate( + animatedValue.value, + inputRange, + opacityOutputRange as number[], + ); - const backgroundColor = scrollXAnimated.interpolate({ - inputRange, - outputRange: backgroundColorOutputRange as string[], - }); + return { height, opacity }; + }, [animatedValue]); - const animatedStyle = { - backgroundColor, + const handleClick = () => { + setActiveIndex(index); + animatedValue.value = withTiming(index); }; return ( setActiveIndex(index)} + onPress={handleClick} style={styles.container} > @@ -55,7 +72,7 @@ const styles = StyleSheet.create({ padding: 3, }, indicator: { - backgroundColor: '#566674', + backgroundColor: '#0694D3', width: 6, height: 6, borderRadius: 6, diff --git a/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx b/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx index cc3ec1971..06d6881b4 100644 --- a/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/SwipableHighlightItems.tsx @@ -9,13 +9,11 @@ import { Directions, Gesture, GestureDetector, - GestureHandlerRootView, State, } from 'react-native-gesture-handler'; import type { SharedValue } from 'react-native-reanimated'; -import Animated, { useSharedValue, withSpring } from 'react-native-reanimated'; +import Animated, { useSharedValue, withTiming } from 'react-native-reanimated'; import type { WidgetDocument } from '@walless/store'; -import { mockWidgets } from 'state/widget'; import { useWidgets } from 'utils/hooks'; import { navigate } from 'utils/navigation'; import { addWidgetToStorage } from 'utils/storage'; @@ -26,17 +24,16 @@ interface SwipableHighlightItemsProps { setActiveIndex: (activeIndex: number) => void; activeIndex: number; data: WidgetDocument[]; - currentIndex: SharedValue; + animatedValue: SharedValue; } const SwipableHighlightItems: FC = ({ setActiveIndex, activeIndex, data, - currentIndex, + animatedValue, }) => { const prevIndex = useSharedValue(0); - const animatedValue = useSharedValue(0); const activeWidgets = useWidgets().map((widget) => widget._id); const handleSwipeLeft = useCallback( @@ -44,8 +41,7 @@ const SwipableHighlightItems: FC = ({ if (event.state === State.END) { if (activeIndex === data.length - 1) return; setActiveIndex(activeIndex + 1); - console.log('active: ', mockWidgets[activeIndex + 1]); - animatedValue.value = withSpring(activeIndex + 1); + animatedValue.value = withTiming(activeIndex + 1); } }, [activeIndex], @@ -55,8 +51,7 @@ const SwipableHighlightItems: FC = ({ (event: GestureStateChangeEvent) => { if (event.state === State.END) { if (activeIndex === 0) return; - animatedValue.value = withSpring(activeIndex - 1); - console.log(mockWidgets[activeIndex - 1]); + animatedValue.value = withTiming(activeIndex - 1); setActiveIndex(activeIndex - 1); } }, @@ -89,37 +84,34 @@ const SwipableHighlightItems: FC = ({ }; return ( - - - - - {data.map((widget, index) => ( - { - if (activeWidgets.includes(widget._id)) { - handleOpenWidget(widget._id); - return; - } - handleAddWidget(widget); - }} - isAdded={activeWidgets.includes(widget._id)} - /> - ))} - - + + + + {data.map((widget, index) => ( + { + if (activeWidgets.includes(widget._id)) { + handleOpenWidget(widget._id); + return; + } + handleAddWidget(widget); + }} + isAdded={activeWidgets.includes(widget._id)} + /> + ))} + - + ); }; diff --git a/apps/wallet/src/features/Explorer/Highlights/index.tsx b/apps/wallet/src/features/Explorer/Highlights/index.tsx index 852a58f08..dc02977c4 100644 --- a/apps/wallet/src/features/Explorer/Highlights/index.tsx +++ b/apps/wallet/src/features/Explorer/Highlights/index.tsx @@ -1,6 +1,6 @@ -import { useCallback, useState } from 'react'; -import { Animated, StyleSheet, Text } from 'react-native'; -import { useSharedValue } from 'react-native-reanimated'; +import { useCallback, useEffect, useState } from 'react'; +import { StyleSheet, Text } from 'react-native'; +import { useSharedValue, withTiming } from 'react-native-reanimated'; import { View } from '@walless/gui'; import { mockWidgets } from 'state/widget'; @@ -10,19 +10,18 @@ import SwipableHighlightItems from './SwipableHighlightItems'; const Highlights = () => { const [index, setIndex] = useState(0); const currentIndex = useSharedValue(0); + const animatedValue = useSharedValue(0); + const animatedIndicatorValue = useSharedValue(0); const setActiveIndex = useCallback((activeIndex: number) => { setIndex(activeIndex); currentIndex.value = activeIndex; }, []); - // useEffect(() => { - // Animated.timing(scrollXAnimatedIndicator, { - // toValue: scrollXIndex, - // duration: 200, - // useNativeDriver: true, - // }).start(); - // }, []); + useEffect(() => { + animatedValue.value = withTiming(index); + animatedIndicatorValue.value = withTiming(index); + }, [index]); return ( @@ -32,13 +31,15 @@ const Highlights = () => { setActiveIndex={setActiveIndex} activeIndex={index} data={mockWidgets} - currentIndex={currentIndex} + animatedValue={animatedValue} /> - {/* */} + currentIndex={currentIndex} + animatedValue={animatedIndicatorValue} + /> ); diff --git a/apps/wallet/src/stacks/Explorer/index.tsx b/apps/wallet/src/stacks/Explorer/index.tsx index d2bbe1701..078b3813b 100644 --- a/apps/wallet/src/stacks/Explorer/index.tsx +++ b/apps/wallet/src/stacks/Explorer/index.tsx @@ -17,7 +17,7 @@ export const ExplorerStack = () => { const screenOptions: DrawerNavigationOptions = { headerShown: false, drawerStyle: styles.drawer, - swipeEdgeWidth: 5000, + swipeEdgeWidth: 100, swipeMinDistance: sidebarWidth / 3, overlayColor: 'transparent', drawerType: navigationDisplay.isPermanentDrawer ? 'permanent' : 'back', From 37fb84288643b96311bec5cbe79f3335b76b479d Mon Sep 17 00:00:00 2001 From: ltminhthu Date: Thu, 23 May 2024 17:44:55 +0700 Subject: [PATCH 23/48] adjust some details on explorer screen --- .../Explorer/Missions/MissionItem.tsx | 10 ++-- .../Explorer/Widgets/CategoryButton.tsx | 47 ++++++++++++------- .../Explorer/Widgets/CategoryButtons.tsx | 24 ++++------ .../features/Explorer/Widgets/WidgetItem.tsx | 3 ++ .../src/features/Explorer/Widgets/index.tsx | 2 +- 5 files changed, 48 insertions(+), 38 deletions(-) diff --git a/apps/wallet/src/features/Explorer/Missions/MissionItem.tsx b/apps/wallet/src/features/Explorer/Missions/MissionItem.tsx index bfbbb5caa..fec4eec90 100644 --- a/apps/wallet/src/features/Explorer/Missions/MissionItem.tsx +++ b/apps/wallet/src/features/Explorer/Missions/MissionItem.tsx @@ -1,7 +1,7 @@ import { type FC } from 'react'; -import { StyleSheet, Text } from 'react-native'; +import { StyleSheet, Text, TouchableOpacity } from 'react-native'; import Animated from 'react-native-reanimated'; -import { Button, View } from '@walless/gui'; +import { View } from '@walless/gui'; import { Chair, InfoIcon, MissionBackground } from '@walless/icons'; interface MissionProps { @@ -25,9 +25,9 @@ const MissionItem: FC = ({ title, onPress, id, colors }) => { {title} - + ); }; @@ -62,6 +62,8 @@ const styles = StyleSheet.create({ height: 25, borderRadius: 6, backgroundColor: '#ffffff', + justifyContent: 'center', + alignItems: 'center', }, text: { color: '#ffffff', diff --git a/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx b/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx index 4d22a4fe7..cbec74775 100644 --- a/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/CategoryButton.tsx @@ -1,5 +1,10 @@ import type { FC } from 'react'; -import { Animated, StyleSheet, TouchableOpacity } from 'react-native'; +import { StyleSheet, TouchableOpacity } from 'react-native'; +import type { SharedValue } from 'react-native-reanimated'; +import Animated, { + interpolateColor, + useAnimatedStyle, +} from 'react-native-reanimated'; import type { WidgetType } from '@walless/core'; const AnimatedHoverable = Animated.createAnimatedComponent(TouchableOpacity); @@ -13,7 +18,7 @@ interface CategoryButtonProps { index: number; title: WidgetType; onPress: (index: number, category: WidgetType) => void; - scrollXAnimated: Animated.Value; + animatedValue: SharedValue; inputRange: number[]; outputRange: AnimatedCategoryButtonProps[]; } @@ -24,7 +29,7 @@ const CategoryButton: FC = ({ onPress, inputRange, outputRange, - scrollXAnimated, + animatedValue, }) => { const activeColor = '#FFFFFF'; const activeBorderColor = '#0694D3'; @@ -36,21 +41,29 @@ const CategoryButton: FC = ({ colorOutputRange[index] = activeColor; borderColorOutputRange[index] = activeBorderColor; - const color = scrollXAnimated.interpolate({ - inputRange, - outputRange: colorOutputRange, - }); - const borderColor = scrollXAnimated.interpolate({ - inputRange, - outputRange: borderColorOutputRange, - }); + const containerAnimatedStyle = useAnimatedStyle(() => { + const borderColor = interpolateColor( + animatedValue.value, + inputRange, + borderColorOutputRange, + ); - const containerAnimatedStyle = { - borderColor, - }; - const titleAnimatedStyle = { - color, - }; + return { + borderColor, + }; + }, [animatedValue]); + + const titleAnimatedStyle = useAnimatedStyle(() => { + const color = interpolateColor( + animatedValue.value, + inputRange, + colorOutputRange, + ); + + return { + color, + }; + }, [animatedValue]); return ( = ({ setWidgets }) => { - const scrollXIndex = useRef(new Animated.Value(0)).current; - const scrollXAnimated = useRef(new Animated.Value(0)).current; + const currentIndex = useSharedValue(0); + const animatedValue = useSharedValue(0); const categories = Object.values(WidgetType); const borderColor = '#23303C'; @@ -24,23 +24,15 @@ const CategoryButtons: FC = ({ setWidgets }) => { color, })); - const handleCategoryPress = (index: number, category: WidgetType) => { - scrollXIndex.setValue(index); + const handleCategoryPress = (activeIndex: number, category: WidgetType) => { + currentIndex.value = activeIndex; + animatedValue.value = withTiming(activeIndex); const filteredLayoutCards = mockWidgets.filter( (item) => item.widgetType === category, ); setWidgets(filteredLayoutCards); }; - useEffect(() => { - Animated.timing(scrollXAnimated, { - toValue: scrollXIndex, - easing: Easing.linear, - duration: 200, - useNativeDriver: true, - }).start(); - }, []); - return ( {categories.map((category, index) => ( @@ -48,7 +40,7 @@ const CategoryButtons: FC = ({ setWidgets }) => { key={category} index={index} title={category} - scrollXAnimated={scrollXAnimated} + animatedValue={animatedValue} inputRange={inputRange} outputRange={outputRange} onPress={handleCategoryPress} diff --git a/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx index 3e82dd8ee..fcf6562f3 100644 --- a/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/WidgetItem.tsx @@ -61,6 +61,7 @@ const styles = StyleSheet.create({ backgroundColor: '#23313C', padding: 8, borderRadius: 8, + alignItems: 'center', }, coverImage: { width: 75, @@ -104,6 +105,8 @@ const styles = StyleSheet.create({ borderRadius: 6, width: 62, height: 28, + justifyContent: 'center', + alignItems: 'center', }, addBtn: { backgroundColor: '#19A3E1', diff --git a/apps/wallet/src/features/Explorer/Widgets/index.tsx b/apps/wallet/src/features/Explorer/Widgets/index.tsx index 59878a865..7c69777d9 100644 --- a/apps/wallet/src/features/Explorer/Widgets/index.tsx +++ b/apps/wallet/src/features/Explorer/Widgets/index.tsx @@ -37,7 +37,7 @@ const Widgets = () => { return ( Enhance your collection - {/* */} + {widgets.map((card) => ( Date: Thu, 30 May 2024 14:37:23 +0700 Subject: [PATCH 24/48] refactor: re-skin action card --- .../Loyalty/AchievementsTab/CountDown.tsx | 24 --- .../Loyalty/AchievementsTab/StreakBar.tsx | 40 ---- .../Loyalty/AchievementsTab/index.tsx | 4 +- .../features/Loyalty/AchievementsTab/utils.ts | 8 - .../features/Loyalty/ActionCard/CountDown.tsx | 38 ++++ .../features/Loyalty/ActionCard/PointTag.tsx | 39 ++++ .../features/Loyalty/ActionCard/StreakBar.tsx | 60 ++++++ .../ActionCard.tsx => ActionCard/index.tsx} | 201 ++++++------------ .../internal.tsx | 24 ++- 9 files changed, 232 insertions(+), 206 deletions(-) delete mode 100644 apps/wallet/src/features/Loyalty/AchievementsTab/CountDown.tsx delete mode 100644 apps/wallet/src/features/Loyalty/AchievementsTab/StreakBar.tsx delete mode 100644 apps/wallet/src/features/Loyalty/AchievementsTab/utils.ts create mode 100644 apps/wallet/src/features/Loyalty/ActionCard/CountDown.tsx create mode 100644 apps/wallet/src/features/Loyalty/ActionCard/PointTag.tsx create mode 100644 apps/wallet/src/features/Loyalty/ActionCard/StreakBar.tsx rename apps/wallet/src/features/Loyalty/{AchievementsTab/ActionCard.tsx => ActionCard/index.tsx} (54%) rename apps/wallet/src/features/Loyalty/{AchievementsTab => ActionCard}/internal.tsx (82%) diff --git a/apps/wallet/src/features/Loyalty/AchievementsTab/CountDown.tsx b/apps/wallet/src/features/Loyalty/AchievementsTab/CountDown.tsx deleted file mode 100644 index ef3145554..000000000 --- a/apps/wallet/src/features/Loyalty/AchievementsTab/CountDown.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { FC } from 'react'; -import { StyleSheet, Text } from 'react-native'; - -import { formatCountdownTime } from './utils'; - -interface Props { - timeRemaining: number; -} - -const CountDown: FC = ({ timeRemaining }) => { - return ( - {formatCountdownTime(timeRemaining)} - ); -}; - -const styles = StyleSheet.create({ - counterText: { - fontSize: 11, - fontWeight: '500', - color: '#00B1FF', - }, -}); - -export default CountDown; diff --git a/apps/wallet/src/features/Loyalty/AchievementsTab/StreakBar.tsx b/apps/wallet/src/features/Loyalty/AchievementsTab/StreakBar.tsx deleted file mode 100644 index 663dee703..000000000 --- a/apps/wallet/src/features/Loyalty/AchievementsTab/StreakBar.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { FC } from 'react'; -import type { ViewStyle } from 'react-native'; -import { StyleSheet, View } from 'react-native'; - -interface Props { - style?: ViewStyle; - currentStreak: number; - streak: number; -} - -const StreakBar: FC = ({ style, currentStreak, streak }) => { - return ( - - {Array.from({ length: streak }, (_, i) => ( - - ))} - - ); -}; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - gap: 4, - }, - bar: { - height: 3, - borderRadius: 4, - flex: 1, - backgroundColor: '#515151', - }, - activeBar: { - backgroundColor: '#00B1FF', - }, -}); - -export default StreakBar; diff --git a/apps/wallet/src/features/Loyalty/AchievementsTab/index.tsx b/apps/wallet/src/features/Loyalty/AchievementsTab/index.tsx index d2bbef0f6..e3b261734 100644 --- a/apps/wallet/src/features/Loyalty/AchievementsTab/index.tsx +++ b/apps/wallet/src/features/Loyalty/AchievementsTab/index.tsx @@ -11,8 +11,8 @@ import { ActionCategory, queries } from '@walless/graphql'; import { groupBy } from 'lodash'; import { qlClient } from 'utils/graphql'; -import ActionCard from './ActionCard'; -import { getCycleEndTime } from './internal'; +import ActionCard from '../ActionCard'; +import { getCycleEndTime } from '../ActionCard/internal'; interface Props { userProgress?: UserProgress; diff --git a/apps/wallet/src/features/Loyalty/AchievementsTab/utils.ts b/apps/wallet/src/features/Loyalty/AchievementsTab/utils.ts deleted file mode 100644 index ec5560cf6..000000000 --- a/apps/wallet/src/features/Loyalty/AchievementsTab/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const formatCountdownTime = (timeRemaining: number) => { - const hours = Math.floor( - (timeRemaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60), - ); - const minutes = Math.floor((timeRemaining % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((timeRemaining % (1000 * 60)) / 1000); - return `${hours}h:${minutes}m:${seconds}s`; -}; diff --git a/apps/wallet/src/features/Loyalty/ActionCard/CountDown.tsx b/apps/wallet/src/features/Loyalty/ActionCard/CountDown.tsx new file mode 100644 index 000000000..dccb2afb6 --- /dev/null +++ b/apps/wallet/src/features/Loyalty/ActionCard/CountDown.tsx @@ -0,0 +1,38 @@ +import type { FC } from 'react'; +import { useEffect, useState } from 'react'; +import { StyleSheet, Text } from 'react-native'; + +import { formatCountdownTime } from './internal'; + +interface Props { + initialTimeRemaining: number; +} + +const CountDown: FC = ({ initialTimeRemaining }) => { + const [timeRemaining, setTimeRemaining] = + useState(initialTimeRemaining); + + useEffect(() => { + const interval = setInterval(() => { + if (timeRemaining > 0) { + setTimeRemaining((prev) => prev - 1000); + } + }, 1000); + + return () => clearInterval(interval); + }, [initialTimeRemaining]); + + return ( + {formatCountdownTime(timeRemaining)} + ); +}; + +const styles = StyleSheet.create({ + counterText: { + fontSize: 11, + fontWeight: '500', + color: '#00B1FF', + }, +}); + +export default CountDown; diff --git a/apps/wallet/src/features/Loyalty/ActionCard/PointTag.tsx b/apps/wallet/src/features/Loyalty/ActionCard/PointTag.tsx new file mode 100644 index 000000000..107dba064 --- /dev/null +++ b/apps/wallet/src/features/Loyalty/ActionCard/PointTag.tsx @@ -0,0 +1,39 @@ +import type { FC } from 'react'; +import type { ViewStyle } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; +import { BlingBling } from '@walless/icons'; + +import { sharedStyles } from './internal'; + +interface Props { + points: number; + style?: ViewStyle; +} + +const PointTag: FC = ({ points, style }) => { + return ( + + + + + {points} Points + + ); +}; + +export default PointTag; + +const styles = StyleSheet.create({ + blingContainer: { + backgroundColor: '#212F3C', + alignItems: 'center', + justifyContent: 'center', + width: 16, + height: 16, + borderRadius: 8, + }, + text: { + fontSize: 10, + color: 'white', + }, +}); diff --git a/apps/wallet/src/features/Loyalty/ActionCard/StreakBar.tsx b/apps/wallet/src/features/Loyalty/ActionCard/StreakBar.tsx new file mode 100644 index 000000000..87906938a --- /dev/null +++ b/apps/wallet/src/features/Loyalty/ActionCard/StreakBar.tsx @@ -0,0 +1,60 @@ +import type { FC } from 'react'; +import type { ViewStyle } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; + +interface Props { + style?: ViewStyle; + currentStreak: number; + streak: number; + isRecorded: boolean; +} + +const StreakBar: FC = ({ style, currentStreak, streak, isRecorded }) => { + return ( + + + {Array.from({ length: streak }, (_, i) => ( + + ))} + + + + {isRecorded ? 'Recorded streak' : ''} + {`${currentStreak}/${streak}`} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + gap: 4, + }, + streakContainer: { + flexDirection: 'row', + gap: 4, + }, + bar: { + height: 3, + borderRadius: 4, + flex: 1, + backgroundColor: '#515151', + }, + activeBar: { + backgroundColor: '#00B1FF', + }, + textContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + text: { + fontSize: 10, + color: '#A4B3C1', + }, +}); + +export default StreakBar; diff --git a/apps/wallet/src/features/Loyalty/AchievementsTab/ActionCard.tsx b/apps/wallet/src/features/Loyalty/ActionCard/index.tsx similarity index 54% rename from apps/wallet/src/features/Loyalty/AchievementsTab/ActionCard.tsx rename to apps/wallet/src/features/Loyalty/ActionCard/index.tsx index 61b9bd3fd..8401a29e3 100644 --- a/apps/wallet/src/features/Loyalty/AchievementsTab/ActionCard.tsx +++ b/apps/wallet/src/features/Loyalty/ActionCard/index.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import type { ViewStyle } from 'react-native'; import { ActivityIndicator, @@ -34,7 +34,9 @@ import { getCycleEndTime, getIconByType, navigateInternalByCta, + sharedStyles, } from './internal'; +import PointTag from './PointTag'; import StreakBar from './StreakBar'; interface Props { @@ -49,47 +51,32 @@ const ActionCard: FC = ({ style, action, canUserPerformAction }) => { return extractDataFromMetadata(action.metadata as ActionMetadata[]); }, [action]); - const [timeRemaining, setTimeRemaining] = useState(null); const [isPerformingAction, setIsPerformingAction] = useState(false); - useEffect(() => { + const initialTimeRemaining = useMemo(() => { if ( !userProgress || canUserPerformAction || action.category !== ActionCategory.Recurring || !action.cycleInHours ) { - return; + return null; } - const interval = setInterval(() => { - setTimeRemaining((prev) => { - if (prev === null) { - const lastRecord = ( - userProgress.actionRecords as ActionRecord[] - ).findLast((record) => record.actionId === action.id); - if (!lastRecord) { - return 0; - } - - const cycleEndTime = getCycleEndTime( - new Date(lastRecord.timestamp), - action.cycleInHours as number, - ); - - return cycleEndTime.getTime() - Date.now(); - } - - if (prev <= 0) { - return null; - } + const lastRecord = (userProgress.actionRecords as ActionRecord[]).findLast( + (record) => record.actionId === action.id, + ); + if (!lastRecord) { + return 0; + } - return prev - 1000; - }); - }, 1000); + const cycleEndTime = getCycleEndTime( + new Date(lastRecord.timestamp), + action.cycleInHours as number, + ); - return () => clearInterval(interval); - }, [userProgress, action, canUserPerformAction]); + return cycleEndTime.getTime() - Date.now(); + }, [userProgress]); const FallbackIcon = getIconByType(action.type || ''); @@ -159,98 +146,70 @@ const ActionCard: FC = ({ style, action, canUserPerformAction }) => { return currentStreak; }, [stat, action.streak]); - const showDescContainer: boolean = useMemo(() => { - if ( - action.category === ActionCategory.Milestone && - stat?.milestone && - action.milestone - ) { - return stat.milestone < action.milestone; - } - - if (action.category === ActionCategory.Streak && action.streak) { - return true; - } - - return !!desc; - }, [desc, stat, action.category]); + const isPassthrough = + !canUserPerformAction && action.category !== ActionCategory.Streak; return ( - - {icon ? ( - - ) : ( - FallbackIcon - )} + + + {icon ? ( + + ) : ( + FallbackIcon + )} - - + {name} - - {timeRemaining && } + {desc && {desc}} + + + + + + {initialTimeRemaining && ( + + + + )} {action.category === ActionCategory.Streak && action.streak && ( )} - - {showDescContainer && ( - - - {!canUserPerformAction && action.streak - ? 'Recorded streak' - : desc} - - - - {action.category === ActionCategory.Streak && - `${currentStreak}/${action.streak}`} - - {action.category === ActionCategory.Milestone && - stat?.milestone && - action.milestone && - stat.milestone < action.milestone && - `${stat.milestone || 0}/${action.milestone}`} - - - )} - - {action.points} points - {action.category !== ActionCategory.Milestone && - action.category !== ActionCategory.Streak && - (isPerformingAction ? ( - - ) : ( -