Documentação

This commit is contained in:
Carlos Correia
2026-05-03 23:31:31 +01:00
commit d24cb3242a
167 changed files with 14263 additions and 0 deletions

View File

@@ -0,0 +1,212 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class CuriosidadeScreen extends StatelessWidget {
const CuriosidadeScreen({super.key});
static const Color _teal = Color(0xFF2F9E94);
@override
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
return Scaffold(
appBar: AppBar(
backgroundColor: _teal,
foregroundColor: Colors.white,
elevation: 0,
title: const Text(
'Curiosidades',
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: ListView(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
children: [
_CuriosityTopicTile(
title: 'Tema X',
description: 'Aprenda dicas rápidas e simples para cuidar dos dentes no dia a dia.',
),
const SizedBox(height: 12),
const _CuriosityTopicTile(
title: 'Tema Y',
description: 'Conteúdo em breve.',
),
const SizedBox(height: 12),
const _CuriosityTopicTile(
title: 'Tema Z',
description: 'Conteúdo em breve.',
),
const SizedBox(height: 12),
const _CuriosityTopicTile(
title: 'Tema U',
description: 'Conteúdo em breve.',
),
],
),
),
),
),
],
),
);
}
}
class _CuriosityTopicTile extends StatelessWidget {
const _CuriosityTopicTile({required this.title, required this.description});
final String title;
final String description;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white.withValues(alpha: 0.82),
borderRadius: BorderRadius.circular(18),
elevation: 8,
shadowColor: Colors.black.withValues(alpha: 0.10),
child: InkWell(
borderRadius: BorderRadius.circular(18),
onTap: () {
showModalBottomSheet<void>(
context: context,
showDragHandle: true,
backgroundColor: const Color(0xFFFFE6F1),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (ctx) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(18, 6, 18, 18),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
color: Color(0xFFFF55A7),
),
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.82),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.black.withValues(alpha: 0.08)),
),
child: Text(
description,
style: TextStyle(
color: Colors.black.withValues(alpha: 0.72),
fontWeight: FontWeight.w600,
height: 1.25,
),
),
),
const SizedBox(height: 14),
SizedBox(
height: 44,
child: FilledButton(
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF2F9E94),
foregroundColor: Colors.white,
shape: const StadiumBorder(),
textStyle: const TextStyle(fontWeight: FontWeight.w900),
),
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Fechar'),
),
),
],
),
),
);
},
);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 18, 16, 18),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFFF55A7).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.lightbulb_rounded,
color: Color(0xFFFF55A7),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w900,
color: Color(0xFF2F9E94),
),
),
),
const Icon(Icons.chevron_right_rounded, color: Colors.black54),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,92 @@
import 'dart:async';
import 'package:flutter/material.dart';
class HelloSplashScreen extends StatefulWidget {
const HelloSplashScreen({super.key, required this.onFinished, this.duration = const Duration(seconds: 5)});
final Duration duration;
final VoidCallback onFinished;
@override
State<HelloSplashScreen> createState() => _HelloSplashScreenState();
}
class _HelloSplashScreenState extends State<HelloSplashScreen> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _opacity;
Timer? _fadeTimer;
Timer? _doneTimer;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_opacity = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
_controller.value = 1.0;
final int fadeMs = (widget.duration.inMilliseconds - 500).clamp(0, widget.duration.inMilliseconds);
_fadeTimer = Timer(Duration(milliseconds: fadeMs), () {
if (!mounted) return;
_controller.reverse();
});
_doneTimer = Timer(widget.duration, () {
if (!mounted) return;
widget.onFinished();
});
}
@override
void dispose() {
_fadeTimer?.cancel();
_doneTimer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.sizeOf(context);
return Scaffold(
body: FadeTransition(
opacity: _opacity,
child: Container(
width: size.width,
height: size.height,
color: const Color(0xFFFFC9DF),
child: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Text(
'Olá',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 64,
fontWeight: FontWeight.w900,
color: Colors.white,
height: 1.0,
),
),
],
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,315 @@
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,
),
),
],
),
),
),
);
}
}