Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/features/player/widgets/android/android_bottom_transport_controls.dart';
import '../../../../helpers/fake_player_engine.dart';
void main() {
testWidgets('renders prev play next order', (tester) async {
final player = FakePlayerEngine(playing: true);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
backgroundColor: Colors.black,
body: Center(
child: AndroidBottomTransportControls(
player: player,
hasPrev: true,
hasNext: true,
onPrevItem: () {},
onNextItem: () {},
),
),
),
),
);
expect(find.byIcon(Icons.skip_previous), findsOneWidget);
expect(find.byIcon(Icons.pause), findsOneWidget);
expect(find.byIcon(Icons.skip_next), findsOneWidget);
final prevCenter = tester.getCenter(find.byIcon(Icons.skip_previous));
final playCenter = tester.getCenter(find.byIcon(Icons.pause));
final nextCenter = tester.getCenter(find.byIcon(Icons.skip_next));
expect(prevCenter.dx < playCenter.dx, isTrue);
expect(playCenter.dx < nextCenter.dx, isTrue);
await player.dispose();
});
testWidgets('invokes prev/next callbacks and toggles playback', (
tester,
) async {
final player = FakePlayerEngine(playing: true);
var prevTaps = 0;
var nextTaps = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: AndroidBottomTransportControls(
player: player,
hasPrev: true,
hasNext: true,
onPrevItem: () => prevTaps++,
onNextItem: () => nextTaps++,
),
),
),
),
);
await tester.tap(find.byIcon(Icons.skip_previous));
await tester.tap(find.byIcon(Icons.skip_next));
await tester.tap(find.byIcon(Icons.pause));
await tester.pump();
expect(prevTaps, 1);
expect(nextTaps, 1);
expect(player.pauseCalls, 1);
expect(player.playCalls, 0);
await player.dispose();
});
testWidgets('disables prev/next when unavailable', (tester) async {
final player = FakePlayerEngine(playing: true);
var prevTaps = 0;
var nextTaps = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: AndroidBottomTransportControls(
player: player,
hasPrev: false,
hasNext: false,
onPrevItem: () => prevTaps++,
onNextItem: () => nextTaps++,
),
),
),
),
);
final prevBtn = tester.widget<IconButton>(
find.widgetWithIcon(IconButton, Icons.skip_previous),
);
final nextBtn = tester.widget<IconButton>(
find.widgetWithIcon(IconButton, Icons.skip_next),
);
expect(prevBtn.onPressed, isNull);
expect(nextBtn.onPressed, isNull);
await player.dispose();
});
}
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/features/player/widgets/android/android_gesture_feedback.dart';
void main() {
Widget buildSubject({required bool seekIsForward}) {
return MaterialApp(
home: SizedBox(
width: 360,
height: 240,
child: AndroidGestureFeedback(
data: GestureFeedbackData(
kind: GestureFeedbackKind.seek,
seekTarget: const Duration(minutes: 1),
totalDuration: const Duration(minutes: 10),
seekIsForward: seekIsForward,
),
),
),
);
}
testWidgets('seek preview shows rewind icon for backward seek', (
tester,
) async {
await tester.pumpWidget(buildSubject(seekIsForward: false));
expect(find.byIcon(Icons.fast_rewind), findsOneWidget);
expect(find.byIcon(Icons.fast_forward), findsNothing);
});
testWidgets('seek preview shows forward icon for forward seek', (
tester,
) async {
await tester.pumpWidget(buildSubject(seekIsForward: true));
expect(find.byIcon(Icons.fast_forward), findsOneWidget);
expect(find.byIcon(Icons.fast_rewind), findsNothing);
});
testWidgets('long press speed uses light background', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: SizedBox(
width: 360,
height: 240,
child: AndroidGestureFeedback(
data: GestureFeedbackData(
kind: GestureFeedbackKind.longPressSpeed,
speedRate: 3,
),
),
),
),
);
final container = tester.widget<Container>(
find.descendant(
of: find.byType(AndroidGestureFeedback),
matching: find.byType(Container),
),
);
final decoration = container.decoration as BoxDecoration;
expect(decoration.color, const Color(0x45181818));
});
}
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/features/player/widgets/player_hold_speed_prompt.dart';
void main() {
testWidgets('hold speed prompt uses light background', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Stack(
children: [HoldSpeedPrompt(visible: true, isLeft: false, rate: 3)],
),
),
);
final container = tester.widget<Container>(
find.descendant(
of: find.byType(HoldSpeedPrompt),
matching: find.byType(Container),
),
);
final decoration = container.decoration as BoxDecoration;
expect(decoration.color, const Color(0x45181818));
});
}
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/features/player/widgets/player_hold_speed_prompt.dart';
import 'package:smplayer/features/player/widgets/player_video_widget.dart';
import '../../../helpers/fake_player_engine.dart';
void main() {
testWidgets('keyboard hold speed prompt hides immediately on release', (
tester,
) async {
final player = FakePlayerEngine(playing: true);
final fit = ValueNotifier<BoxFit>(BoxFit.contain);
final rates = <double>[];
await tester.pumpWidget(
MaterialApp(
home: SizedBox(
width: 800,
height: 450,
child: PlayerVideoWidget(
player: player,
fit: fit,
topBar: const [],
bottomBar: const SizedBox.shrink(),
overlays: const [],
onControlsAreaTap: null,
isFullscreen: false,
onToggleFullscreen: () async {},
isPip: false,
keyboardEnabled: true,
currentRate: 1,
onSetRate: rates.add,
suppressControlsChrome: true,
),
),
),
);
await tester.pump();
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight);
await tester.pump(const Duration(milliseconds: 200));
expect(_promptOpacity(tester), 1);
expect(rates.last, 3);
await tester.sendKeyUpEvent(LogicalKeyboardKey.arrowRight);
await tester.pump();
expect(_promptOpacity(tester), 0);
expect(rates.last, 1);
fit.dispose();
await player.dispose();
});
}
double _promptOpacity(WidgetTester tester) {
return tester
.widget<AnimatedOpacity>(
find.descendant(
of: find.byType(HoldSpeedPrompt),
matching: find.byType(AnimatedOpacity),
),
)
.opacity;
}
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/core/infra/player_engine/player_engine.dart';
import 'package:smplayer/features/player/widgets/track_selector_button.dart';
import '../../../helpers/fake_player_engine.dart';
void main() {
testWidgets('音轨按钮由初始 tracks 决定显隐', (tester) async {
final player = FakePlayerEngine(
playing: true,
tracks: const PlayerTracks(
audio: [
AudioTrack(id: '1'),
AudioTrack(id: '2'),
],
),
);
addTearDown(player.dispose);
await tester.pumpWidget(
MaterialApp(
home: AudioTracksButton(
player: player,
isPanelOpen: false,
onTogglePanel: () {},
),
),
);
expect(find.byTooltip('音轨'), findsOneWidget);
});
testWidgets('字幕按钮响应 tracks stream 更新', (tester) async {
final player = FakePlayerEngine(playing: true);
addTearDown(player.dispose);
await tester.pumpWidget(
MaterialApp(
home: SubtitleTracksButton(
player: player,
embySubtitles: const [],
isPanelOpen: false,
onTogglePanel: () {},
),
),
);
expect(find.byTooltip('字幕'), findsNothing);
player.addTracks(
const PlayerTracks(subtitle: [SubtitleTrack(id: 'embedded')]),
);
await tester.pump();
expect(find.byTooltip('字幕'), findsOneWidget);
});
}