mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
updated contribution page to show special mentions
This commit is contained in:
parent
d9aaa045fd
commit
6e3f79a231
2 changed files with 445 additions and 88 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -31,9 +31,9 @@ yarn-error.*
|
|||
*.pem
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
.env*.local
|
||||
.env
|
||||
# Sentry
|
||||
ios/sentry.properties
|
||||
android/sentry.properties
|
||||
|
|
|
|||
|
|
@ -12,13 +12,14 @@ import {
|
|||
Linking,
|
||||
RefreshControl,
|
||||
FlatList,
|
||||
ActivityIndicator
|
||||
ActivityIndicator,
|
||||
Alert
|
||||
} from 'react-native';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NavigationProp } from '@react-navigation/native';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import { Feather } from '@expo/vector-icons';
|
||||
import { Feather, FontAwesome5 } from '@expo/vector-icons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { fetchContributors, GitHubContributor } from '../services/githubReleaseService';
|
||||
|
|
@ -30,6 +31,48 @@ const isLargeTablet = width >= 1024;
|
|||
|
||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||
|
||||
// Discord API URL from environment
|
||||
const DISCORD_USER_API = process.env.EXPO_PUBLIC_DISCORD_USER_API || 'https://pfpfinder.com/api/discord/user';
|
||||
|
||||
// Discord brand color
|
||||
const DISCORD_BRAND_COLOR = '#5865F2';
|
||||
|
||||
// Special mentions - Discord community members (only store IDs and roles)
|
||||
interface SpecialMentionConfig {
|
||||
discordId: string;
|
||||
role: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface DiscordUserData {
|
||||
id: string;
|
||||
global_name: string | null;
|
||||
username: string;
|
||||
avatar: string | null;
|
||||
}
|
||||
|
||||
interface SpecialMention extends SpecialMentionConfig {
|
||||
name: string;
|
||||
username: string;
|
||||
avatarUrl: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const SPECIAL_MENTIONS_CONFIG: SpecialMentionConfig[] = [
|
||||
{
|
||||
discordId: '709281623866081300',
|
||||
role: 'Community Manager',
|
||||
description: 'Manages the Discord & Reddit communities for Nuvio',
|
||||
},
|
||||
{
|
||||
discordId: '777773947071758336',
|
||||
role: 'Server Sponsor',
|
||||
description: 'Sponsored the server infrastructure for Nuvio',
|
||||
},
|
||||
];
|
||||
|
||||
type TabType = 'contributors' | 'special';
|
||||
|
||||
interface ContributorCardProps {
|
||||
contributor: GitHubContributor;
|
||||
currentTheme: any;
|
||||
|
|
@ -86,15 +129,174 @@ const ContributorCard: React.FC<ContributorCardProps> = ({ contributor, currentT
|
|||
);
|
||||
};
|
||||
|
||||
// Special Mention Card Component - Same layout as ContributorCard
|
||||
interface SpecialMentionCardProps {
|
||||
mention: SpecialMention;
|
||||
currentTheme: any;
|
||||
isTablet: boolean;
|
||||
isLargeTablet: boolean;
|
||||
}
|
||||
|
||||
const SpecialMentionCard: React.FC<SpecialMentionCardProps> = ({ mention, currentTheme, isTablet, isLargeTablet }) => {
|
||||
const handlePress = useCallback(() => {
|
||||
// Try to open Discord profile
|
||||
const discordUrl = `discord://-/users/${mention.discordId}`;
|
||||
Linking.canOpenURL(discordUrl).then((supported) => {
|
||||
if (supported) {
|
||||
Linking.openURL(discordUrl);
|
||||
} else {
|
||||
// Fallback: show alert with Discord info
|
||||
Alert.alert(
|
||||
mention.name,
|
||||
`Discord: @${mention.username}\n\nOpen Discord and search for this user to connect with them.`,
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
}
|
||||
});
|
||||
}, [mention.discordId, mention.name, mention.username]);
|
||||
|
||||
// Default avatar fallback
|
||||
const defaultAvatar = `https://cdn.discordapp.com/embed/avatars/0.png`;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.contributorCard,
|
||||
{ backgroundColor: currentTheme.colors.elevation1 },
|
||||
isTablet && styles.tabletContributorCard
|
||||
]}
|
||||
onPress={handlePress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{/* Avatar with Discord badge */}
|
||||
<View style={styles.specialAvatarContainer}>
|
||||
{mention.isLoading ? (
|
||||
<View style={[
|
||||
styles.avatar,
|
||||
isTablet && styles.tabletAvatar,
|
||||
{ backgroundColor: currentTheme.colors.elevation2, justifyContent: 'center', alignItems: 'center' }
|
||||
]}>
|
||||
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
|
||||
</View>
|
||||
) : (
|
||||
<FastImage
|
||||
source={{ uri: mention.avatarUrl || defaultAvatar }}
|
||||
style={[
|
||||
styles.avatar,
|
||||
isTablet && styles.tabletAvatar
|
||||
]}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
)}
|
||||
<View style={[styles.discordBadgeSmall, { backgroundColor: DISCORD_BRAND_COLOR }]}>
|
||||
<FontAwesome5 name="discord" size={10} color="#FFFFFF" />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* User info */}
|
||||
<View style={styles.contributorInfo}>
|
||||
<Text style={[
|
||||
styles.username,
|
||||
{ color: currentTheme.colors.highEmphasis },
|
||||
isTablet && styles.tabletUsername
|
||||
]}>
|
||||
{mention.isLoading ? 'Loading...' : mention.name}
|
||||
</Text>
|
||||
{!mention.isLoading && mention.username && (
|
||||
<Text style={[
|
||||
styles.contributions,
|
||||
{ color: currentTheme.colors.mediumEmphasis },
|
||||
isTablet && styles.tabletContributions
|
||||
]}>
|
||||
@{mention.username}
|
||||
</Text>
|
||||
)}
|
||||
<View style={[styles.roleBadgeSmall, { backgroundColor: currentTheme.colors.primary + '20' }]}>
|
||||
<Text style={[styles.roleBadgeText, { color: currentTheme.colors.primary }]}>
|
||||
{mention.role}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Discord icon on right */}
|
||||
<FontAwesome5
|
||||
name="discord"
|
||||
size={isTablet ? 20 : 16}
|
||||
color={currentTheme.colors.mediumEmphasis}
|
||||
style={styles.externalIcon}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const ContributorsScreen: React.FC = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
|
||||
const [activeTab, setActiveTab] = useState<TabType>('contributors');
|
||||
const [contributors, setContributors] = useState<GitHubContributor[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [specialMentions, setSpecialMentions] = useState<SpecialMention[]>([]);
|
||||
const [specialMentionsLoading, setSpecialMentionsLoading] = useState(true);
|
||||
|
||||
// Fetch Discord user data for special mentions
|
||||
const loadSpecialMentions = useCallback(async () => {
|
||||
setSpecialMentionsLoading(true);
|
||||
|
||||
// Initialize with loading state
|
||||
const initialMentions: SpecialMention[] = SPECIAL_MENTIONS_CONFIG.map(config => ({
|
||||
...config,
|
||||
name: 'Loading...',
|
||||
username: '',
|
||||
avatarUrl: '',
|
||||
isLoading: true,
|
||||
}));
|
||||
setSpecialMentions(initialMentions);
|
||||
|
||||
// Fetch each user's data from Discord API
|
||||
const fetchedMentions = await Promise.all(
|
||||
SPECIAL_MENTIONS_CONFIG.map(async (config): Promise<SpecialMention> => {
|
||||
try {
|
||||
const response = await fetch(`${DISCORD_USER_API}/${config.discordId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch Discord user');
|
||||
}
|
||||
const userData: DiscordUserData = await response.json();
|
||||
|
||||
return {
|
||||
...config,
|
||||
name: userData.global_name || userData.username,
|
||||
username: userData.username,
|
||||
avatarUrl: userData.avatar || '',
|
||||
isLoading: false,
|
||||
};
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error(`Error fetching Discord user ${config.discordId}:`, error);
|
||||
// Return fallback data
|
||||
return {
|
||||
...config,
|
||||
name: 'Discord User',
|
||||
username: config.discordId,
|
||||
avatarUrl: '',
|
||||
isLoading: false,
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
setSpecialMentions(fetchedMentions);
|
||||
setSpecialMentionsLoading(false);
|
||||
}, []);
|
||||
|
||||
// Load special mentions when switching to that tab
|
||||
useEffect(() => {
|
||||
if (activeTab === 'special' && specialMentions.length === 0) {
|
||||
loadSpecialMentions();
|
||||
}
|
||||
}, [activeTab, specialMentions.length, loadSpecialMentions]);
|
||||
|
||||
const loadContributors = useCallback(async (isRefresh = false) => {
|
||||
try {
|
||||
|
|
@ -104,7 +306,7 @@ const ContributorsScreen: React.FC = () => {
|
|||
setLoading(true);
|
||||
}
|
||||
setError(null);
|
||||
|
||||
|
||||
// Check cache first (unless refreshing)
|
||||
if (!isRefresh) {
|
||||
try {
|
||||
|
|
@ -112,7 +314,7 @@ const ContributorsScreen: React.FC = () => {
|
|||
const cacheTimestamp = await mmkvStorage.getItem('github_contributors_timestamp');
|
||||
const now = Date.now();
|
||||
const ONE_HOUR = 60 * 60 * 1000; // 1 hour cache
|
||||
|
||||
|
||||
if (cachedData && cacheTimestamp) {
|
||||
const timestamp = parseInt(cacheTimestamp, 10);
|
||||
if (now - timestamp < ONE_HOUR) {
|
||||
|
|
@ -136,10 +338,10 @@ const ContributorsScreen: React.FC = () => {
|
|||
try {
|
||||
await mmkvStorage.removeItem('github_contributors');
|
||||
await mmkvStorage.removeItem('github_contributors_timestamp');
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const data = await fetchContributors();
|
||||
if (data && Array.isArray(data) && data.length > 0) {
|
||||
setContributors(data);
|
||||
|
|
@ -155,7 +357,7 @@ const ContributorsScreen: React.FC = () => {
|
|||
try {
|
||||
await mmkvStorage.removeItem('github_contributors');
|
||||
await mmkvStorage.removeItem('github_contributors_timestamp');
|
||||
} catch {}
|
||||
} catch { }
|
||||
setError('Unable to load contributors. This might be due to GitHub API rate limits.');
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -184,7 +386,7 @@ const ContributorsScreen: React.FC = () => {
|
|||
if (__DEV__) console.error('Error checking cache on mount:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
clearInvalidCache();
|
||||
loadContributors();
|
||||
}, [loadContributors]);
|
||||
|
|
@ -247,7 +449,7 @@ const ContributorsScreen: React.FC = () => {
|
|||
{ backgroundColor: currentTheme.colors.darkBackground }
|
||||
]}>
|
||||
<StatusBar barStyle={'light-content'} />
|
||||
|
||||
|
||||
<View style={[styles.headerContainer, { paddingTop: topSpacing }]}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
|
|
@ -267,85 +469,176 @@ const ContributorsScreen: React.FC = () => {
|
|||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Tab Switcher */}
|
||||
<View style={[
|
||||
styles.tabSwitcher,
|
||||
{ backgroundColor: currentTheme.colors.elevation1 },
|
||||
isTablet && styles.tabletTabSwitcher
|
||||
]}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.tab,
|
||||
activeTab === 'contributors' && { backgroundColor: currentTheme.colors.primary },
|
||||
isTablet && styles.tabletTab
|
||||
]}
|
||||
onPress={() => setActiveTab('contributors')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[
|
||||
styles.tabText,
|
||||
{ color: activeTab === 'contributors' ? currentTheme.colors.white : currentTheme.colors.mediumEmphasis },
|
||||
isTablet && styles.tabletTabText
|
||||
]}>
|
||||
Contributors
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.tab,
|
||||
activeTab === 'special' && { backgroundColor: currentTheme.colors.primary },
|
||||
isTablet && styles.tabletTab
|
||||
]}
|
||||
onPress={() => setActiveTab('special')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[
|
||||
styles.tabText,
|
||||
{ color: activeTab === 'special' ? currentTheme.colors.white : currentTheme.colors.mediumEmphasis },
|
||||
isTablet && styles.tabletTabText
|
||||
]}>
|
||||
Special Mentions
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View style={styles.content}>
|
||||
<View style={[styles.contentContainer, isTablet && styles.tabletContentContainer]}>
|
||||
{error ? (
|
||||
<View style={styles.errorContainer}>
|
||||
<Feather name="alert-circle" size={48} color={currentTheme.colors.mediumEmphasis} />
|
||||
<Text style={[styles.errorText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
{error}
|
||||
</Text>
|
||||
<Text style={[styles.errorSubtext, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
GitHub API rate limit exceeded. Please try again later or pull to refresh.
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.retryButton, { backgroundColor: currentTheme.colors.primary }]}
|
||||
onPress={() => loadContributors()}
|
||||
>
|
||||
<Text style={[styles.retryText, { color: currentTheme.colors.white }]}>
|
||||
Try Again
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : contributors.length === 0 ? (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Feather name="users" size={48} color={currentTheme.colors.mediumEmphasis} />
|
||||
<Text style={[styles.emptyText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
No contributors found
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={[
|
||||
styles.listContent,
|
||||
isTablet && styles.tabletListContent
|
||||
]}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
tintColor={currentTheme.colors.primary}
|
||||
colors={[currentTheme.colors.primary]}
|
||||
/>
|
||||
}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={[
|
||||
styles.gratitudeCard,
|
||||
{ backgroundColor: currentTheme.colors.elevation1 },
|
||||
isTablet && styles.tabletGratitudeCard
|
||||
]}>
|
||||
<View style={styles.gratitudeContent}>
|
||||
<Feather name="heart" size={isTablet ? 32 : 24} color={currentTheme.colors.primary} />
|
||||
<Text style={[
|
||||
styles.gratitudeText,
|
||||
{ color: currentTheme.colors.highEmphasis },
|
||||
isTablet && styles.tabletGratitudeText
|
||||
]}>
|
||||
We're grateful for every contribution
|
||||
</Text>
|
||||
<Text style={[
|
||||
styles.gratitudeSubtext,
|
||||
{ color: currentTheme.colors.mediumEmphasis },
|
||||
isTablet && styles.tabletGratitudeSubtext
|
||||
]}>
|
||||
Each line of code, bug report, and suggestion helps make Nuvio better for everyone
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={contributors}
|
||||
renderItem={renderContributor}
|
||||
keyExtractor={keyExtractor}
|
||||
numColumns={isTablet ? 2 : 1}
|
||||
key={isTablet ? 'tablet' : 'mobile'}
|
||||
scrollEnabled={false}
|
||||
{activeTab === 'contributors' ? (
|
||||
// Contributors Tab
|
||||
<>
|
||||
{error ? (
|
||||
<View style={styles.errorContainer}>
|
||||
<Feather name="alert-circle" size={48} color={currentTheme.colors.mediumEmphasis} />
|
||||
<Text style={[styles.errorText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
{error}
|
||||
</Text>
|
||||
<Text style={[styles.errorSubtext, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
GitHub API rate limit exceeded. Please try again later or pull to refresh.
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.retryButton, { backgroundColor: currentTheme.colors.primary }]}
|
||||
onPress={() => loadContributors()}
|
||||
>
|
||||
<Text style={[styles.retryText, { color: currentTheme.colors.white }]}>
|
||||
Try Again
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : contributors.length === 0 ? (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Feather name="users" size={48} color={currentTheme.colors.mediumEmphasis} />
|
||||
<Text style={[styles.emptyText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
No contributors found
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={[
|
||||
styles.listContent,
|
||||
isTablet && styles.tabletListContent
|
||||
]}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
tintColor={currentTheme.colors.primary}
|
||||
colors={[currentTheme.colors.primary]}
|
||||
/>
|
||||
}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={[
|
||||
styles.gratitudeCard,
|
||||
{ backgroundColor: currentTheme.colors.elevation1 },
|
||||
isTablet && styles.tabletGratitudeCard
|
||||
]}>
|
||||
<View style={styles.gratitudeContent}>
|
||||
<Feather name="heart" size={isTablet ? 32 : 24} color={currentTheme.colors.primary} />
|
||||
<Text style={[
|
||||
styles.gratitudeText,
|
||||
{ color: currentTheme.colors.highEmphasis },
|
||||
isTablet && styles.tabletGratitudeText
|
||||
]}>
|
||||
We're grateful for every contribution
|
||||
</Text>
|
||||
<Text style={[
|
||||
styles.gratitudeSubtext,
|
||||
{ color: currentTheme.colors.mediumEmphasis },
|
||||
isTablet && styles.tabletGratitudeSubtext
|
||||
]}>
|
||||
Each line of code, bug report, and suggestion helps make Nuvio better for everyone
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={contributors}
|
||||
renderItem={renderContributor}
|
||||
keyExtractor={keyExtractor}
|
||||
numColumns={isTablet ? 2 : 1}
|
||||
key={isTablet ? 'tablet' : 'mobile'}
|
||||
scrollEnabled={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
columnWrapperStyle={isTablet ? styles.tabletRow : undefined}
|
||||
/>
|
||||
</ScrollView>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// Special Mentions Tab
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={[
|
||||
styles.listContent,
|
||||
isTablet && styles.tabletListContent
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
columnWrapperStyle={isTablet ? styles.tabletRow : undefined}
|
||||
/>
|
||||
</ScrollView>
|
||||
>
|
||||
<View style={[
|
||||
styles.gratitudeCard,
|
||||
{ backgroundColor: currentTheme.colors.elevation1 },
|
||||
isTablet && styles.tabletGratitudeCard
|
||||
]}>
|
||||
<View style={styles.gratitudeContent}>
|
||||
<FontAwesome5 name="star" size={isTablet ? 32 : 24} color={currentTheme.colors.primary} solid />
|
||||
<Text style={[
|
||||
styles.gratitudeText,
|
||||
{ color: currentTheme.colors.highEmphasis },
|
||||
isTablet && styles.tabletGratitudeText
|
||||
]}>
|
||||
Special Thanks
|
||||
</Text>
|
||||
<Text style={[
|
||||
styles.gratitudeSubtext,
|
||||
{ color: currentTheme.colors.mediumEmphasis },
|
||||
isTablet && styles.tabletGratitudeSubtext
|
||||
]}>
|
||||
These amazing people help keep the Nuvio community running and the servers online
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{specialMentions.map((mention: SpecialMention) => (
|
||||
<SpecialMentionCard
|
||||
key={mention.discordId}
|
||||
mention={mention}
|
||||
currentTheme={currentTheme}
|
||||
isTablet={isTablet}
|
||||
isLargeTablet={isLargeTablet}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -563,6 +856,70 @@ const styles = StyleSheet.create({
|
|||
externalIcon: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
// Special Mentions - Compact styles for horizontal layout
|
||||
specialAvatarContainer: {
|
||||
position: 'relative',
|
||||
marginRight: 16,
|
||||
},
|
||||
discordBadgeSmall: {
|
||||
position: 'absolute',
|
||||
bottom: -2,
|
||||
right: -2,
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: '#1a1a1a',
|
||||
},
|
||||
roleBadgeSmall: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 10,
|
||||
marginTop: 4,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
roleBadgeText: {
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.3,
|
||||
},
|
||||
// Tab Switcher Styles
|
||||
tabSwitcher: {
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
padding: 4,
|
||||
borderRadius: 12,
|
||||
},
|
||||
tabletTabSwitcher: {
|
||||
marginHorizontal: 32,
|
||||
marginBottom: 24,
|
||||
padding: 6,
|
||||
borderRadius: 16,
|
||||
},
|
||||
tab: {
|
||||
flex: 1,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
tabletTab: {
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: 12,
|
||||
},
|
||||
tabText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
tabletTabText: {
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
|
||||
export default ContributorsScreen;
|
||||
|
|
|
|||
Loading…
Reference in a new issue