fix(mobile): no center pause button while seeking + incognito diagnostic browser
Player (report dccc05e4): swipe-seek no longer force-opens full controls (it has its own ±time bubble), and the center play/pause button is hidden while actively seeking (pan or scrub-bar drag) — kills the big pause that popped up mid-seek and lingered ~3.5s. Diagnostics "open in browser" now routes to an in-app WebView with incognito=true instead of Linking.openURL: fresh sessionless view of the host page + no NSFW URL dumped into the user's real browser history (fits the app's privacy stance). New DiagnosticBrowser route. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
811ea3232d
commit
c073acff03
5 changed files with 78 additions and 8 deletions
|
|
@ -16,6 +16,14 @@ export type ChangelogEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
id: '2026-06-29b',
|
||||||
|
date: 'June 2026',
|
||||||
|
items: [
|
||||||
|
'Seeking (swipe or drag) no longer pops a big pause button into the middle of the screen.',
|
||||||
|
'Diagnostics "open in browser" now opens privately inside the app (incognito) — no cookies, no trace in your real browser.',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: '2026-06-29',
|
id: '2026-06-29',
|
||||||
date: 'June 2026',
|
date: 'June 2026',
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import React from 'react';
|
||||||
import { Pressable, Text, View } from 'react-native';
|
import { Pressable, Text, View } from 'react-native';
|
||||||
import { AppLockSettingsScreen } from './screens/AppLockSettingsScreen';
|
import { AppLockSettingsScreen } from './screens/AppLockSettingsScreen';
|
||||||
import { BlacklistScreen } from './screens/BlacklistScreen';
|
import { BlacklistScreen } from './screens/BlacklistScreen';
|
||||||
|
import { DiagnosticBrowserScreen } from './screens/DiagnosticBrowserScreen';
|
||||||
import { DonateScreen } from './screens/DonateScreen';
|
import { DonateScreen } from './screens/DonateScreen';
|
||||||
import { FavoritesScreen } from './screens/FavoritesScreen';
|
import { FavoritesScreen } from './screens/FavoritesScreen';
|
||||||
import { MovieDetailScreen } from './screens/MovieDetailScreen';
|
import { MovieDetailScreen } from './screens/MovieDetailScreen';
|
||||||
|
|
@ -52,6 +53,8 @@ export type RootStackParamList = {
|
||||||
AppLockSettings: undefined;
|
AppLockSettings: undefined;
|
||||||
Blacklist: undefined;
|
Blacklist: undefined;
|
||||||
Donate: undefined;
|
Donate: undefined;
|
||||||
|
// In-app incognito browser do diagnostyki hostera (long-press na linku playbacku).
|
||||||
|
DiagnosticBrowser: { url: string };
|
||||||
Player: {
|
Player: {
|
||||||
url: string;
|
url: string;
|
||||||
sceneId: string;
|
sceneId: string;
|
||||||
|
|
@ -275,6 +278,11 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
||||||
component={DonateScreen}
|
component={DonateScreen}
|
||||||
options={{ title: 'Support project' }}
|
options={{ title: 'Support project' }}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="DiagnosticBrowser"
|
||||||
|
component={DiagnosticBrowserScreen}
|
||||||
|
options={{ title: 'Diagnostics (incognito)' }}
|
||||||
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="Player"
|
name="Player"
|
||||||
component={PlayerScreen}
|
component={PlayerScreen}
|
||||||
|
|
|
||||||
44
mobile/src/screens/DiagnosticBrowserScreen.tsx
Normal file
44
mobile/src/screens/DiagnosticBrowserScreen.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { useRoute, type RouteProp } from '@react-navigation/native';
|
||||||
|
import React from 'react';
|
||||||
|
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
||||||
|
import { WebView } from 'react-native-webview';
|
||||||
|
import type { RootStackParamList } from '../navigation';
|
||||||
|
import { theme } from '../theme';
|
||||||
|
|
||||||
|
// Diagnostyczny in-app browser w trybie incognito (report dccc05e4). Long-press na
|
||||||
|
// linku playbacku → "Open in browser (diagnostics)" otwiera page_url hostera tutaj,
|
||||||
|
// żeby user ocenił czy faktycznie broken. `incognito` → zero cookies/cache/historii:
|
||||||
|
// świeży widok strony (jak u kogoś bez sesji) ORAZ nie zostawia NSFW URL-a w prawdziwej
|
||||||
|
// przeglądarce usera. Wcześniej Linking.openURL wrzucał to do Chrome z jego sesją.
|
||||||
|
export function DiagnosticBrowserScreen() {
|
||||||
|
const route = useRoute<RouteProp<RootStackParamList, 'DiagnosticBrowser'>>();
|
||||||
|
const { url } = route.params;
|
||||||
|
return (
|
||||||
|
<View style={styles.root}>
|
||||||
|
<WebView
|
||||||
|
source={{ uri: url }}
|
||||||
|
incognito
|
||||||
|
cacheEnabled={false}
|
||||||
|
thirdPartyCookiesEnabled={false}
|
||||||
|
startInLoadingState
|
||||||
|
renderLoading={() => (
|
||||||
|
<View style={styles.loading}>
|
||||||
|
<ActivityIndicator color={theme.fg} size="large" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
style={styles.web}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
root: { flex: 1, backgroundColor: theme.bg },
|
||||||
|
web: { flex: 1, backgroundColor: theme.bg },
|
||||||
|
loading: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: theme.bg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -575,7 +575,9 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
||||||
.onStart(() => {
|
.onStart(() => {
|
||||||
cancelHide();
|
cancelHide();
|
||||||
panStartTimeRef.current = player.currentTime || 0;
|
panStartTimeRef.current = player.currentTime || 0;
|
||||||
setControlsVisible(true);
|
// NIE pokazujemy pełnych kontrolek przy swipe-seeku — pan ma własny popup
|
||||||
|
// ±czas (panSeekBubble). Wcześniej setControlsVisible(true) wyrzucał duży
|
||||||
|
// przycisk pauzy na środek i zostawiał go ~3.5s po puszczeniu (report dccc05e4).
|
||||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
|
||||||
})
|
})
|
||||||
.onUpdate((e) => {
|
.onUpdate((e) => {
|
||||||
|
|
@ -686,6 +688,9 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
||||||
// Priorytet: scrubber (palec na pasku) > pan-seek (swipe na video) > playback.
|
// Priorytet: scrubber (palec na pasku) > pan-seek (swipe na video) > playback.
|
||||||
const panSeekRatio = panSeekTarget !== null && dur > 0 ? panSeekTarget / dur : null;
|
const panSeekRatio = panSeekTarget !== null && dur > 0 ? panSeekTarget / dur : null;
|
||||||
const displayRatio = scrubbingRatio ?? panSeekRatio ?? progressRatio;
|
const displayRatio = scrubbingRatio ?? panSeekRatio ?? progressRatio;
|
||||||
|
// Aktywne przewijanie (palec na pasku albo swipe) — chowamy wtedy duży środkowy
|
||||||
|
// przycisk play/pauza (report dccc05e4): przy seeku nie chcesz wielkiej pauzy na ekranie.
|
||||||
|
const isSeeking = panSeekTarget !== null || scrubX !== null;
|
||||||
const displayedTime =
|
const displayedTime =
|
||||||
scrubbingRatio !== null
|
scrubbingRatio !== null
|
||||||
? scrubbingRatio * dur
|
? scrubbingRatio * dur
|
||||||
|
|
@ -775,11 +780,13 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{!isSeeking && (
|
||||||
<View style={styles.controlsCenter} pointerEvents="box-none">
|
<View style={styles.controlsCenter} pointerEvents="box-none">
|
||||||
<Pressable onPress={togglePlay} hitSlop={20} style={styles.playBtn}>
|
<Pressable onPress={togglePlay} hitSlop={20} style={styles.playBtn}>
|
||||||
<Text style={styles.playBtnText}>{isPlaying ? '❚❚' : '▶'}</Text>
|
<Text style={styles.playBtnText}>{isPlaying ? '❚❚' : '▶'}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<View style={styles.controlsBottom} pointerEvents="box-none">
|
<View style={styles.controlsBottom} pointerEvents="box-none">
|
||||||
<Text style={styles.timeText}>{formatTime(displayedTime)}</Text>
|
<Text style={styles.timeText}>{formatTime(displayedTime)}</Text>
|
||||||
|
|
|
||||||
|
|
@ -775,12 +775,15 @@ function PlaybackButton({
|
||||||
'What do you want to do with this link?',
|
'What do you want to do with this link?',
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: 'Open in browser (diagnostics)',
|
text: 'Open in browser (incognito)',
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
try {
|
try {
|
||||||
const url = source.page_url || source.embed_url || source.stream_url;
|
const url = source.page_url || source.embed_url || source.stream_url;
|
||||||
if (url) {
|
if (url) {
|
||||||
await Linking.openURL(url);
|
// In-app incognito WebView (report dccc05e4): świeży widok bez sesji/
|
||||||
|
// cookies usera i bez śladu w prawdziwej przeglądarce. Zamiast
|
||||||
|
// Linking.openURL (otwierał Chrome z historią + sesją).
|
||||||
|
nav.navigate('DiagnosticBrowser', { url });
|
||||||
} else {
|
} else {
|
||||||
Alert.alert('No URL', 'This playback has no page_url to open.');
|
Alert.alert('No URL', 'This playback has no page_url to open.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue