Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
85668a0
feat: use native slider for playback (#88)
jsit Nov 15, 2025
68aa47b
fix: fix height of slider on Android
jsit Nov 15, 2025
0d6e651
fix: class rename
jsit Nov 15, 2025
27a3dc6
Merge branch 'main' into feat/88-native-playback-slider
jsit Nov 15, 2025
06f7275
fix: make slider progress bar WMBR green
jsit Nov 15, 2025
2c97d68
Merge branch 'main' into feat/88-native-playback-slider
jsit Nov 17, 2025
cf2bfa9
fix: don't seek on every value change; add callback for valuechange
jsit Nov 17, 2025
ad39072
chore: move fontVariant prop
jsit Nov 17, 2025
60c6cf1
chore: don't memoize hook
jsit Nov 17, 2025
ab693f2
chore: remove obsolete comment
jsit Nov 17, 2025
00d2604
fix: harden progress duration math
jsit Nov 17, 2025
1f8d8ad
fix: don't seek immediately when dragging playback slider
jsit Nov 17, 2025
83aeb93
fix: remove unused import
jsit Nov 17, 2025
cdb5c05
style: fix comment, state order
jsit Nov 17, 2025
c804a84
fix: remove unneeded memo dependency
jsit Nov 17, 2025
f3540d3
Merge remote-tracking branch 'origin/main' into feat/88-native-playba…
jsit Nov 20, 2025
e8bd4e0
fix: fix typo in import
jsit Nov 20, 2025
d7ead6f
Merge branch 'main' into feat/88-native-playback-slider
jsit Nov 20, 2025
12dbb78
Merge branch 'main' into feat/88-native-playback-slider
jsit Nov 20, 2025
1dbd83b
feat: allow jump forward/back when playing archives (#124)
jsit Nov 21, 2025
a188b9e
docs: add conventional commits format to copilot instructions (#133)
jsit Nov 22, 2025
70f1ae8
Feat: adds skip buttons to both the archive view and homescreen (#146)
alexandersimoes Nov 22, 2025
ec1169a
fix: fix track player Android track color
jsit Nov 22, 2025
b9345fc
Merge remote-tracking branch 'origin/main' into feat/88-native-playba…
jsit Nov 22, 2025
f072226
feat: only color thumb if android
jsit Nov 22, 2025
c8fd90d
Merge remote-tracking branch 'origin/main' into feat/88-native-playba…
jsit Dec 1, 2025
cc07a78
chore: error handling for seekTo
jsit Dec 6, 2025
0ed4ea5
chore: fix prop plurality
jsit Dec 6, 2025
ffa6544
docs: remove obsolete comment
jsit Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,63 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-slider (5.1.1):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsc
- React-jsi
- react-native-slider/common (= 5.1.1)
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-slider/common (5.1.1):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsc
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-track-player (4.1.1):
- React-Core
- SwiftAudioEx (= 1.1.0)
Expand Down Expand Up @@ -2466,6 +2523,7 @@ DEPENDENCIES:
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-track-player (from `../node_modules/react-native-track-player`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
Expand Down Expand Up @@ -2593,6 +2651,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-slider:
:path: "../node_modules/@react-native-community/slider"
react-native-track-player:
:path: "../node_modules/react-native-track-player"
React-NativeModulesApple:
Expand Down Expand Up @@ -2710,6 +2770,7 @@ SPEC CHECKSUMS:
React-Mapbuffer: 0c045c844ce6d85cde53e85ab163294c6adad349
React-microtasksnativemodule: 26edbece4d0c029ef6ac694e42102a338f2bae0a
react-native-safe-area-context: 8a49d796bc2fda5d22207da244713de67b4b8895
react-native-slider: ca5fb99cb306e895d43dc08c48e1832657f9c73b
react-native-track-player: 6dc2e2633265704b8ab6d8124b80239d4ed1f911
React-NativeModulesApple: 00beb364b4dd0a64d91b07658a43628672e888cb
React-oscompat: 114036cd8f064558c9c1a0c04fc9ae5e1453706a
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"postinstall": "patch-package"
},
"dependencies": {
"@react-native-community/slider": "^5.1.1",
"@react-navigation/bottom-tabs": "^7.4.9",
"@react-navigation/native": "^7.1.18",
"@react-navigation/native-stack": "^7.3.28",
Expand Down
139 changes: 9 additions & 130 deletions src/app/Schedule/ArchivedShowView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import LinearGradient from 'react-native-linear-gradient';
import { useRoute, RouteProp } from '@react-navigation/native';
import { useHeaderHeight } from '@react-navigation/elements';
import { debugError } from '../../utils/Debug';
import Animated, { useSharedValue, runOnJS } from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import TrackPlayer, {
useProgress,
usePlaybackState,
Expand All @@ -33,6 +31,7 @@ import {
generateGradientColors,
} from '../../utils/GradientColors';
import { ShowImage } from '../../components/ShowImage';
import PlaybackSlider from '../../components/PlaybackSlider';

interface ArchivedShowViewProps {
show: Show;
Expand All @@ -53,13 +52,8 @@ export default function ArchivedShowView() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isArchivePlaying, setIsArchivePlaying] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [scrubPosition, setScrubPosition] = useState(0);
const [dragPercentage, setDragPercentage] = useState(0);

// Shared values for gesture handling
const progressBarWidth = useSharedValue(0);
const dragPosition = useSharedValue(0);

const playlistService = PlaylistService.getInstance();
const archiveService = ArchiveService.getInstance();
Expand Down Expand Up @@ -115,76 +109,11 @@ export default function ArchivedShowView() {
}, [archive.url, archiveService]);

// Calculate current progress percentage
const getCurrentPercentage = useCallback(() => {
if (isDragging) {
// During dragging, clamp the visual percentage but allow the user to keep dragging
return Math.min(Math.max(dragPercentage, 0), 100);
}
if (progress.duration > 0) {
return Math.min(
Math.max((progress.position / progress.duration) * 100, 0),
100,
);
}
return 0;
}, [dragPercentage, isDragging, progress.duration, progress.position]);

const [gradientStart] = generateGradientColors(show.name);
const [darkGradientStart, darkGradientEnd] = generateDarkGradientColors(
show.name,
);

const updateScrubPosition = (position: number, percentage: number) => {
setScrubPosition(position);
setDragPercentage(percentage);
};

const seekToPosition = async (position: number) => {
try {
// Clamp position to avoid seeking to exact beginning or end
const clampedPosition = Math.max(
0.1,
Math.min(position, progress.duration - 0.1),
);
await TrackPlayer.seekTo(clampedPosition);
} catch (e) {
debugError('Error seeking:', e);
}
};

const panGesture = Gesture.Pan()
.onStart(event => {
runOnJS(setIsDragging)(true);
// Set initial drag position to the touch point - allow full range
dragPosition.value = event.x;
})
.onUpdate(event => {
// Allow dragging to full range without clamping during drag
dragPosition.value = event.x;

if (progressBarWidth.value > 0) {
// Clamp only for visual display and position calculation
const clampedX = Math.max(0, Math.min(event.x, progressBarWidth.value));
const percentage = (clampedX / progressBarWidth.value) * 100;
const newPosition =
(clampedX / progressBarWidth.value) * progress.duration;
runOnJS(updateScrubPosition)(newPosition, percentage);
}
})
.onEnd(() => {
if (progressBarWidth.value > 0) {
// Clamp the final position for seeking
const clampedX = Math.max(
0,
Math.min(dragPosition.value, progressBarWidth.value),
);
const percentage = clampedX / progressBarWidth.value;
const newPosition = percentage * progress.duration;
runOnJS(seekToPosition)(newPosition);
}
runOnJS(setIsDragging)(false);
});

const handlePlayPause = async () => {
try {
if (isArchivePlaying && playbackState?.state === State.Playing) {
Expand Down Expand Up @@ -240,35 +169,11 @@ export default function ArchivedShowView() {
{isArchivePlaying && progress.duration > 0 && (
<View style={styles.progressSection}>
<View style={styles.progressContainer}>
<GestureDetector gesture={panGesture}>
<Animated.View style={styles.progressTouchArea}>
<View
style={styles.progressBar}
onLayout={event => {
progressBarWidth.value =
event.nativeEvent.layout.width;
}}
>
<View
style={[
styles.progressFill,
{ width: `${getCurrentPercentage()}%` },
]}
/>
<View
style={[
styles.progressDot,
{ left: `${getCurrentPercentage()}%` },
]}
/>
</View>
</Animated.View>
</GestureDetector>
<PlaybackSlider styles={styles.slider} />

<View style={styles.timeContainer}>
<Text style={styles.timeText}>
{secondsToTime(
isDragging ? scrubPosition : progress.position,
)}
{secondsToTime(progress.position)}
</Text>
<Text style={styles.timeText}>
{secondsToTime(progress.duration)}
Expand Down Expand Up @@ -441,38 +346,8 @@ const styles = StyleSheet.create({
width: '100%',
maxWidth: 300,
},
progressTouchArea: {
paddingVertical: 12,
paddingHorizontal: 4,
marginBottom: 12,
},
progressBar: {
height: 4,
backgroundColor: 'rgba(255, 255, 255, 0.3)',
borderRadius: 2,
position: 'relative',
},
progressFill: {
height: '100%',
backgroundColor: '#FFFFFF',
borderRadius: 2,
minWidth: 2,
},
progressDot: {
position: 'absolute',
top: -4,
width: 12,
height: 12,
backgroundColor: '#FFFFFF',
borderRadius: 6,
marginLeft: -6,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 4,
},
timeContainer: {
fontVariant: ['tabular-nums'],
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
Expand All @@ -482,4 +357,8 @@ const styles = StyleSheet.create({
fontSize: 12,
fontWeight: '500',
},
slider: {
width: '100%',
height: 40,
},
});
Loading
Loading