Skip to content

Commit

Permalink
Merge branch 'main' into feat/lfg
Browse files Browse the repository at this point in the history
  • Loading branch information
MSghais committed Oct 14, 2024
2 parents 71e9152 + 0b31fc3 commit 18b2660
Show file tree
Hide file tree
Showing 20 changed files with 1,771 additions and 20 deletions.
10 changes: 9 additions & 1 deletion apps/mobile/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@ EXPO_PUBLIC_DYNAMIC_API_KEY="DYNAMIC_API_KEY"
EXPO_PUBLIC_LAYERSWAP_CLIENT_ID=""
EXPO_PUBLIC_LAYERSWAP_API_KEY="API_KEY_LAYER_SWAP"

EXPO_PUBLIC_GOOGLE_TAG_ID=
EXPO_PUBLIC_GOOGLE_TAG_ID=


# EKUBU API
EXPO_PUBLIC_EKUBO_API="https://mainnet-api.ekubo.org"
EXPO_PUBLIC_EKUBO_ROUTE_API="https://quoter-mainnet-api.ekubo.org"

# AVNU API
EXPO_PUBLIC_AVNU_API="https://starknet.api.avnu.fi"
9 changes: 5 additions & 4 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
"name": "mobile",
"version": "1.0.0",
"main": "index.js",
"packageManager": "[email protected]",
"packageManager": "[email protected]",
"scripts": {
"dev": "expo start",
"build:web": "npx expo export --platform web",
"start": "expo start",
"start:tunnel": "expo start --tunnel",
"android": "expo run:android",
"build:android": "expo build:android",
"build:android:eas":"eas build -p android",
"build:android:eas": "eas build -p android",
"ios": "expo run:ios",
"build:ios": "expo build:ios",
"build:ios:eas":"eas build -p ios",
"build:ios:eas": "eas build -p ios",
"web": "expo start --web",
"postinstall": "patch-package",
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
Expand Down Expand Up @@ -75,6 +75,7 @@
"expo": "~51.0.28",
"expo-application": "^5.9.1",
"expo-auth-session": "^5.5.2",
"expo-av": "~14.0.7",
"expo-camera": "^15.0.16",
"expo-clipboard": "~6.0.3",
"expo-constants": "^16.0.2",
Expand Down Expand Up @@ -146,4 +147,4 @@
"typescript": "~5.3.3"
},
"private": true
}
}
Binary file removed apps/mobile/patches/@[email protected]
Binary file not shown.
4 changes: 4 additions & 0 deletions apps/mobile/src/app/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import GroupChatGroupRequest from '../modules/Group/memberAction/ViewRequest';
import GroupChat from '../modules/Group/message/GroupMessage';
import AuthSidebar from '../modules/Layout/auth-sidebar';
import Sidebar from '../modules/Layout/sidebar';
import RightSidebar from '../modules/Layout/RightSideBar';
import ShortVideosModule from '../modules/ShortVideos';
// import RightSidebar from '../modules/Layout/RightSideBar';

// Components
Expand Down Expand Up @@ -305,6 +307,8 @@ const MainNavigator: React.FC = () => {
<MainStack.Screen name="ImportKeys" component={ImportKeys} />

<MainStack.Screen name="Wallet" component={Wallet} />

<MainStack.Screen name="ShortVideos" component={ShortVideosModule} />
<MainStack.Screen name="Onboarding" component={Onboarding} />
</MainStack.Navigator>
);
Expand Down
29 changes: 16 additions & 13 deletions apps/mobile/src/app/Wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {EVMProvider} from './EVMProvider';
import {WalletModalEVMProvider} from '../context/WalletModalEvmProvider';
import {CashuProvider} from '../providers/CashuProvider';
import {dynamicClient} from './DynamicClient';
import {SwapModalEVMProvider} from '../context/SwapModalProvider';

const queryClient = new QueryClient({
defaultOptions: {queries: {retry: 2}},
Expand All @@ -31,19 +32,21 @@ const ModalProviders = ({children}: {children: React.ReactNode}) => {
return (
<ToastProvider>
<WalletModalEVMProvider>
<WalletModalProvider>
<TransactionModalProvider>
<TipModalProvider>
<TipModalStarknetProvider>
<TokenCreateModalProvider>
<KeyModalProvider>
<ModalParentProvider>{children}</ModalParentProvider>
</KeyModalProvider>
</TokenCreateModalProvider>
</TipModalStarknetProvider>
</TipModalProvider>
</TransactionModalProvider>
</WalletModalProvider>
<SwapModalEVMProvider>
<WalletModalProvider>
<TransactionModalProvider>
<TipModalProvider>
<TipModalStarknetProvider>
<TokenCreateModalProvider>
<KeyModalProvider>
<ModalParentProvider>{children}</ModalParentProvider>
</KeyModalProvider>
</TokenCreateModalProvider>
</TipModalStarknetProvider>
</TipModalProvider>
</TransactionModalProvider>
</WalletModalProvider>
</SwapModalEVMProvider>
</WalletModalEVMProvider>
</ToastProvider>
);
Expand Down
80 changes: 80 additions & 0 deletions apps/mobile/src/components/NostrVideo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {NostrEvent} from '@nostr-dev-kit/ndk';
import {ResizeMode, Video} from 'expo-av';
import React, {useEffect, useState} from 'react';
import {Pressable, TouchableOpacity, View} from 'react-native';

import {BookmarkIcon, LikeIcon, RepostIcon} from '../../assets/icons';
import {useStyles} from '../../hooks';
import stylesheet from './styles';

const NostrVideo = ({item, shouldPlay}: {shouldPlay: boolean; item: NostrEvent}) => {
const video = React.useRef<Video | null>(null);
const [status, setStatus] = useState<any>(null);
const styles = useStyles(stylesheet);

useEffect(() => {
if (!video.current) return;

if (shouldPlay) {
video.current.playAsync();
} else {
video.current.pauseAsync();
video.current.setPositionAsync(0);
}
}, [shouldPlay]);

const extractVideoURL = (event: NostrEvent) => {
return event?.tags?.find((tag) => tag?.[0] === 'url')?.[1] || '';
};

const handleLike = () => {
console.log('like');
//todo: integrate hook
};

const handleRepost = () => {
console.log('like');
//todo: integrate hook
};

const handleBookmark = () => {
console.log('like');
//todo: integrate hook
};

return (
<>
<Pressable
onPress={() =>
status.isPlaying ? video.current?.pauseAsync() : video.current?.playAsync()
}
>
<View style={styles.videoContainer}>
<Video
ref={video}
source={{uri: extractVideoURL(item)}}
style={styles.video}
isLooping
resizeMode={ResizeMode.COVER}
useNativeControls={false}
onPlaybackStatusUpdate={(status) => setStatus(() => status)}
videoStyle={styles.innerVideo}
/>
</View>
</Pressable>
<View style={styles.actionsContainer}>
<TouchableOpacity onPress={handleLike}>
<LikeIcon width={20} height={20} color="white" />
</TouchableOpacity>
<TouchableOpacity onPress={handleRepost}>
<RepostIcon width={20} height={20} color="white" />
</TouchableOpacity>
<TouchableOpacity style={{width: 15}} onPress={handleBookmark}>
<BookmarkIcon width={15} height={20} color="white" />
</TouchableOpacity>
</View>
</>
);
};

export default NostrVideo;
26 changes: 26 additions & 0 deletions apps/mobile/src/components/NostrVideo/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Dimensions} from 'react-native';

import {ThemedStyleSheet} from '../../styles';

export default ThemedStyleSheet(() => ({
videoContainer: {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height - 60,
},
video: {
width: '100%',
height: '100%',
},
innerVideo: {
height: Dimensions.get('window').height - 60,
},
actionsContainer: {
position: 'absolute',
right: 20,
bottom: 40,
display: 'flex',
flexDirection: 'column',
gap: 20,
alignItems: 'center',
},
}));
57 changes: 57 additions & 0 deletions apps/mobile/src/context/SwapModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {createContext, useCallback, useContext, useRef} from 'react';

import {Modalize} from '../components';
import TokenSwapView from '../modules/Swap';

// Create a context for Swap
export const SwapModalContext = createContext<{
showSwap: () => void;
hideSwap: () => void;
} | null>(null);

export const SwapModalEVMProvider: React.FC<React.PropsWithChildren> = ({children}) => {
const modalizeRef = useRef<Modalize | any>(null);

const showSwap = useCallback(() => {
modalizeRef?.current.open();
}, []);

const hideSwap = useCallback(() => {
modalizeRef?.current.close();
}, [modalizeRef]);

return (
<SwapModalContext.Provider
value={{
showSwap,
hideSwap,
}}
>
{children}

<Modalize
modalStyle={{
maxWidth: 500,
width: '100%',
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 20,
borderRadius: 20,
}}
ref={modalizeRef}
adjustToContentHeight
>
<TokenSwapView showHeader />
</Modalize>
</SwapModalContext.Provider>
);
};

export const useSwapModal = () => {
const context = useContext(SwapModalContext);
if (!context) {
throw new Error('useSwap must be used within a SwapProvider');
}

return context;
};
60 changes: 60 additions & 0 deletions apps/mobile/src/modules/ShortVideos/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {NostrEvent} from '@nostr-dev-kit/ndk';
import {useGetVideos} from 'afk_nostr_sdk';
import React, {useRef, useState} from 'react';
import {FlatList, Text, View} from 'react-native';

import {InfoIcon} from '../../assets/icons';
import NostrVideo from '../../components/NostrVideo';
import {useStyles, useTheme} from '../../hooks';
import {mockEvents} from '../../utils/dummyData';
import stylesheet from './styles';

const ShortVideosModule = () => {
const styles = useStyles(stylesheet);
const {theme} = useTheme();
const [currentViewableItemIndex, setCurrentViewableItemIndex] = useState(0);
const viewabilityConfig = {viewAreaCoveragePercentThreshold: 50};
const videos = useGetVideos();
const [videosEvents, setVideosEvents] = useState<NostrEvent[]>(
videos?.data?.pages?.flat() as NostrEvent[],
);

const fetchNostrEvents = async () => {
// This mock should be replaced with actual implementation (hook integration to get videos)
setVideosEvents(mockEvents);
};

const onViewableItemsChanged = ({viewableItems}: any) => {
if (viewableItems.length > 0) {
setCurrentViewableItemIndex(viewableItems[0].index ?? 0);
}
};

const viewabilityConfigCallbackPairs = useRef([{viewabilityConfig, onViewableItemsChanged}]);

return (
<View style={styles.container}>
{videosEvents.length > 0 ? (
<FlatList
style={styles.list}
data={videosEvents}
renderItem={({item, index}) => (
<NostrVideo item={item} shouldPlay={index === currentViewableItemIndex} />
)}
keyExtractor={(item, index) => item.content + index}
pagingEnabled
horizontal={false}
showsVerticalScrollIndicator={false}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
/>
) : (
<View style={styles.noDataContainer}>
<InfoIcon width={30} height={30} color={theme.colors.primary} />
<Text style={styles.noDataText}>No videos uploaded yet.</Text>
</View>
)}
</View>
);
};

export default ShortVideosModule;
24 changes: 24 additions & 0 deletions apps/mobile/src/modules/ShortVideos/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {ThemedStyleSheet} from '../../styles';

export default ThemedStyleSheet((theme) => ({
container: {
flex: 1,
height: '100%',
},
list: {
height: '100%',
},
noDataContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 20,
marginTop: 20,
},
noDataText: {
color: theme.colors.text,
textAlign: 'center',
fontSize: 14,
fontWeight: 'bold',
},
}));
Loading

0 comments on commit 18b2660

Please sign in to comment.