236 lines
7.0 KiB
Dart
236 lines
7.0 KiB
Dart
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:video_player/video_player.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'dart:math';
|
|
import 'package:open_filex/open_filex.dart';
|
|
|
|
class MediaPreviewScreen extends StatefulWidget {
|
|
final String path;
|
|
final bool isVideo;
|
|
|
|
const MediaPreviewScreen({
|
|
super.key,
|
|
required this.path,
|
|
required this.isVideo,
|
|
});
|
|
|
|
@override
|
|
State<MediaPreviewScreen> createState() => _MediaPreviewScreenState();
|
|
}
|
|
|
|
class _MediaPreviewScreenState extends State<MediaPreviewScreen> {
|
|
VideoPlayerController? _videoController;
|
|
bool _isPlaying = true;
|
|
String? _videoInitError;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
if (widget.isVideo) {
|
|
_videoController = VideoPlayerController.file(File(widget.path))
|
|
..initialize()
|
|
.then((_) {
|
|
_videoInitError = null;
|
|
if (!mounted) return;
|
|
setState(() {});
|
|
_videoController!.setLooping(false);
|
|
_videoController!.play();
|
|
})
|
|
.catchError((e) {
|
|
_videoInitError = e.toString();
|
|
_videoController?.dispose().catchError((_) {});
|
|
_videoController = null;
|
|
if (mounted) setState(() {});
|
|
});
|
|
|
|
_videoController!.addListener(() {
|
|
if (mounted) setState(() {});
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_videoController?.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
String _formatDuration(Duration d) {
|
|
String two(int n) => n.toString().padLeft(2, '0');
|
|
final m = two(d.inMinutes.remainder(60));
|
|
final s = two(d.inSeconds.remainder(60));
|
|
return "$m:$s";
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.black,
|
|
appBar: null,
|
|
body: Stack(
|
|
children: [
|
|
// MEDIA
|
|
Center(
|
|
child: widget.isVideo
|
|
? (_videoInitError != null)
|
|
? _buildVideoInitErrorFallback()
|
|
: (_videoController != null &&
|
|
_videoController!.value.isInitialized)
|
|
? Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
AspectRatio(
|
|
aspectRatio: _videoController!.value.aspectRatio,
|
|
child: VideoPlayer(_videoController!),
|
|
),
|
|
|
|
// overlay controls
|
|
Positioned(
|
|
bottom: 40,
|
|
left: 16,
|
|
right: 16,
|
|
child: _buildVideoControls(),
|
|
),
|
|
],
|
|
)
|
|
: const CircularProgressIndicator()
|
|
: Image.file(File(widget.path)),
|
|
),
|
|
|
|
// BOTTOM ACTIONS (как Telegram)
|
|
Positioned(
|
|
bottom: 40,
|
|
left: 16,
|
|
right: 16,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// переснять
|
|
ElevatedButton.icon(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white10,
|
|
),
|
|
onPressed: () {
|
|
Navigator.pop(context, false);
|
|
},
|
|
icon: const Icon(Icons.refresh),
|
|
label: const Text("Переснять"),
|
|
),
|
|
|
|
// отправить
|
|
ElevatedButton.icon(
|
|
onPressed: () {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
Navigator.pop(context, true);
|
|
});
|
|
},
|
|
icon: const Icon(Icons.send),
|
|
label: const Text("Отправить"),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildVideoControls() {
|
|
final c = _videoController!;
|
|
final duration = c.value.duration;
|
|
final position = c.value.position;
|
|
|
|
final posMs = position.inMilliseconds.toDouble();
|
|
final maxMs = duration.inMilliseconds
|
|
.toDouble()
|
|
.clamp(1, double.infinity)
|
|
.toDouble();
|
|
|
|
return Container(
|
|
child: Row(
|
|
children: [
|
|
// ▶️ / ⏸ слева
|
|
IconButton(
|
|
padding: EdgeInsets.zero,
|
|
constraints: const BoxConstraints(),
|
|
icon: Icon(
|
|
c.value.isPlaying ? Icons.pause : Icons.play_arrow,
|
|
color: Colors.white,
|
|
size: 26,
|
|
),
|
|
onPressed: () {
|
|
setState(() {
|
|
if (c.value.isPlaying) {
|
|
c.pause();
|
|
_isPlaying = false;
|
|
} else {
|
|
c.play();
|
|
_isPlaying = true;
|
|
}
|
|
});
|
|
},
|
|
),
|
|
|
|
Expanded(
|
|
child: SliderTheme(
|
|
data: SliderTheme.of(context).copyWith(
|
|
trackHeight: 2, // ТОНКИЙ как в Telegram
|
|
activeTrackColor: Colors.white,
|
|
inactiveTrackColor: Colors.white24,
|
|
thumbColor: Colors.white,
|
|
overlayColor: Colors.transparent,
|
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 5),
|
|
),
|
|
child: Slider(
|
|
value: posMs.clamp(0, maxMs).toDouble(),
|
|
min: 0,
|
|
max: maxMs,
|
|
onChanged: (v) {
|
|
c.seekTo(Duration(milliseconds: v.toInt()));
|
|
},
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 8),
|
|
|
|
Text(
|
|
"${_formatDuration(position)} / ${_formatDuration(duration)}",
|
|
style: const TextStyle(color: Colors.white, fontSize: 12),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildVideoInitErrorFallback() {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(Icons.play_disabled, color: Colors.white70, size: 48),
|
|
const SizedBox(height: 10),
|
|
const Text(
|
|
'Видео не воспроизводится на этом устройстве',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: Colors.white70),
|
|
),
|
|
const SizedBox(height: 10),
|
|
OutlinedButton.icon(
|
|
onPressed: () async {
|
|
try {
|
|
await OpenFilex.open(widget.path);
|
|
} catch (_) {}
|
|
},
|
|
icon: const Icon(Icons.open_in_new, color: Colors.white70),
|
|
label: const Text(
|
|
'Открыть внешним плеером',
|
|
style: TextStyle(color: Colors.white70),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|