1
1
import React , { useCallback , useEffect , useRef , useState } from 'react' ;
2
2
import ReactDOM from 'react-dom' ;
3
3
import type { LayoutChangeEvent } from 'react-native' ;
4
- import { View } from 'react-native' ;
5
- import Animated , { FadeIn , FadeOut } from 'react-native-reanimated' ;
4
+ import { ActivityIndicator , StyleSheet , View } from 'react-native' ;
5
+ import Animated , { FadeIn , FadeOut , useAnimatedStyle , useSharedValue , withTiming } from 'react-native-reanimated' ;
6
6
import DistanceEReceipt from '@components/DistanceEReceipt' ;
7
- import EReceipt from '@components/EReceipt' ;
8
- import BaseImage from '@components/Image/BaseImage' ;
7
+ import EReceiptWithSizeCalculation from '@components/EReceiptWithSizeCalculation' ;
9
8
import type { ImageOnLoadEvent } from '@components/Image/types' ;
10
9
import useDebouncedState from '@hooks/useDebouncedState' ;
11
10
import useResponsiveLayout from '@hooks/useResponsiveLayout' ;
11
+ import useTheme from '@hooks/useTheme' ;
12
12
import useThemeStyles from '@hooks/useThemeStyles' ;
13
13
import useWindowDimensions from '@hooks/useWindowDimensions' ;
14
14
import { isDistanceRequest } from '@libs/TransactionUtils' ;
15
15
import variables from '@styles/variables' ;
16
+ import Image from '@src/components/Image' ;
16
17
import CONST from '@src/CONST' ;
17
18
import type { Transaction } from '@src/types/onyx' ;
18
19
19
- const eReceiptAspectRatio = variables . eReceiptBGHWidth / variables . eReceiptBGHeight ;
20
-
21
20
type ReceiptPreviewProps = {
22
21
/** Path to the image to be opened in the preview */
23
22
source : string ;
@@ -35,14 +34,20 @@ type ReceiptPreviewProps = {
35
34
function ReceiptPreview ( { source, hovered, isEReceipt = false , transactionItem} : ReceiptPreviewProps ) {
36
35
const isDistanceEReceipt = isDistanceRequest ( transactionItem ) ;
37
36
const styles = useThemeStyles ( ) ;
37
+ const theme = useTheme ( ) ;
38
38
const [ eReceiptScaleFactor , setEReceiptScaleFactor ] = useState ( 0 ) ;
39
39
const [ imageAspectRatio , setImageAspectRatio ] = useState < string | number | undefined > ( undefined ) ;
40
40
const [ distanceEReceiptAspectRatio , setDistanceEReceiptAspectRatio ] = useState < string | number | undefined > ( undefined ) ;
41
41
const [ shouldShow , debounceShouldShow , setShouldShow ] = useDebouncedState ( false , CONST . TIMING . SHOW_HOVER_PREVIEW_DELAY ) ;
42
42
const { shouldUseNarrowLayout} = useResponsiveLayout ( ) ;
43
43
const hasMeasured = useRef ( false ) ;
44
44
const { windowHeight} = useWindowDimensions ( ) ;
45
+ const [ isLoading , setIsLoading ] = useState ( true ) ;
46
+ const containerHeight = useSharedValue ( 150 ) ;
45
47
48
+ const animatedContainerStyle = useAnimatedStyle ( ( ) => ( {
49
+ height : containerHeight . get ( ) ,
50
+ } ) ) ;
46
51
const handleDistanceEReceiptLayout = ( e : LayoutChangeEvent ) => {
47
52
if ( hasMeasured . current ) {
48
53
return ;
@@ -57,8 +62,10 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}:
57
62
}
58
63
if ( height * eReceiptScaleFactor > windowHeight - CONST . RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN ) {
59
64
setDistanceEReceiptAspectRatio ( variables . eReceiptBGHWidth / ( windowHeight - CONST . RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN ) ) ;
65
+ containerHeight . set ( withTiming ( windowHeight - CONST . RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN , { duration : 300 } ) ) ;
60
66
return ;
61
67
}
68
+ containerHeight . set ( withTiming ( height , { duration : 300 } ) ) ;
62
69
setDistanceEReceiptAspectRatio ( variables . eReceiptBGHWidth / height ) ;
63
70
setEReceiptScaleFactor ( width / variables . eReceiptBGHWidth ) ;
64
71
} ;
@@ -75,17 +82,21 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}:
75
82
) ;
76
83
77
84
const handleLoad = useCallback (
78
- ( event : ImageOnLoadEvent ) => {
79
- const { width, height} = event . nativeEvent ;
80
-
85
+ ( e : ImageOnLoadEvent ) => {
86
+ const { width, height} = e . nativeEvent ;
81
87
updateImageAspectRatio ( width , height ) ;
88
+ setIsLoading ( false ) ;
89
+
90
+ if ( width && height ) {
91
+ const scaledHeight = ( height / width ) * 380 ;
92
+ containerHeight . set ( withTiming ( scaledHeight , { duration : 300 } ) ) ;
93
+ }
82
94
} ,
83
- [ updateImageAspectRatio ] ,
95
+ [ updateImageAspectRatio , containerHeight ] ,
84
96
) ;
85
97
86
- const handleEReceiptLayout = ( e : LayoutChangeEvent ) => {
87
- const { width} = e . nativeEvent . layout ;
88
- setEReceiptScaleFactor ( width / variables . eReceiptBGHWidth ) ;
98
+ const handleError = ( ) => {
99
+ setIsLoading ( false ) ;
89
100
} ;
90
101
91
102
useEffect ( ( ) => {
@@ -103,22 +114,55 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}:
103
114
< Animated . View
104
115
entering = { FadeIn . duration ( CONST . TIMING . SHOW_HOVER_PREVIEW_ANIMATION_DURATION ) }
105
116
exiting = { FadeOut . duration ( CONST . TIMING . SHOW_HOVER_PREVIEW_ANIMATION_DURATION ) }
106
- style = { [ styles . receiptPreview , styles . flexColumn , styles . alignItemsCenter , styles . justifyContentStart ] }
117
+ style = { [
118
+ styles . receiptPreview ,
119
+ styles . flexColumn ,
120
+ styles . alignItemsCenter ,
121
+ styles . justifyContentStart ,
122
+ animatedContainerStyle , // smoothly animated height
123
+ ] }
107
124
>
108
125
{ shouldShowImage ? (
109
126
< View style = { [ styles . w100 ] } >
110
- < BaseImage
127
+ { isLoading && (
128
+ < View style = { [ StyleSheet . absoluteFillObject , { top : 75 } ] } >
129
+ < ActivityIndicator
130
+ color = { theme . spinner }
131
+ size = "large"
132
+ />
133
+ </ View >
134
+ ) }
135
+
136
+ < Image
111
137
source = { { uri : source } }
112
- style = { [ styles . w100 , { aspectRatio : imageAspectRatio } ] }
138
+ style = { [
139
+ styles . w100 ,
140
+ { aspectRatio : imageAspectRatio ?? 1 } ,
141
+ isLoading && { opacity : 0 } , // hide until loaded
142
+ ] }
143
+ onLoadStart = { ( ) => {
144
+ if ( isLoading ) {
145
+ return ;
146
+ }
147
+ setIsLoading ( true ) ;
148
+ } }
149
+ onError = { handleError }
113
150
onLoad = { handleLoad }
151
+ isAuthTokenRequired
114
152
/>
115
153
</ View >
116
154
) : (
117
155
< View style = { styles . receiptPreviewEReceiptsContainer } >
118
156
{ shouldShowDistanceEReceipt ? (
119
157
< View
120
158
onLayout = { handleDistanceEReceiptLayout }
121
- style = { [ { transformOrigin : 'center' , scale : eReceiptScaleFactor , aspectRatio : distanceEReceiptAspectRatio } ] }
159
+ style = { [
160
+ {
161
+ transformOrigin : 'center' ,
162
+ scale : eReceiptScaleFactor ,
163
+ aspectRatio : distanceEReceiptAspectRatio ,
164
+ } ,
165
+ ] }
122
166
>
123
167
< DistanceEReceipt
124
168
transaction = { transactionItem }
@@ -127,10 +171,11 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}:
127
171
</ View >
128
172
) : (
129
173
< View
130
- onLayout = { handleEReceiptLayout }
131
- style = { [ styles . receiptPreviewEReceipt , { aspectRatio : eReceiptAspectRatio , scale : eReceiptScaleFactor } ] }
174
+ onLayout = { ( ) => {
175
+ containerHeight . set ( withTiming ( variables . eReceiptBGHeight , { duration : 300 } ) ) ;
176
+ } }
132
177
>
133
- < EReceipt
178
+ < EReceiptWithSizeCalculation
134
179
transactionID = { transactionItem . transactionID }
135
180
transactionItem = { transactionItem }
136
181
/>
0 commit comments