Files
CheckTheethKids/lib/screens/video_screen.dart
Carlos Correia d24cb3242a Documentação
2026-05-03 23:31:31 +01:00

315 lines
10 KiB
Dart

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class VideoItem {
const VideoItem({required this.title, required this.url});
final String title;
final String url;
}
class VideoScreen extends StatelessWidget {
const VideoScreen({super.key});
static const Color _teal = Color(0xFF2F9E94);
static const Color _accentPink = Color(0xFFFF55A7);
static const List<VideoItem> library = [
VideoItem(title: 'Como escovar da maneira certa', url: 'https://www.youtube.com/watch?v=uH8dBWkD__0'),
];
@override
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
return Scaffold(
appBar: AppBar(
backgroundColor: _teal,
foregroundColor: Colors.white,
elevation: 0,
title: const Text(
'Vídeos Educativos',
style: TextStyle(fontWeight: FontWeight.w900),
),
),
body: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFE6F1),
Color(0xFFFFC9DF),
],
),
),
),
),
Positioned(
left: -size.width * 0.40,
bottom: -size.width * 0.45,
child: IgnorePointer(
child: SizedBox(
width: size.width * 1.05,
height: size.width * 1.05,
child: Transform.rotate(
angle: 35 * math.pi / 180,
child: Opacity(
opacity: 0.95,
child: Lottie.asset(
'lottie/Liquid waves.json',
fit: BoxFit.cover,
repeat: true,
),
),
),
),
),
),
SafeArea(
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.65),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.black.withValues(alpha: 0.08)),
),
child: const Row(
children: [
Icon(Icons.play_circle_fill_rounded, color: _accentPink),
SizedBox(width: 10),
Expanded(
child: Text(
'Vídeos Educativos',
style: TextStyle(
fontWeight: FontWeight.w900,
color: _accentPink,
),
),
),
],
),
),
const SizedBox(height: 14),
Expanded(
child: GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.92,
children: library.map((item) {
final videoId = YoutubePlayer.convertUrlToId(item.url);
final thumb = videoId == null ? null : 'https://img.youtube.com/vi/$videoId/0.jpg';
return _VideoCard(
title: item.title,
thumbnailUrl: thumb,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => VideoPlayerScreen(url: item.url, title: item.title),
),
);
},
);
}).toList(),
),
),
],
),
),
),
),
),
],
),
);
}
}
class _VideoCard extends StatelessWidget {
const _VideoCard({required this.title, required this.thumbnailUrl, required this.onTap});
final String title;
final String? thumbnailUrl;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white.withValues(alpha: 0.80),
borderRadius: BorderRadius.circular(16),
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
child: Stack(
fit: StackFit.expand,
children: [
if (thumbnailUrl != null)
Image.network(
thumbnailUrl!,
fit: BoxFit.cover,
)
else
Container(color: Colors.black.withValues(alpha: 0.06)),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withValues(alpha: 0.10),
Colors.black.withValues(alpha: 0.42),
],
),
),
),
const Align(
alignment: Alignment.center,
child: Icon(Icons.play_circle_fill_rounded, size: 54, color: Colors.white),
),
],
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 12),
child: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w900,
color: Color(0xFF2F9E94),
),
),
),
],
),
),
);
}
}
class VideoPlayerScreen extends StatefulWidget {
const VideoPlayerScreen({super.key, required this.url, required this.title});
final String url;
final String title;
@override
State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
static const Color _teal = Color(0xFF2F9E94);
static const Color _bg = Color(0xFFFFE6F1);
late final YoutubePlayerController _controller;
@override
void initState() {
super.initState();
final id = YoutubePlayer.convertUrlToId(widget.url);
_controller = YoutubePlayerController(
initialVideoId: id ?? '',
flags: const YoutubePlayerFlags(
autoPlay: true,
mute: false,
enableCaption: true,
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final hasVideo = _controller.initialVideoId.isNotEmpty;
return Scaffold(
appBar: AppBar(
backgroundColor: _teal,
foregroundColor: Colors.white,
elevation: 0,
title: Text(
widget.title,
style: const TextStyle(fontWeight: FontWeight.w900),
),
),
body: Container(
color: _bg,
child: SafeArea(
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
children: [
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AspectRatio(
aspectRatio: 16 / 9,
child: hasVideo
? YoutubePlayer(
controller: _controller,
showVideoProgressIndicator: true,
progressIndicatorColor: Colors.white,
)
: Container(
color: Colors.black.withValues(alpha: 0.10),
child: const Center(
child: Text(
'Link de vídeo inválido',
style: TextStyle(fontWeight: FontWeight.w800),
),
),
),
),
),
const SizedBox(height: 14),
Text(
widget.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
color: Color(0xFFFF55A7),
),
),
const SizedBox(height: 6),
Text(
'Assista ao vídeo e aprenda mais sobre saúde bucal.',
style: TextStyle(
color: Colors.black.withValues(alpha: 0.70),
fontWeight: FontWeight.w600,
),
),
],
),
),
),
);
}
}