11import 'react-native-url-polyfill/auto' ;
2- import React , { useEffect , useState } from 'react' ;
2+ import React , { useEffect , useState , useRef } from 'react' ;
33import {
44 StyleSheet ,
55 Text ,
66 View ,
77 FlatList ,
88 StatusBar ,
9- ActivityIndicator ,
109 RefreshControl ,
1110 Dimensions ,
11+ Animated ,
12+ Easing ,
1213} from 'react-native' ;
1314import { fetchApps , AppModel } from './src/config/supabase' ;
1415import { AppCard } from './src/components/AppCard' ;
@@ -17,6 +18,67 @@ import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
1718
1819const { width, height } = Dimensions . get ( 'window' ) ;
1920
21+ // --- Skeleton Card Component ---
22+ const SkeletonCard = ( ) => {
23+ const shimmer = useRef ( new Animated . Value ( 0 ) ) . current ;
24+
25+ useEffect ( ( ) => {
26+ const loop = Animated . loop (
27+ Animated . sequence ( [
28+ Animated . timing ( shimmer , {
29+ toValue : 1 ,
30+ duration : 1200 ,
31+ easing : Easing . ease ,
32+ useNativeDriver : true ,
33+ } ) ,
34+ Animated . timing ( shimmer , {
35+ toValue : 0 ,
36+ duration : 1200 ,
37+ easing : Easing . ease ,
38+ useNativeDriver : true ,
39+ } ) ,
40+ ] )
41+ ) ;
42+ loop . start ( ) ;
43+ return ( ) => loop . stop ( ) ;
44+ } , [ shimmer ] ) ;
45+
46+ const opacity = shimmer . interpolate ( {
47+ inputRange : [ 0 , 1 ] ,
48+ outputRange : [ 0.3 , 0.7 ] ,
49+ } ) ;
50+
51+ return (
52+ < View style = { skeletonStyles . card } >
53+ < Animated . View style = { [ skeletonStyles . iconBox , { opacity } ] } />
54+ < View style = { skeletonStyles . textArea } >
55+ < Animated . View style = { [ skeletonStyles . lineWide , { opacity } ] } />
56+ < Animated . View style = { [ skeletonStyles . lineNarrow , { opacity } ] } />
57+ < View style = { { flexDirection : 'row' , gap : 6 , marginTop : 6 } } >
58+ < Animated . View style = { [ skeletonStyles . pill , { opacity } ] } />
59+ < Animated . View style = { [ skeletonStyles . pill , { opacity, width : 50 } ] } />
60+ </ View >
61+ </ View >
62+ < Animated . View style = { [ skeletonStyles . btn , { opacity } ] } />
63+ </ View >
64+ ) ;
65+ } ;
66+
67+ const SkeletonList = ( ) => (
68+ < View style = { { paddingTop : StatusBar . currentHeight ? StatusBar . currentHeight + 16 : 56 } } >
69+ { /* Skeleton header */ }
70+ < View style = { skeletonStyles . header } >
71+ < View >
72+ < View style = { [ skeletonStyles . lineNarrow , { width : 100 , marginBottom : 8 } ] } />
73+ < View style = { [ skeletonStyles . lineWide , { width : 160 , height : 26 } ] } />
74+ </ View >
75+ < View style = { skeletonStyles . avatarSkel } />
76+ </ View >
77+ < View style = { [ skeletonStyles . lineNarrow , { width : 80 , marginLeft : 22 , marginBottom : 16 } ] } />
78+ { [ 0 , 1 , 2 , 3 ] . map ( i => < SkeletonCard key = { i } /> ) }
79+ </ View >
80+ ) ;
81+
2082function App ( ) : React . JSX . Element {
2183 const [ apps , setApps ] = useState < AppModel [ ] > ( [ ] ) ;
2284 const [ loading , setLoading ] = useState ( true ) ;
@@ -52,24 +114,26 @@ function App(): React.JSX.Element {
52114
53115 const closePopup = ( ) => {
54116 setPopupVisible ( false ) ;
55- setTimeout ( ( ) => setSelectedApp ( null ) , 300 ) ;
117+ setTimeout ( ( ) => setSelectedApp ( null ) , 350 ) ;
56118 } ;
57119
58120 const renderHeader = ( ) => (
59121 < View style = { styles . header } >
60- { /* Top bar */ }
61122 < View style = { styles . topBar } >
62123 < View >
63- < Text style = { styles . greeting } > Welcome back 👋</ Text >
124+ < View style = { { flexDirection : 'row' , alignItems : 'center' , gap : 6 } } >
125+ < Text style = { styles . greeting } > Welcome back</ Text >
126+ < Icon name = "hand-wave" size = { 16 } color = "#FBBF24" />
127+ </ View >
64128 < Text style = { styles . title } > Zeeshan Hub</ Text >
65129 </ View >
66130 < View style = { styles . avatar } >
67131 < Text style = { styles . avatarLetter } > Z</ Text >
68132 </ View >
69133 </ View >
70134
71- { /* Section title */ }
72135 < View style = { styles . sectionRow } >
136+ < Icon name = "apps" size = { 18 } color = "#A78BFA" />
73137 < Text style = { styles . sectionTitle } > Your Apps</ Text >
74138 < View style = { styles . countBadge } >
75139 < Text style = { styles . countText } > { apps . length } </ Text >
@@ -82,18 +146,15 @@ function App(): React.JSX.Element {
82146 < View style = { styles . container } >
83147 < StatusBar barStyle = "light-content" backgroundColor = "transparent" translucent />
84148
85- { /* Background orbs */ }
86- < View style = { styles . orb1 } />
87- < View style = { styles . orb2 } />
88- < View style = { styles . orb3 } />
149+ { /* Layered background for depth */ }
150+ < View style = { styles . bgBase } />
151+ < View style = { styles . bgOrb1 } />
152+ < View style = { styles . bgOrb2 } />
153+ < View style = { styles . bgOrb3 } />
154+ < View style = { styles . bgTopStrip } />
89155
90156 { loading && ! refreshing ? (
91- < View style = { styles . loadingWrap } >
92- < View style = { styles . loadingRing } >
93- < ActivityIndicator size = "large" color = "#A78BFA" />
94- </ View >
95- < Text style = { styles . loadingText } > Loading apps…</ Text >
96- </ View >
157+ < SkeletonList />
97158 ) : (
98159 < FlatList
99160 data = { apps }
@@ -114,10 +175,10 @@ function App(): React.JSX.Element {
114175 ListEmptyComponent = {
115176 < View style = { styles . emptyWrap } >
116177 < View style = { styles . emptyIcon } >
117- < Icon name = "package-variant" size = { 56 } color = "rgba(167, 139, 250, 0.3 )" />
178+ < Icon name = "package-variant" size = { 48 } color = "rgba(167, 139, 250, 0.4 )" />
118179 </ View >
119180 < Text style = { styles . emptyTitle } > No apps yet</ Text >
120- < Text style = { styles . emptyDesc } > Upload your first app via the{ '\n' } Admin Portal to see it here.</ Text >
181+ < Text style = { styles . emptyDesc } > Upload your first app via the Admin Portal to see it here.</ Text >
121182 </ View >
122183 }
123184 />
@@ -135,58 +196,47 @@ function App(): React.JSX.Element {
135196const styles = StyleSheet . create ( {
136197 container : {
137198 flex : 1 ,
138- backgroundColor : '#0A0E1A ' ,
199+ backgroundColor : '#080B16 ' ,
139200 } ,
140- // Background decorative orbs
141- orb1 : {
142- position : 'absolute' ,
143- top : - height * 0.08 ,
144- left : - width * 0.25 ,
145- width : width * 0.7 ,
146- height : width * 0.7 ,
147- borderRadius : width * 0.35 ,
148- backgroundColor : 'rgba(124, 58, 237, 0.08)' ,
201+ // Multi-layer background
202+ bgBase : {
203+ ...StyleSheet . absoluteFillObject ,
204+ backgroundColor : '#080B16' ,
149205 } ,
150- orb2 : {
206+ bgOrb1 : {
151207 position : 'absolute' ,
152- top : height * 0.35 ,
153- right : - width * 0.3 ,
154- width : width * 0.8 ,
155- height : width * 0.8 ,
156- borderRadius : width * 0.4 ,
157- backgroundColor : 'rgba(56, 189, 248 , 0.05 )' ,
208+ top : - height * 0.12 ,
209+ left : - width * 0.3 ,
210+ width : width * 0.9 ,
211+ height : width * 0.9 ,
212+ borderRadius : width * 0.45 ,
213+ backgroundColor : 'rgba(124, 58, 237 , 0.06 )' ,
158214 } ,
159- orb3 : {
215+ bgOrb2 : {
160216 position : 'absolute' ,
161- bottom : - height * 0.1 ,
162- left : width * 0.1 ,
163- width : width * 0.5 ,
164- height : width * 0.5 ,
165- borderRadius : width * 0.25 ,
166- backgroundColor : 'rgba(167, 139, 250 , 0.04 )' ,
217+ top : height * 0.4 ,
218+ right : - width * 0.35 ,
219+ width : width ,
220+ height : width ,
221+ borderRadius : width * 0.5 ,
222+ backgroundColor : 'rgba(56, 189, 248 , 0.035 )' ,
167223 } ,
168- // Loading
169- loadingWrap : {
170- flex : 1 ,
171- justifyContent : 'center' ,
172- alignItems : 'center' ,
173- } ,
174- loadingRing : {
175- width : 80 ,
176- height : 80 ,
177- borderRadius : 40 ,
178- backgroundColor : 'rgba(124, 58, 237, 0.08)' ,
179- justifyContent : 'center' ,
180- alignItems : 'center' ,
181- marginBottom : 16 ,
182- borderWidth : 1 ,
183- borderColor : 'rgba(167, 139, 250, 0.15)' ,
224+ bgOrb3 : {
225+ position : 'absolute' ,
226+ bottom : - height * 0.15 ,
227+ left : - width * 0.1 ,
228+ width : width * 0.7 ,
229+ height : width * 0.7 ,
230+ borderRadius : width * 0.35 ,
231+ backgroundColor : 'rgba(167, 139, 250, 0.03)' ,
184232 } ,
185- loadingText : {
186- color : '#A78BFA' ,
187- fontSize : 15 ,
188- fontWeight : '600' ,
189- letterSpacing : 0.3 ,
233+ bgTopStrip : {
234+ position : 'absolute' ,
235+ top : 0 ,
236+ left : 0 ,
237+ right : 0 ,
238+ height : StatusBar . currentHeight ? StatusBar . currentHeight + 80 : 120 ,
239+ backgroundColor : 'rgba(124, 58, 237, 0.04)' ,
190240 } ,
191241 // List
192242 listContent : {
@@ -208,14 +258,14 @@ const styles = StyleSheet.create({
208258 fontSize : 14 ,
209259 color : '#64748B' ,
210260 fontWeight : '500' ,
211- marginBottom : 4 ,
212261 letterSpacing : 0.3 ,
213262 } ,
214263 title : {
215- fontSize : 30 ,
264+ fontSize : 28 ,
216265 fontWeight : '800' ,
217266 color : '#F8FAFC' ,
218267 letterSpacing : 0.3 ,
268+ marginTop : 4 ,
219269 } ,
220270 avatar : {
221271 width : 46 ,
@@ -242,7 +292,7 @@ const styles = StyleSheet.create({
242292 marginBottom : 4 ,
243293 } ,
244294 sectionTitle : {
245- fontSize : 18 ,
295+ fontSize : 17 ,
246296 fontWeight : '700' ,
247297 color : '#CBD5E1' ,
248298 letterSpacing : 0.3 ,
@@ -268,13 +318,13 @@ const styles = StyleSheet.create({
268318 paddingHorizontal : 40 ,
269319 } ,
270320 emptyIcon : {
271- width : 100 ,
272- height : 100 ,
273- borderRadius : 30 ,
321+ width : 90 ,
322+ height : 90 ,
323+ borderRadius : 28 ,
274324 backgroundColor : 'rgba(124, 58, 237, 0.06)' ,
275325 justifyContent : 'center' ,
276326 alignItems : 'center' ,
277- marginBottom : 20 ,
327+ marginBottom : 18 ,
278328 borderWidth : 1 ,
279329 borderColor : 'rgba(167, 139, 250, 0.1)' ,
280330 } ,
@@ -292,4 +342,66 @@ const styles = StyleSheet.create({
292342 } ,
293343} ) ;
294344
345+ const skeletonStyles = StyleSheet . create ( {
346+ card : {
347+ marginHorizontal : 20 ,
348+ marginBottom : 14 ,
349+ borderRadius : 20 ,
350+ backgroundColor : '#161B2E' ,
351+ padding : 16 ,
352+ flexDirection : 'row' ,
353+ alignItems : 'center' ,
354+ borderWidth : 1 ,
355+ borderColor : 'rgba(255, 255, 255, 0.03)' ,
356+ } ,
357+ iconBox : {
358+ width : 58 ,
359+ height : 58 ,
360+ borderRadius : 16 ,
361+ backgroundColor : '#1E2440' ,
362+ } ,
363+ textArea : {
364+ flex : 1 ,
365+ marginLeft : 14 ,
366+ } ,
367+ lineWide : {
368+ width : '75%' ,
369+ height : 14 ,
370+ borderRadius : 7 ,
371+ backgroundColor : '#1E2440' ,
372+ marginBottom : 8 ,
373+ } ,
374+ lineNarrow : {
375+ width : '45%' ,
376+ height : 10 ,
377+ borderRadius : 5 ,
378+ backgroundColor : '#1E2440' ,
379+ } ,
380+ pill : {
381+ width : 40 ,
382+ height : 18 ,
383+ borderRadius : 6 ,
384+ backgroundColor : '#1E2440' ,
385+ } ,
386+ btn : {
387+ width : 62 ,
388+ height : 38 ,
389+ borderRadius : 12 ,
390+ backgroundColor : '#1E2440' ,
391+ } ,
392+ header : {
393+ paddingHorizontal : 22 ,
394+ flexDirection : 'row' ,
395+ justifyContent : 'space-between' ,
396+ alignItems : 'center' ,
397+ marginBottom : 28 ,
398+ } ,
399+ avatarSkel : {
400+ width : 46 ,
401+ height : 46 ,
402+ borderRadius : 15 ,
403+ backgroundColor : '#1E2440' ,
404+ } ,
405+ } ) ;
406+
295407export default App ;
0 commit comments