CTK 1.2.0

This commit is contained in:
Carlos Correia
2026-07-30 00:53:51 +01:00
parent 89b27e5e86
commit fc875d559c
7 changed files with 298 additions and 311 deletions

View File

@@ -36,7 +36,7 @@ class _HelloSplashScreenState extends State<HelloSplashScreen> with TickerProvid
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
duration: const Duration(milliseconds: 250),
);
_opacity = CurvedAnimation(

View File

@@ -8,6 +8,7 @@ import 'package:youtube_player_flutter/youtube_player_flutter.dart';
import '../watched_videos_prefs.dart';
import '../colors/app_gradients.dart';
import '../strings/home_strings.dart';
import '../strings/video_strings.dart';
import '../widgets/entrance.dart';
import '../widgets/liquid_waves_background.dart';
@@ -213,12 +214,25 @@ class VideoScreen extends StatefulWidget {
class _VideoScreenState extends State<VideoScreen> {
final TextEditingController _searchController = TextEditingController();
List<VideoData> _filteredVideos = videoList;
int? _watchedCount;
@override
void initState() {
super.initState();
_filteredVideos = videoList;
_searchController.addListener(_onSearchChanged);
_loadWatchedCount();
}
Future<void> _loadWatchedCount() async {
final scope = (widget.scopeId ?? '').trim();
if (scope.isEmpty) {
if (mounted) setState(() => _watchedCount = null);
return;
}
final count = await WatchedVideosPrefs.getWatchedCount(scope);
if (!mounted) return;
setState(() => _watchedCount = count);
}
@override
@@ -302,6 +316,10 @@ class _VideoScreenState extends State<VideoScreen> {
),
),
),
if ((widget.scopeId ?? '').trim().isNotEmpty) ...[
const SizedBox(height: 12),
_CompletedEpisodesCard(count: _watchedCount),
],
const SizedBox(height: 16),
// Lista vertical (um vídeo abaixo do outro, rolando para
// baixo), mas cada card em si é horizontal — miniatura à
@@ -329,6 +347,7 @@ class _VideoScreenState extends State<VideoScreen> {
child: _VideoButton(
video: _filteredVideos[index],
scopeId: widget.scopeId,
onVideoClosed: _loadWatchedCount,
),
);
},
@@ -344,6 +363,66 @@ class _VideoScreenState extends State<VideoScreen> {
}
}
/// Cartão com o número de episódios já assistidos até ao fim pela criança
/// selecionada — vivia antes na Home como um dos dois "stat cards", mas
/// mudou-se para aqui (a própria biblioteca de vídeos) por ser mais
/// relevante neste contexto.
class _CompletedEpisodesCard extends StatelessWidget {
const _CompletedEpisodesCard({required this.count});
final int? count;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Container(
width: 40,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColors.purple.withValues(alpha: 0.14),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.movie_filter_rounded,
color: AppColors.purple,
size: 20,
),
),
const SizedBox(width: 12),
Text(
count == null ? '--' : '$count',
style: const TextStyle(fontWeight: FontWeight.w900, fontSize: 20),
),
const SizedBox(width: 8),
Text(
HomeStrings.completedEpisodes,
style: TextStyle(
fontSize: 12.5,
fontWeight: FontWeight.w600,
color: Colors.black.withValues(alpha: 0.6),
),
),
],
),
);
}
}
/// Preview de um vídeo (frame local ou thumbnail do YouTube), reutilizável
/// em qualquer card que precise mostrar "a cara" de um episódio.
class VideoThumbnail extends StatefulWidget {
@@ -509,18 +588,20 @@ class _VideoThumbnailState extends State<VideoThumbnail> {
}
class _VideoButton extends StatelessWidget {
const _VideoButton({required this.video, this.scopeId});
const _VideoButton({required this.video, this.scopeId, this.onVideoClosed});
final VideoData video;
final String? scopeId;
final VoidCallback? onVideoClosed;
void _showVideoPlayer(BuildContext context, VideoData video) {
Future<void> _showVideoPlayer(BuildContext context, VideoData video) async {
if (video.youtubeId == null) {
// Liberta todos os decodificadores de prévia antes de abrir o player
// em dialog, que precisa dos seus próprios decoders de vídeo/áudio.
_evictAllVideoControllers();
}
showVideoPlayerDialog(context, video, scopeId: scopeId);
await showVideoPlayerDialog(context, video, scopeId: scopeId);
onVideoClosed?.call();
}
@override