CTK 1.2.0
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user