mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
adjusted plugintest screen layout for tablets
This commit is contained in:
parent
2169354f0d
commit
f865b737e6
5 changed files with 383 additions and 131 deletions
|
|
@ -6,14 +6,15 @@ import { useTheme } from '../contexts/ThemeContext';
|
||||||
import { RepoTester } from './plugin-tester/RepoTester';
|
import { RepoTester } from './plugin-tester/RepoTester';
|
||||||
import { IndividualTester } from './plugin-tester/IndividualTester';
|
import { IndividualTester } from './plugin-tester/IndividualTester';
|
||||||
import { Header, MainTabBar } from './plugin-tester/components';
|
import { Header, MainTabBar } from './plugin-tester/components';
|
||||||
import { getPluginTesterStyles } from './plugin-tester/styles';
|
import { getPluginTesterStyles, useIsLargeScreen } from './plugin-tester/styles';
|
||||||
|
|
||||||
const PluginTesterScreen = () => {
|
const PluginTesterScreen = () => {
|
||||||
const [mainTab, setMainTab] = useState<'individual' | 'repo'>('individual');
|
const [mainTab, setMainTab] = useState<'individual' | 'repo'>('individual');
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const styles = getPluginTesterStyles(currentTheme);
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
const styles = getPluginTesterStyles(currentTheme, isLargeScreen);
|
||||||
|
|
||||||
if (mainTab === 'individual') {
|
if (mainTab === 'individual') {
|
||||||
return <IndividualTester onSwitchTab={setMainTab} />;
|
return <IndividualTester onSwitchTab={setMainTab} />;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { useNavigation } from '@react-navigation/native';
|
||||||
import { useTheme } from '../../contexts/ThemeContext';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { pluginService } from '../../services/pluginService';
|
import { pluginService } from '../../services/pluginService';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { getPluginTesterStyles } from './styles';
|
import { getPluginTesterStyles, useIsLargeScreen } from './styles';
|
||||||
import { Header, MainTabBar } from './components';
|
import { Header, MainTabBar } from './components';
|
||||||
|
|
||||||
interface IndividualTesterProps {
|
interface IndividualTesterProps {
|
||||||
|
|
@ -27,7 +27,8 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const styles = getPluginTesterStyles(currentTheme);
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
const styles = getPluginTesterStyles(currentTheme, isLargeScreen);
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
|
|
@ -171,141 +172,361 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCodeTab = () => (
|
const renderCodeTab = () => {
|
||||||
<KeyboardAvoidingView
|
// On large screens, show code + logs/results side by side
|
||||||
style={{ flex: 1 }}
|
if (isLargeScreen) {
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
return (
|
||||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 60 : 0}
|
<View style={styles.largeScreenWrapper}>
|
||||||
>
|
<View style={styles.twoColumnContainer}>
|
||||||
<ScrollView style={styles.content} contentContainerStyle={{ paddingBottom: 10 }} keyboardShouldPersistTaps="handled">
|
<View style={styles.leftColumn}>
|
||||||
<View style={styles.card}>
|
<ScrollView style={styles.content} contentContainerStyle={{ paddingBottom: 10 }} keyboardShouldPersistTaps="handled">
|
||||||
<View style={styles.cardTitleRow}>
|
<View style={styles.card}>
|
||||||
<Text style={styles.cardTitle}>Load from URL</Text>
|
<View style={styles.cardTitleRow}>
|
||||||
<Ionicons name="link-outline" size={18} color={currentTheme.colors.mediumEmphasis} />
|
<Text style={styles.cardTitle}>Load from URL</Text>
|
||||||
|
<Ionicons name="link-outline" size={18} color={currentTheme.colors.mediumEmphasis} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.helperText}>
|
||||||
|
Paste a raw GitHub URL or local IP and tap download.
|
||||||
|
</Text>
|
||||||
|
<View style={[styles.row, { marginTop: 10 }]}>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.input, { flex: 1 }]}
|
||||||
|
value={url}
|
||||||
|
onChangeText={setUrl}
|
||||||
|
placeholder="http://192.168.1.5:8000/provider.js"
|
||||||
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.secondaryButton, { paddingHorizontal: 12, minHeight: 48 }]}
|
||||||
|
onPress={fetchFromUrl}
|
||||||
|
>
|
||||||
|
<Ionicons name="download-outline" size={20} color={currentTheme.colors.white} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[styles.card, { flex: 1, minHeight: 400 }]}>
|
||||||
|
<View style={styles.cardTitleRow}>
|
||||||
|
<Text style={styles.cardTitle}>Plugin Code</Text>
|
||||||
|
<View style={styles.cardActionsRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.cardActionButton}
|
||||||
|
onPress={() => setIsEditorFocused(true)}
|
||||||
|
accessibilityLabel="Focus code editor"
|
||||||
|
>
|
||||||
|
<Ionicons name="expand-outline" size={18} color={currentTheme.colors.highEmphasis} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Ionicons name="code-slash-outline" size={18} color={currentTheme.colors.mediumEmphasis} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.codeInput, { minHeight: 350 }]}
|
||||||
|
value={code}
|
||||||
|
onChangeText={setCode}
|
||||||
|
multiline
|
||||||
|
placeholder="// Paste plugin code here..."
|
||||||
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Test parameters on large screen */}
|
||||||
|
<View style={styles.card}>
|
||||||
|
<View style={styles.cardTitleRow}>
|
||||||
|
<Text style={styles.cardTitle}>Test Parameters</Text>
|
||||||
|
<Ionicons name="options-outline" size={16} color={currentTheme.colors.mediumEmphasis} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.segment}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.segmentItem, mediaType === 'movie' && styles.segmentItemActive]}
|
||||||
|
onPress={() => setMediaType('movie')}
|
||||||
|
>
|
||||||
|
<Ionicons name="film-outline" size={18} color={mediaType === 'movie' ? currentTheme.colors.primary : currentTheme.colors.highEmphasis} />
|
||||||
|
<Text style={[styles.segmentText, mediaType === 'movie' && styles.segmentTextActive]}>Movie</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.segmentItem, mediaType === 'tv' && styles.segmentItemActive]}
|
||||||
|
onPress={() => setMediaType('tv')}
|
||||||
|
>
|
||||||
|
<Ionicons name="tv-outline" size={18} color={mediaType === 'tv' ? currentTheme.colors.primary : currentTheme.colors.highEmphasis} />
|
||||||
|
<Text style={[styles.segmentText, mediaType === 'tv' && styles.segmentTextActive]}>TV</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[styles.row, { marginTop: 10, alignItems: 'flex-start' }]}>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={styles.fieldLabel}>TMDB ID</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={tmdbId}
|
||||||
|
onChangeText={setTmdbId}
|
||||||
|
keyboardType="numeric"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{mediaType === 'tv' && (
|
||||||
|
<>
|
||||||
|
<View style={{ width: 110 }}>
|
||||||
|
<Text style={styles.fieldLabel}>Season</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={season}
|
||||||
|
onChangeText={setSeason}
|
||||||
|
keyboardType="numeric"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={{ width: 110 }}>
|
||||||
|
<Text style={styles.fieldLabel}>Episode</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={episode}
|
||||||
|
onChangeText={setEpisode}
|
||||||
|
keyboardType="numeric"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, { marginTop: 12, opacity: isRunning ? 0.85 : 1 }]}
|
||||||
|
onPress={runTest}
|
||||||
|
disabled={isRunning}
|
||||||
|
>
|
||||||
|
{isRunning ? (
|
||||||
|
<ActivityIndicator color={currentTheme.colors.white} />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="play" size={20} color={currentTheme.colors.white} />
|
||||||
|
)}
|
||||||
|
<Text style={styles.buttonText}>{isRunning ? 'Running…' : 'Run Test'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.rightColumn}>
|
||||||
|
{/* Right side: Logs and Results */}
|
||||||
|
<View style={[styles.content, { flex: 1 }]}>
|
||||||
|
<View style={{ flexDirection: 'row', marginBottom: 12, gap: 8 }}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.tab,
|
||||||
|
activeTab === 'logs' && styles.activeTab,
|
||||||
|
{ paddingVertical: 8, borderWidth: 1, borderColor: currentTheme.colors.elevation3, borderRadius: 8, flex: 1 }
|
||||||
|
]}
|
||||||
|
onPress={() => setActiveTab('logs')}
|
||||||
|
>
|
||||||
|
<Text style={[styles.tabText, activeTab === 'logs' && styles.activeTabText]}>Logs</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.tab,
|
||||||
|
activeTab === 'results' && styles.activeTab,
|
||||||
|
{ paddingVertical: 8, borderWidth: 1, borderColor: currentTheme.colors.elevation3, borderRadius: 8, flex: 1 }
|
||||||
|
]}
|
||||||
|
onPress={() => setActiveTab('results')}
|
||||||
|
>
|
||||||
|
<Text style={[styles.tabText, activeTab === 'results' && styles.activeTabText]}>Results ({streams.length})</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{activeTab === 'logs' || activeTab === 'code' ? (
|
||||||
|
<ScrollView
|
||||||
|
ref={(r) => (logsScrollRef.current = r)}
|
||||||
|
style={[styles.logContainer, { flex: 1, minHeight: 400 }]}
|
||||||
|
contentContainerStyle={{ paddingBottom: 20 }}
|
||||||
|
onContentSizeChange={() => {
|
||||||
|
logsScrollRef.current?.scrollToEnd({ animated: true });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{logs.length === 0 ? (
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Ionicons name="terminal-outline" size={48} color={currentTheme.colors.mediumGray} />
|
||||||
|
<Text style={styles.emptyText}>No logs yet. Run a test to see output.</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
logs.map((log, i) => {
|
||||||
|
let style = styles.logItem;
|
||||||
|
if (log.includes('[ERROR]') || log.includes('[FATAL')) style = { ...style, ...styles.logError };
|
||||||
|
else if (log.includes('[WARN]')) style = { ...style, ...styles.logWarn };
|
||||||
|
else if (log.includes('[INFO]')) style = { ...style, ...styles.logInfo };
|
||||||
|
else if (log.includes('[DEBUG]')) style = { ...style, ...styles.logDebug };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text key={i} style={style}>
|
||||||
|
{log}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
) : (
|
||||||
|
<ScrollView style={{ flex: 1, minHeight: 400 }} contentContainerStyle={{ paddingBottom: 20 }}>
|
||||||
|
{streams.length === 0 ? (
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Ionicons name="list-outline" size={48} color={currentTheme.colors.mediumGray} />
|
||||||
|
<Text style={styles.emptyText}>No streams found yet.</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
streams.map((stream, i) => (
|
||||||
|
<View key={i} style={styles.resultItem}>
|
||||||
|
<Text style={styles.resultTitle}>{stream.title || stream.name}</Text>
|
||||||
|
<Text style={styles.resultMeta}>Quality: {stream.quality || 'Unknown'}</Text>
|
||||||
|
<Text style={styles.resultMeta}>Size: {stream.description || 'Unknown'}</Text>
|
||||||
|
<Text style={styles.resultUrl} numberOfLines={2}>URL: {stream.url}</Text>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.helperText}>
|
</View>
|
||||||
Paste a raw GitHub URL or local IP and tap download.
|
);
|
||||||
</Text>
|
}
|
||||||
<View style={[styles.row, { marginTop: 10 }]}>
|
|
||||||
|
// Original mobile layout
|
||||||
|
return (
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||||
|
keyboardVerticalOffset={Platform.OS === 'ios' ? 60 : 0}
|
||||||
|
>
|
||||||
|
<ScrollView style={styles.content} contentContainerStyle={{ paddingBottom: 10 }} keyboardShouldPersistTaps="handled">
|
||||||
|
<View style={styles.card}>
|
||||||
|
<View style={styles.cardTitleRow}>
|
||||||
|
<Text style={styles.cardTitle}>Load from URL</Text>
|
||||||
|
<Ionicons name="link-outline" size={18} color={currentTheme.colors.mediumEmphasis} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.helperText}>
|
||||||
|
Paste a raw GitHub URL or local IP and tap download.
|
||||||
|
</Text>
|
||||||
|
<View style={[styles.row, { marginTop: 10 }]}>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.input, { flex: 1 }]}
|
||||||
|
value={url}
|
||||||
|
onChangeText={setUrl}
|
||||||
|
placeholder="http://192.168.1.5:8000/provider.js"
|
||||||
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.secondaryButton, { paddingHorizontal: 12, minHeight: 48 }]}
|
||||||
|
onPress={fetchFromUrl}
|
||||||
|
>
|
||||||
|
<Ionicons name="download-outline" size={20} color={currentTheme.colors.white} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.card}>
|
||||||
|
<View style={styles.cardTitleRow}>
|
||||||
|
<Text style={styles.cardTitle}>Plugin Code</Text>
|
||||||
|
<View style={styles.cardActionsRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.cardActionButton}
|
||||||
|
onPress={() => setIsEditorFocused(true)}
|
||||||
|
accessibilityLabel="Focus code editor"
|
||||||
|
>
|
||||||
|
<Ionicons name="expand-outline" size={18} color={currentTheme.colors.highEmphasis} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Ionicons name="code-slash-outline" size={18} color={currentTheme.colors.mediumEmphasis} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { flex: 1 }]}
|
style={styles.codeInput}
|
||||||
value={url}
|
value={code}
|
||||||
onChangeText={setUrl}
|
onChangeText={setCode}
|
||||||
placeholder="http://192.168.1.5:8000/provider.js"
|
multiline
|
||||||
|
placeholder="// Paste plugin code here..."
|
||||||
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
/>
|
/>
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.button, styles.secondaryButton, { paddingHorizontal: 12, minHeight: 48 }]}
|
|
||||||
onPress={fetchFromUrl}
|
|
||||||
>
|
|
||||||
<Ionicons name="download-outline" size={20} color={currentTheme.colors.white} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</ScrollView>
|
||||||
|
|
||||||
<View style={styles.card}>
|
<View style={[styles.stickyFooter, { paddingBottom: Math.max(insets.bottom, 14) }]}>
|
||||||
<View style={styles.cardTitleRow}>
|
<View style={styles.footerCard}>
|
||||||
<Text style={styles.cardTitle}>Plugin Code</Text>
|
<View style={styles.footerTitleRow}>
|
||||||
<View style={styles.cardActionsRow}>
|
<Text style={styles.footerTitle}>Test Parameters</Text>
|
||||||
|
<Ionicons name="options-outline" size={16} color={currentTheme.colors.mediumEmphasis} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.segment}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.cardActionButton}
|
style={[styles.segmentItem, mediaType === 'movie' && styles.segmentItemActive]}
|
||||||
onPress={() => setIsEditorFocused(true)}
|
onPress={() => setMediaType('movie')}
|
||||||
accessibilityLabel="Focus code editor"
|
|
||||||
>
|
>
|
||||||
<Ionicons name="expand-outline" size={18} color={currentTheme.colors.highEmphasis} />
|
<Ionicons name="film-outline" size={18} color={mediaType === 'movie' ? currentTheme.colors.primary : currentTheme.colors.highEmphasis} />
|
||||||
|
<Text style={[styles.segmentText, mediaType === 'movie' && styles.segmentTextActive]}>Movie</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.segmentItem, mediaType === 'tv' && styles.segmentItemActive]}
|
||||||
|
onPress={() => setMediaType('tv')}
|
||||||
|
>
|
||||||
|
<Ionicons name="tv-outline" size={18} color={mediaType === 'tv' ? currentTheme.colors.primary : currentTheme.colors.highEmphasis} />
|
||||||
|
<Text style={[styles.segmentText, mediaType === 'tv' && styles.segmentTextActive]}>TV</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Ionicons name="code-slash-outline" size={18} color={currentTheme.colors.mediumEmphasis} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<TextInput
|
|
||||||
style={styles.codeInput}
|
|
||||||
value={code}
|
|
||||||
onChangeText={setCode}
|
|
||||||
multiline
|
|
||||||
placeholder="// Paste plugin code here..."
|
|
||||||
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect={false}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<View style={[styles.stickyFooter, { paddingBottom: Math.max(insets.bottom, 14) }]}>
|
|
||||||
<View style={styles.footerCard}>
|
|
||||||
<View style={styles.footerTitleRow}>
|
|
||||||
<Text style={styles.footerTitle}>Test Parameters</Text>
|
|
||||||
<Ionicons name="options-outline" size={16} color={currentTheme.colors.mediumEmphasis} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.segment}>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.segmentItem, mediaType === 'movie' && styles.segmentItemActive]}
|
|
||||||
onPress={() => setMediaType('movie')}
|
|
||||||
>
|
|
||||||
<Ionicons name="film-outline" size={18} color={mediaType === 'movie' ? currentTheme.colors.primary : currentTheme.colors.highEmphasis} />
|
|
||||||
<Text style={[styles.segmentText, mediaType === 'movie' && styles.segmentTextActive]}>Movie</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.segmentItem, mediaType === 'tv' && styles.segmentItemActive]}
|
|
||||||
onPress={() => setMediaType('tv')}
|
|
||||||
>
|
|
||||||
<Ionicons name="tv-outline" size={18} color={mediaType === 'tv' ? currentTheme.colors.primary : currentTheme.colors.highEmphasis} />
|
|
||||||
<Text style={[styles.segmentText, mediaType === 'tv' && styles.segmentTextActive]}>TV</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[styles.row, { marginTop: 10, alignItems: 'flex-start' }]}>
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<Text style={styles.fieldLabel}>TMDB ID</Text>
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
value={tmdbId}
|
|
||||||
onChangeText={setTmdbId}
|
|
||||||
keyboardType="numeric"
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{mediaType === 'tv' && (
|
<View style={[styles.row, { marginTop: 10, alignItems: 'flex-start' }]}>
|
||||||
<>
|
<View style={{ flex: 1 }}>
|
||||||
<View style={{ width: 110 }}>
|
<Text style={styles.fieldLabel}>TMDB ID</Text>
|
||||||
<Text style={styles.fieldLabel}>Season</Text>
|
<TextInput
|
||||||
<TextInput
|
style={styles.input}
|
||||||
style={styles.input}
|
value={tmdbId}
|
||||||
value={season}
|
onChangeText={setTmdbId}
|
||||||
onChangeText={setSeason}
|
keyboardType="numeric"
|
||||||
keyboardType="numeric"
|
/>
|
||||||
/>
|
</View>
|
||||||
</View>
|
|
||||||
<View style={{ width: 110 }}>
|
{mediaType === 'tv' && (
|
||||||
<Text style={styles.fieldLabel}>Episode</Text>
|
<>
|
||||||
<TextInput
|
<View style={{ width: 110 }}>
|
||||||
style={styles.input}
|
<Text style={styles.fieldLabel}>Season</Text>
|
||||||
value={episode}
|
<TextInput
|
||||||
onChangeText={setEpisode}
|
style={styles.input}
|
||||||
keyboardType="numeric"
|
value={season}
|
||||||
/>
|
onChangeText={setSeason}
|
||||||
</View>
|
keyboardType="numeric"
|
||||||
</>
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={{ width: 110 }}>
|
||||||
|
<Text style={styles.fieldLabel}>Episode</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={episode}
|
||||||
|
onChangeText={setEpisode}
|
||||||
|
keyboardType="numeric"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, { opacity: isRunning ? 0.85 : 1 }]}
|
||||||
|
onPress={runTest}
|
||||||
|
disabled={isRunning}
|
||||||
|
>
|
||||||
|
{isRunning ? (
|
||||||
|
<ActivityIndicator color={currentTheme.colors.white} />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="play" size={20} color={currentTheme.colors.white} />
|
||||||
)}
|
)}
|
||||||
</View>
|
<Text style={styles.buttonText}>{isRunning ? 'Running…' : 'Run Test'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
</KeyboardAvoidingView>
|
||||||
style={[styles.button, { opacity: isRunning ? 0.85 : 1 }]}
|
);
|
||||||
onPress={runTest}
|
};
|
||||||
disabled={isRunning}
|
|
||||||
>
|
|
||||||
{isRunning ? (
|
|
||||||
<ActivityIndicator color={currentTheme.colors.white} />
|
|
||||||
) : (
|
|
||||||
<Ionicons name="play" size={20} color={currentTheme.colors.white} />
|
|
||||||
)}
|
|
||||||
<Text style={styles.buttonText}>{isRunning ? 'Running…' : 'Run Test'}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</KeyboardAvoidingView>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderLogsTab = () => (
|
const renderLogsTab = () => (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||||
import { useTheme } from '../../contexts/ThemeContext';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { pluginService } from '../../services/pluginService';
|
import { pluginService } from '../../services/pluginService';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { getPluginTesterStyles } from './styles';
|
import { getPluginTesterStyles, useIsLargeScreen } from './styles';
|
||||||
import { RepoManifest, RepoScraper, RepoTestResult, RepoTestStatus } from './types';
|
import { RepoManifest, RepoScraper, RepoTestResult, RepoTestStatus } from './types';
|
||||||
|
|
||||||
const extractRepositoryName = (url: string) => {
|
const extractRepositoryName = (url: string) => {
|
||||||
|
|
@ -96,7 +96,8 @@ const buildScraperCandidates = (baseRepoUrl: string, filename: string) => {
|
||||||
|
|
||||||
export const RepoTester = () => {
|
export const RepoTester = () => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const styles = getPluginTesterStyles(currentTheme);
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
const styles = getPluginTesterStyles(currentTheme, isLargeScreen);
|
||||||
|
|
||||||
// Repo tester state
|
// Repo tester state
|
||||||
const [repoUrl, setRepoUrl] = useState('');
|
const [repoUrl, setRepoUrl] = useState('');
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { View, Text, TouchableOpacity } from 'react-native';
|
import { View, Text, TouchableOpacity } from 'react-native';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { useTheme } from '../../contexts/ThemeContext';
|
import { useTheme } from '../../contexts/ThemeContext';
|
||||||
import { getPluginTesterStyles } from './styles';
|
import { getPluginTesterStyles, useIsLargeScreen } from './styles';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -14,7 +14,8 @@ interface HeaderProps {
|
||||||
|
|
||||||
export const Header = ({ title, subtitle, onBack, backIcon = 'arrow-back', rightElement }: HeaderProps) => {
|
export const Header = ({ title, subtitle, onBack, backIcon = 'arrow-back', rightElement }: HeaderProps) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const styles = getPluginTesterStyles(currentTheme);
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
const styles = getPluginTesterStyles(currentTheme, isLargeScreen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
|
|
@ -39,7 +40,8 @@ interface MainTabBarProps {
|
||||||
|
|
||||||
export const MainTabBar = ({ activeTab, onTabChange }: MainTabBarProps) => {
|
export const MainTabBar = ({ activeTab, onTabChange }: MainTabBarProps) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const styles = getPluginTesterStyles(currentTheme);
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
const styles = getPluginTesterStyles(currentTheme, isLargeScreen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.tabBar}>
|
<View style={styles.tabBar}>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,37 @@
|
||||||
import { StyleSheet, Platform } from 'react-native';
|
import { StyleSheet, Platform, useWindowDimensions } from 'react-native';
|
||||||
|
|
||||||
export const getPluginTesterStyles = (theme: any) => StyleSheet.create({
|
// Breakpoint for larger screens (tablets, iPads)
|
||||||
|
export const LARGE_SCREEN_BREAKPOINT = 768;
|
||||||
|
|
||||||
|
export const useIsLargeScreen = () => {
|
||||||
|
const { width } = useWindowDimensions();
|
||||||
|
return width >= LARGE_SCREEN_BREAKPOINT;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPluginTesterStyles = (theme: any, isLargeScreen: boolean = false) => StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: theme.colors.darkBackground,
|
backgroundColor: theme.colors.darkBackground,
|
||||||
},
|
},
|
||||||
|
// Large screen wrapper for centering content
|
||||||
|
largeScreenWrapper: {
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: isLargeScreen ? 900 : undefined,
|
||||||
|
alignSelf: isLargeScreen ? 'center' : undefined,
|
||||||
|
width: isLargeScreen ? '100%' : undefined,
|
||||||
|
paddingHorizontal: isLargeScreen ? 24 : 0,
|
||||||
|
},
|
||||||
|
// Two-column layout for large screens
|
||||||
|
twoColumnContainer: {
|
||||||
|
flexDirection: isLargeScreen ? 'row' : 'column',
|
||||||
|
gap: isLargeScreen ? 16 : 0,
|
||||||
|
},
|
||||||
|
leftColumn: {
|
||||||
|
flex: isLargeScreen ? 1 : undefined,
|
||||||
|
},
|
||||||
|
rightColumn: {
|
||||||
|
flex: isLargeScreen ? 1 : undefined,
|
||||||
|
},
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue