From 89a92dbdf6e495c85aa60cca2963bd472b362131 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 22:52:17 -0300 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20adicionar=20link=20para=20reposit?= =?UTF-8?q?=C3=B3rios=20do=20GitHub=20na=20se=C3=A7=C3=A3o=20de=20projetos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/constants/app_constants.dart | 3 +++ .../widgets/organisms/projects_section.dart | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/core/constants/app_constants.dart b/lib/core/constants/app_constants.dart index 0eaa4d6..22bcd39 100644 --- a/lib/core/constants/app_constants.dart +++ b/lib/core/constants/app_constants.dart @@ -25,6 +25,9 @@ class AppStrings { static const String whatsappUrl = 'https://api.whatsapp.com/send?phone=5582999915558'; static const String emailUrl = 'mailto:franklyn.dev.mobile@gmail.com'; + + static const String gitHubRepositoriesUrl = + 'https://github.com/Franklyn-R-Silva?tab=repositories'; } class AppColors { diff --git a/lib/presentation/widgets/organisms/projects_section.dart b/lib/presentation/widgets/organisms/projects_section.dart index 8f84dfe..5992dd3 100644 --- a/lib/presentation/widgets/organisms/projects_section.dart +++ b/lib/presentation/widgets/organisms/projects_section.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_animate/flutter_animate.dart'; +import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; +import 'package:url_launcher/url_launcher.dart'; // Project imports: import '../../../data/models/project_model.dart'; @@ -13,6 +15,14 @@ class ProjectsSection extends StatelessWidget { const ProjectsSection({super.key, required this.projects}); + void _launchGitHub() async { + final Uri url = Uri.parse(AppStrings.gitHubRepositoriesUrl); + + if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { + throw 'Could not launch $url'; + } + } + @override Widget build(BuildContext context) { return Column( @@ -30,7 +40,7 @@ class ProjectsSection extends StatelessWidget { ), ), TextButton.icon( - onPressed: () {}, + onPressed: _launchGitHub, icon: const Icon(Icons.arrow_forward), label: const Text("Ver todos no GitHub"), ), From 2d3cbab691aeff823267eabea63511990ede0e34 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 22:58:21 -0300 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20adicionar=20verifica=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20sess=C3=A3o=20no=20AuthController=20e=20bot=C3=B5es=20d?= =?UTF-8?q?e=20a=C3=A7=C3=A3o=20na=20HomePage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/auth_controller.dart | 13 ++++- lib/presentation/pages/home_page.dart | 49 ++++++++++++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/lib/presentation/controllers/auth_controller.dart b/lib/presentation/controllers/auth_controller.dart index a624a42..fd4c41a 100644 --- a/lib/presentation/controllers/auth_controller.dart +++ b/lib/presentation/controllers/auth_controller.dart @@ -7,10 +7,21 @@ import '../../data/repositories/supabase_repository.dart'; class AuthController extends ChangeNotifier { final SupabaseRepository repository; - AuthController(this.repository); + AuthController(this.repository) { + // Verifica se já existe uma sessão ativa ao inicializar + _checkSession(); + } bool get isLogged => repository.isAuthenticated; + // Verifica a sessão existente + void _checkSession() { + // O Supabase automaticamente restaura a sessão se houver uma válida + if (repository.isAuthenticated) { + notifyListeners(); + } + } + Future login(String email, String pass) async { final success = await repository.signIn(email, pass); if (success) notifyListeners(); diff --git a/lib/presentation/pages/home_page.dart b/lib/presentation/pages/home_page.dart index 38d0c0a..453e2de 100644 --- a/lib/presentation/pages/home_page.dart +++ b/lib/presentation/pages/home_page.dart @@ -10,8 +10,10 @@ import 'package:url_launcher/url_launcher.dart'; // Project imports: import 'package:meu_curriculo_flutter/l10n/arb/app_localizations.dart'; +import 'package:meu_curriculo_flutter/presentation/pages/admin/admin_dashboard_page.dart'; import 'package:meu_curriculo_flutter/presentation/pages/admin/login_page.dart'; import '../../../core/constants/app_constants.dart'; +import '../controllers/auth_controller.dart'; import '../controllers/portfolio_controller.dart'; import '../widgets/organisms/certificates_section.dart'; import '../widgets/organisms/experience_section.dart'; @@ -205,13 +207,46 @@ class _HomePageState extends State { ), ], ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () => launchUrl(Uri.parse(AppAssets.cvPtBr)), - label: const Text("Baixar CV"), - icon: const Icon(Icons.download_rounded), - backgroundColor: Theme.of(context).colorScheme.primary, - foregroundColor: Colors.white, - ), + floatingActionButton: _buildFloatingActionButtons(context), + ); + } + + Widget _buildFloatingActionButtons(BuildContext context) { + final authController = context.watch(); + final isLoggedIn = authController.isLogged; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Botão de Admin (só aparece se estiver logado) + if (isLoggedIn) ...[ + FloatingActionButton( + heroTag: 'admin_fab', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AdminDashboardPage(), + ), + ); + }, + backgroundColor: Colors.deepPurple, + foregroundColor: Colors.white, + child: const Icon(Icons.admin_panel_settings), + ), + const SizedBox(height: 12), + ], + // Botão de Download CV (sempre visível) + FloatingActionButton.extended( + heroTag: 'download_fab', + onPressed: () => launchUrl(Uri.parse(AppAssets.cvPtBr)), + label: const Text("Baixar CV"), + icon: const Icon(Icons.download_rounded), + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Colors.white, + ), + ], ); } } From 939b847264814b5fa2393a06478637e99884ae01 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:08:51 -0300 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20adicionar=20tabela=20de=20logs=20?= =?UTF-8?q?de=20aplicativo=20e=20pol=C3=ADticas=20de=20acesso=20para=20usu?= =?UTF-8?q?=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/docs/create.sql | 26 ++++++++++++++++++++- lib/core/constants/.gitkeep | 0 lib/core/theme/.gitkeep | 0 lib/core/utils/.gitkeep | 0 lib/data/mocks/.gitkeep | 0 lib/data/models/.gitkeep | 0 lib/data/repositories/.gitkeep | 0 lib/presentation/controllers/.gitkeep | 0 lib/presentation/pages/.gitkeep | 0 lib/presentation/widgets/atoms/.gitkeep | 0 lib/presentation/widgets/molecules/.gitkeep | 0 lib/presentation/widgets/organisms/.gitkeep | 0 12 files changed, 25 insertions(+), 1 deletion(-) delete mode 100644 lib/core/constants/.gitkeep delete mode 100644 lib/core/theme/.gitkeep delete mode 100644 lib/core/utils/.gitkeep delete mode 100644 lib/data/mocks/.gitkeep delete mode 100644 lib/data/models/.gitkeep delete mode 100644 lib/data/repositories/.gitkeep delete mode 100644 lib/presentation/controllers/.gitkeep delete mode 100644 lib/presentation/pages/.gitkeep delete mode 100644 lib/presentation/widgets/atoms/.gitkeep delete mode 100644 lib/presentation/widgets/molecules/.gitkeep delete mode 100644 lib/presentation/widgets/organisms/.gitkeep diff --git a/assets/docs/create.sql b/assets/docs/create.sql index 31438d5..3c89fc5 100644 --- a/assets/docs/create.sql +++ b/assets/docs/create.sql @@ -69,4 +69,28 @@ create policy "Admin Delete Skills" on skills for delete to authenticated using -- Tabela CERTIFICATES create policy "Admin Insert Certs" on certificates for insert to authenticated with check (true); create policy "Admin Update Certs" on certificates for update to authenticated using (true); -create policy "Admin Delete Certs" on certificates for delete to authenticated using (true); \ No newline at end of file +create policy "Admin Delete Certs" on certificates for delete to authenticated using (true); + + +create table app_logs ( + id bigserial primary key, + level text, -- info, debug, error + message text, + stack text, + timestamp timestamptz default now(), + user_id uuid -- opcional, se tiver login +); + +-- Permitir que o usuário insira apenas logs com user_id igual ao seu UID +create policy "Usuário pode inserir seu próprio log" +on app_logs +for insert +with check (auth.uid() = user_id); + +-- Permitir que o usuário selecione apenas seus próprios logs +create policy "Usuário pode ler seus próprios logs" +on app_logs +for select +using (auth.uid() = user_id); + + diff --git a/lib/core/constants/.gitkeep b/lib/core/constants/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/core/theme/.gitkeep b/lib/core/theme/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/core/utils/.gitkeep b/lib/core/utils/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/data/mocks/.gitkeep b/lib/data/mocks/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/data/models/.gitkeep b/lib/data/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/data/repositories/.gitkeep b/lib/data/repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/presentation/controllers/.gitkeep b/lib/presentation/controllers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/presentation/pages/.gitkeep b/lib/presentation/pages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/presentation/widgets/atoms/.gitkeep b/lib/presentation/widgets/atoms/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/presentation/widgets/molecules/.gitkeep b/lib/presentation/widgets/molecules/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/presentation/widgets/organisms/.gitkeep b/lib/presentation/widgets/organisms/.gitkeep deleted file mode 100644 index e69de29..0000000 From 2057d91bca72c488f3b48a20327e4ca4c65aaa5c Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:15:37 -0300 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20adicionar=20sistema=20de=20loggin?= =?UTF-8?q?g=20de=20erros=20com=20AppLogger=20em=20v=C3=A1rias=20partes=20?= =?UTF-8?q?do=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/utils/app_logger.dart | 25 +++++++++++++ lib/core/utils/app_utils.dart | 8 ++++- .../repositories/supabase_repository.dart | 36 ++++++++++++++++--- .../controllers/portfolio_controller.dart | 8 ++++- .../pages/admin/admin_dashboard_page.dart | 8 ++++- .../widgets/forms/certificate_form.dart | 8 ++++- .../widgets/forms/experience_form.dart | 8 ++++- .../widgets/forms/project_form.dart | 8 ++++- .../widgets/forms/skill_form.dart | 8 ++++- 9 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 lib/core/utils/app_logger.dart diff --git a/lib/core/utils/app_logger.dart b/lib/core/utils/app_logger.dart new file mode 100644 index 0000000..12cd9c2 --- /dev/null +++ b/lib/core/utils/app_logger.dart @@ -0,0 +1,25 @@ +// Package imports: +import 'package:supabase_flutter/supabase_flutter.dart'; + +final supabase = Supabase.instance.client; + +class AppLogger { + static Future log({ + required String level, + required String message, + String? stack, + String? userId, + }) async { + try { + await supabase.from('app_logs').insert({ + 'level': level, + 'message': message, + 'stack': stack, + 'user_id': userId, + }); + } catch (e) { + // evita que falha no logging quebre o app + print('Erro ao gravar log: $e'); + } + } +} diff --git a/lib/core/utils/app_utils.dart b/lib/core/utils/app_utils.dart index 6a1c757..9859bc6 100644 --- a/lib/core/utils/app_utils.dart +++ b/lib/core/utils/app_utils.dart @@ -1,5 +1,6 @@ // Flutter imports: import 'package:flutter/material.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; // Package imports: import 'package:url_launcher/url_launcher.dart'; @@ -13,7 +14,12 @@ class AppUtils { if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) { throw Exception('Could not launch $url'); } - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); if (context != null && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Não foi possível abrir o link: $e')), diff --git a/lib/data/repositories/supabase_repository.dart b/lib/data/repositories/supabase_repository.dart index 703134d..5a58144 100644 --- a/lib/data/repositories/supabase_repository.dart +++ b/lib/data/repositories/supabase_repository.dart @@ -5,6 +5,7 @@ import 'dart:developer'; import 'package:supabase_flutter/supabase_flutter.dart'; // Project imports: +import '../../core/utils/app_logger.dart'; import '../models/certificate_model.dart'; import '../models/experience_model.dart'; import '../models/project_model.dart'; @@ -22,7 +23,12 @@ class SupabaseRepository implements IPortfolioRepository { password: password, ); return response.user != null; - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); log('Erro login: $e'); return false; } @@ -68,7 +74,12 @@ class SupabaseRepository implements IPortfolioRepository { (e) => ProjectModel.fromMap(e), ) // Certifique-se que ProjectModel tem o método fromMap .toList(); - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); // Em caso de erro, retorna lista vazia ou lança exceção log('Erro ao buscar projetos: $e'); return []; @@ -88,7 +99,12 @@ class SupabaseRepository implements IPortfolioRepository { (e) => ExperienceModel.fromMap(e), ) // Certifique-se que ExperienceModel tem fromMap .toList(); - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); log('Erro ao buscar experiências: $e'); return []; } @@ -107,7 +123,12 @@ class SupabaseRepository implements IPortfolioRepository { (e) => SkillModel.fromMap(e), ) // Certifique-se que SkillModel tem fromMap .toList(); - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); log('Erro ao buscar skills: $e'); return []; } @@ -124,7 +145,12 @@ class SupabaseRepository implements IPortfolioRepository { return (response as List) .map((e) => CertificateModel.fromMap(e)) .toList(); - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); // Log de erro para ajudar no debug log('Erro ao buscar certificados: $e'); return []; diff --git a/lib/presentation/controllers/portfolio_controller.dart b/lib/presentation/controllers/portfolio_controller.dart index e55bfcc..8e0ecf0 100644 --- a/lib/presentation/controllers/portfolio_controller.dart +++ b/lib/presentation/controllers/portfolio_controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; // Project imports: +import '../../core/utils/app_logger.dart'; import '../../data/models/certificate_model.dart'; import '../../data/models/experience_model.dart'; import '../../data/models/project_model.dart'; @@ -64,9 +65,14 @@ class PortfolioController extends ChangeNotifier { _loadSkills(), _loadCertificates(), ]); - } catch (e) { + } catch (e, stack) { debugPrint('Erro ao carregar dados: $e'); errorMessage = 'Falha ao carregar dados. Verifique sua conexão.'; + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); } finally { isLoading = false; notifyListeners(); diff --git a/lib/presentation/pages/admin/admin_dashboard_page.dart b/lib/presentation/pages/admin/admin_dashboard_page.dart index fcd4409..9b49ffd 100644 --- a/lib/presentation/pages/admin/admin_dashboard_page.dart +++ b/lib/presentation/pages/admin/admin_dashboard_page.dart @@ -9,6 +9,7 @@ import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; import 'package:meu_curriculo_flutter/data/models/project_model.dart'; import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; +import '../../../core/utils/app_logger.dart'; import '../../controllers/auth_controller.dart'; import '../../controllers/portfolio_controller.dart'; import '../../widgets/forms/certificate_form.dart'; @@ -72,7 +73,12 @@ class _AdminDashboardPageState extends State ); portfolio.loadAllData(); } - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); if (mounted) { ScaffoldMessenger.of( context, diff --git a/lib/presentation/widgets/forms/certificate_form.dart b/lib/presentation/widgets/forms/certificate_form.dart index 978b40b..1be7824 100644 --- a/lib/presentation/widgets/forms/certificate_form.dart +++ b/lib/presentation/widgets/forms/certificate_form.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: +import '../../../core/utils/app_logger.dart'; import '../../../data/models/certificate_model.dart'; import '../../controllers/auth_controller.dart'; import '../../controllers/portfolio_controller.dart'; @@ -98,7 +99,12 @@ class _CertificateFormState extends State { const SnackBar(content: Text('Certificado salvo com sucesso!')), ); } - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); if (mounted) { ScaffoldMessenger.of( context, diff --git a/lib/presentation/widgets/forms/experience_form.dart b/lib/presentation/widgets/forms/experience_form.dart index 17f316c..2213605 100644 --- a/lib/presentation/widgets/forms/experience_form.dart +++ b/lib/presentation/widgets/forms/experience_form.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: +import '../../../core/utils/app_logger.dart'; import '../../../data/models/experience_model.dart'; import '../../controllers/auth_controller.dart'; import '../../controllers/portfolio_controller.dart'; @@ -85,7 +86,12 @@ class _ExperienceFormState extends State { const SnackBar(content: Text('Experiência salva com sucesso!')), ); } - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); if (mounted) { ScaffoldMessenger.of( context, diff --git a/lib/presentation/widgets/forms/project_form.dart b/lib/presentation/widgets/forms/project_form.dart index a481434..911c279 100644 --- a/lib/presentation/widgets/forms/project_form.dart +++ b/lib/presentation/widgets/forms/project_form.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: +import '../../../core/utils/app_logger.dart'; import '../../../data/models/project_model.dart'; import '../../controllers/auth_controller.dart'; import '../../controllers/portfolio_controller.dart'; @@ -89,7 +90,12 @@ class _ProjectFormState extends State { const SnackBar(content: Text('Projeto salvo com sucesso!')), ); } - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); if (mounted) { ScaffoldMessenger.of( context, diff --git a/lib/presentation/widgets/forms/skill_form.dart b/lib/presentation/widgets/forms/skill_form.dart index 530504f..dd3fad5 100644 --- a/lib/presentation/widgets/forms/skill_form.dart +++ b/lib/presentation/widgets/forms/skill_form.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: +import '../../../core/utils/app_logger.dart'; import '../../../data/models/skill_model.dart'; import '../../controllers/auth_controller.dart'; import '../../controllers/portfolio_controller.dart'; @@ -78,7 +79,12 @@ class _SkillFormState extends State { const SnackBar(content: Text('Skill salva com sucesso!')), ); } - } catch (e) { + } catch (e, stack) { + await AppLogger.log( + level: 'error', + message: e.toString(), + stack: stack.toString(), + ); if (mounted) { ScaffoldMessenger.of( context, From 5f11d6eee48887be74d69df1696555ec8533e47a Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:17:38 -0300 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20adicionar=20suporte=20para=20logs?= =?UTF-8?q?=20de=20aviso=20e=20ajuste=20na=20inser=C3=A7=C3=A3o=20de=20log?= =?UTF-8?q?s=20com=20userId=20do=20usu=C3=A1rio=20autenticado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/docs/create.sql | 21 +++++++++++++++++---- lib/core/utils/app_logger.dart | 25 +++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/assets/docs/create.sql b/assets/docs/create.sql index 3c89fc5..18a620e 100644 --- a/assets/docs/create.sql +++ b/assets/docs/create.sql @@ -74,18 +74,21 @@ create policy "Admin Delete Certs" on certificates for delete to authenticated u create table app_logs ( id bigserial primary key, - level text, -- info, debug, error + level text, -- info, debug, error, warning message text, stack text, timestamp timestamptz default now(), user_id uuid -- opcional, se tiver login ); --- Permitir que o usuário insira apenas logs com user_id igual ao seu UID -create policy "Usuário pode inserir seu próprio log" +-- Habilitar RLS +alter table app_logs enable row level security; + +-- Permitir inserção de logs (autenticados ou anônimos) +create policy "Qualquer um pode inserir logs" on app_logs for insert -with check (auth.uid() = user_id); +with check (true); -- Permitir que o usuário selecione apenas seus próprios logs create policy "Usuário pode ler seus próprios logs" @@ -93,4 +96,14 @@ on app_logs for select using (auth.uid() = user_id); +-- Permitir que admins leiam todos os logs +create policy "Admin pode ler todos os logs" +on app_logs +for select +using (auth.uid() IN ( + -- Adicione aqui os UUIDs dos usuários admin + -- Exemplo: 'uuid-do-admin-1', 'uuid-do-admin-2' + select id from auth.users where email = 'seu-email-admin@exemplo.com' +)); + diff --git a/lib/core/utils/app_logger.dart b/lib/core/utils/app_logger.dart index 12cd9c2..4bda5c2 100644 --- a/lib/core/utils/app_logger.dart +++ b/lib/core/utils/app_logger.dart @@ -11,15 +11,36 @@ class AppLogger { String? userId, }) async { try { + // Se não passar userId, tenta pegar o usuário autenticado + final currentUserId = userId ?? supabase.auth.currentUser?.id; + await supabase.from('app_logs').insert({ 'level': level, 'message': message, 'stack': stack, - 'user_id': userId, + 'user_id': currentUserId, }); } catch (e) { // evita que falha no logging quebre o app - print('Erro ao gravar log: $e'); + // Use apenas print em dev, em produção considere remover ou usar serviço externo + print('⚠️ Erro ao gravar log no Supabase: $e'); } } + + // Métodos auxiliares para facilitar o uso + static Future info(String message, {String? stack}) async { + await log(level: 'info', message: message, stack: stack); + } + + static Future debug(String message, {String? stack}) async { + await log(level: 'debug', message: message, stack: stack); + } + + static Future error(String message, {String? stack}) async { + await log(level: 'error', message: message, stack: stack); + } + + static Future warning(String message, {String? stack}) async { + await log(level: 'warning', message: message, stack: stack); + } } From e5d7280120b209dbed8f97e65f647e2fbb3e6685 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:19:52 -0300 Subject: [PATCH 06/12] feat: adicionar coluna opcional icon_asset na tabela de habilidades --- assets/docs/create.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assets/docs/create.sql b/assets/docs/create.sql index 18a620e..17d1da1 100644 --- a/assets/docs/create.sql +++ b/assets/docs/create.sql @@ -28,6 +28,7 @@ create table skills ( name text not null, type text not null, -- Vamos usar: 'mobile', 'web' ou 'tools' is_highlight boolean default false, -- Se é destaque (true/false) + icon_asset text, -- Caminho do ícone (opcional) created_at timestamp with time zone default timezone('utc'::text, now()) not null ); @@ -43,8 +44,6 @@ create policy "Qualquer um pode ler experiencias" on experiences for select usin alter table skills enable row level security; create policy "Qualquer um pode ler skills" on skills for select using (true); -ALTER TABLE skills ADD COLUMN icon_url text; - alter table certificates enable row level security; create policy "Public Access" on certificates for select using (true); From 17fda3bfd895096d94e5670825f14bab67002638 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:33:24 -0300 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20ajustar=20layout=20do=20ProjectCa?= =?UTF-8?q?rd=20e=20adicionar=20bot=C3=A3o=20para=20Live=20Demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/molecules/project_card.dart | 102 ++++++++++++------ 1 file changed, 72 insertions(+), 30 deletions(-) diff --git a/lib/presentation/widgets/molecules/project_card.dart b/lib/presentation/widgets/molecules/project_card.dart index 1357f02..e415681 100644 --- a/lib/presentation/widgets/molecules/project_card.dart +++ b/lib/presentation/widgets/molecules/project_card.dart @@ -84,21 +84,22 @@ class _ProjectCardState extends State { children: [ // Conteúdo do Card Padding( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const FaIcon( FontAwesomeIcons.folderOpen, - size: 30, + size: 28, color: Color(AppColors.primary), ), FaIcon( FontAwesomeIcons.arrowUpRightFromSquare, - size: 16, + size: 14, color: theme.iconTheme.color?.withValues( alpha: 0.5, @@ -107,33 +108,40 @@ class _ProjectCardState extends State { ), ], ), - const SizedBox(height: 20), + const SizedBox(height: 16), Text( widget.project.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, + fontSize: 18, ), ), - const SizedBox(height: 10), - Text( - widget.project.description, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyMedium?.copyWith( - height: 1.5, - color: theme.textTheme.bodyMedium?.color - ?.withValues(alpha: 0.8), + const SizedBox(height: 8), + Flexible( + child: Text( + widget.project.description, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodyMedium?.copyWith( + height: 1.4, + fontSize: 13, + color: theme.textTheme.bodyMedium?.color + ?.withValues(alpha: 0.8), + ), ), ), - const Spacer(), + const SizedBox(height: 12), Wrap( - spacing: 8, - runSpacing: 8, + spacing: 6, + runSpacing: 6, children: widget.project.techStack .take(3) .map((t) => TechChip(label: t)) .toList(), ), + const SizedBox(height: 12), ], ), ), @@ -141,25 +149,27 @@ class _ProjectCardState extends State { // Efeito de Brilho (Gradient Overlay) ao passar o mouse if (hovered) Positioned.fill( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: LinearGradient( - colors: [ - Colors.white.withValues( - alpha: isDark ? 0.1 : 0.4, - ), - Colors.transparent, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - stops: const [0.0, 0.4], + child: IgnorePointer( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + colors: [ + Colors.white.withValues( + alpha: isDark ? 0.1 : 0.4, + ), + Colors.transparent, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: const [0.0, 0.4], + ), ), ), ), ), - // Link Clicável + // Link Clicável para o GitHub (todo o card) Positioned.fill( child: Material( color: Colors.transparent, @@ -170,6 +180,38 @@ class _ProjectCardState extends State { ), ), ), + + // Botão Live Demo (se existir) - Por último para ficar por cima + if (widget.project.liveUrl != null && + widget.project.liveUrl!.isNotEmpty) + Positioned( + left: 20, + right: 20, + bottom: 20, + child: MouseRegion( + onEnter: (_) => _isHovered.value = false, + onExit: (_) {}, + child: ElevatedButton.icon( + onPressed: () => launchUrl( + Uri.parse(widget.project.liveUrl!), + ), + icon: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 16, + ), + label: const Text('Ver Demo'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + backgroundColor: theme.colorScheme.primary, + foregroundColor: Colors.white, + elevation: 3, + ), + ), + ), + ), ], ), ), From 5f215ef7d3f15e7c0ee7bd4d3ed63f09fcd5f993 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:33:34 -0300 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20simplificar=20chamada=20de=20fun?= =?UTF-8?q?=C3=A7=C3=A3o=20para=20abrir=20URL=20no=20bot=C3=A3o=20do=20Pro?= =?UTF-8?q?jectCard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/presentation/widgets/molecules/project_card.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/presentation/widgets/molecules/project_card.dart b/lib/presentation/widgets/molecules/project_card.dart index e415681..267db01 100644 --- a/lib/presentation/widgets/molecules/project_card.dart +++ b/lib/presentation/widgets/molecules/project_card.dart @@ -192,9 +192,8 @@ class _ProjectCardState extends State { onEnter: (_) => _isHovered.value = false, onExit: (_) {}, child: ElevatedButton.icon( - onPressed: () => launchUrl( - Uri.parse(widget.project.liveUrl!), - ), + onPressed: () => + launchUrl(Uri.parse(widget.project.liveUrl!)), icon: const FaIcon( FontAwesomeIcons.arrowUpRightFromSquare, size: 16, From efe357b43bf5e36f3c3b4ff35f7b2382dc8dcf65 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:38:57 -0300 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20atualizar=20README.md=20com=20mel?= =?UTF-8?q?horias=20na=20descri=C3=A7=C3=A3o=20do=20projeto=20e=20ajustes?= =?UTF-8?q?=20na=20stack=20tecnol=C3=B3gica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 200 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9d6df09..dec0974 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,60 @@ # 🚀 Meu Portfólio - Flutter Web Experience -[![Tech Stack](https://go-skill-icons.vercel.app/api/icons?i=flutter,dart,vscode,androidstudio,git,github)](https://github.com/coagro-lab/coagro-app-supabase) +[![Tech Stack](https://go-skill-icons.vercel.app/api/icons?i=flutter,dart,supabase,vscode,androidstudio,git,github)](https://github.com/DevFullStack-Franklyn-R-Silva/meu_curriculo_flutter) -![Architecture](https://img.shields.io/badge/Architecture-Clean%20%2B%20MVVM-green) +![Architecture](https://img.shields.io/badge/Architecture-MVVM%20%2B%20Repository%20Pattern-green) ![State Management](https://img.shields.io/badge/State-Provider-blueviolet) +![Backend](https://img.shields.io/badge/Backend-Supabase-3ECF8E) +![Design Pattern](https://img.shields.io/badge/Design-Atomic%20Design-orange) -> Um portfólio interativo e responsivo desenvolvido com **Flutter Web**, focado em demonstrar UI/UX avançada, física, animações complexas e arquitetura de software limpa. +> Um portfólio **interativo e responsivo** desenvolvido com **Flutter Web**, integrando **Supabase** como backend e demonstrando **arquitetura profissional**, UI/UX avançada, animações complexas e gerenciamento de estado robusto. --- ## 🎨 Funcionalidades & Destaques -Este projeto vai além de uma simples landing page estática. Ele implementa conceitos avançados de renderização e interatividade: +Este projeto vai além de uma simples landing page estática. Ele implementa conceitos avançados de renderização, interatividade e arquitetura: -- **🌌 Hero Section com Física (Gravity/Magnetic):** Ícones de tecnologia que reagem à proximidade do mouse, simulando um campo magnético reverso. -- **🧊 Header Glassmorphism:** Barra de navegação flutuante com efeito de desfoque (blur) e transparência em tempo real. -- **🖥️ Cards Holográficos 3D:** Os cards de projeto inclinam em 3D (Tilt Effect) seguindo a posição do cursor, com iluminação dinâmica. -- **✨ Animações Fluidas:** Uso extensivo do pacote `flutter_animate` para entradas em cascata e micro-interações. -- **📱 Totalmente Responsivo:** Layout adaptativo que funciona perfeitamente em Mobile, Tablet e Desktop (Web). +### 🎯 **Funcionalidades Principais** + +- **🔐 Sistema de Autenticação:** Login com Supabase Auth e persistência de sessão +- **📊 Painel Admin (CRUD Completo):** Gerenciamento de projetos, experiências, habilidades e certificados em tempo real +- **🌐 Internacionalização (i18n):** Suporte para múltiplos idiomas (PT-BR e EN) +- **🌓 Dark Mode:** Alternância entre tema claro e escuro com persistência + +### ✨ **UI/UX Avançada** + +- **🌌 Hero Section com Física:** Ícones de tecnologia com efeito magnético reverso (repulsão ao mouse) +- **🧊 Header Glassmorphism:** Navegação flutuante com blur e transparência dinâmica +- **🖥️ Cards Holográficos 3D:** Efeito tilt 3D seguindo o cursor com iluminação dinâmica +- **⚡ Animações Fluidas:** Micro-interações com `flutter_animate` e animações customizadas +- **📱 Totalmente Responsivo:** Layout adaptativo para Mobile, Tablet e Desktop +- **🎬 Intro Animada:** Loading screen estilo terminal hacker com efeitos de digitação --- -## 🛠️ Tecnologias Utilizadas +## 🛠️ Stack Tecnológica + +### **Core** + +- **Linguagem:** [Dart 3.x](https://dart.dev/) +- **Framework:** [Flutter 3.27+](https://flutter.dev/) (Web, Android, iOS) +- **Backend:** [Supabase](https://supabase.com/) (PostgreSQL + Auth + Storage) + +### **Arquitetura & Padrões** + +- **Padrão de Projeto:** MVVM + Repository Pattern + Clean Architecture Elements +- **Gerência de Estado:** `provider` (ChangeNotifier) +- **Injeção de Dependência:** Provider DI +- **Design System:** Atomic Design (Atoms → Molecules → Organisms) -- **Linguagem:** [Dart](https://dart.dev/) -- **Framework:** [Flutter](https://flutter.dev/) (Foco em Web) -- **Gerência de Estado:** `provider` (Padrão ChangeNotifier) -- **Animações:** `flutter_animate` + `AnimationController` nativo (para física) -- **Fontes & Ícones:** `google_fonts`, `font_awesome_flutter` -- **Links Externos:** `url_launcher` +### **Bibliotecas Principais** + +- **Animações:** `flutter_animate`, `AnimationController` customizados +- **UI Components:** `google_fonts`, `font_awesome_flutter` +- **Networking:** `supabase_flutter`, `http` +- **Utilidades:** `url_launcher`, `flutter_dotenv` +- **Internacionalização:** `flutter_localizations`, ARB files --- @@ -55,6 +81,164 @@ lib/ --- +## 🏗️ Arquitetura do Projeto + +### **Padrão Arquitetural: MVVM + Repository Pattern + Clean Architecture Elements** + +O projeto implementa uma arquitetura híbrida robusta que combina os melhores aspectos de MVVM, Repository Pattern e Clean Architecture: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ PRESENTATION LAYER │ +│ ┌────────────────┐ ┌──────────────────────────────┐ │ +│ │ │ Provider │ │ │ +│ │ VIEW │◄─────────►│ VIEW MODEL │ │ +│ │ (Pages + │ Binding │ (Controllers) │ │ +│ │ Widgets) │ │ │ │ +│ │ │ │ • PortfolioController │ │ +│ └────────────────┘ │ • AuthController │ │ +│ • home_page.dart │ │ │ +│ • admin_dashboard_page.dart │ State Management: Provider │ │ +│ • Atomic Design Components │ (ChangeNotifier Pattern) │ │ +│ └──────────────┬───────────────┘ │ +└───────────────────────────────────────────────┼─────────────────────┘ + │ + │ Dependency + │ Injection + ↓ +┌─────────────────────────────────────────────────────────────────────┐ +│ DATA LAYER │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Repository Interface (Contract) │ │ +│ │ │ │ +│ │ abstract class IPortfolioRepository { │ │ +│ │ Future> getProjects(); │ │ +│ │ Future addProject(Project project); │ │ +│ │ } │ │ +│ └───────────────────────┬─────────────────────────────────┘ │ +│ │ implements │ +│ ↓ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Repository Implementation │ │ +│ │ │ │ +│ │ class SupabaseRepository │ │ +│ │ implements IPortfolioRepository { │ │ +│ │ │ │ +│ │ • getProjects() │ │ +│ │ • getExperiences() │ │ +│ │ • getSkills() │ │ +│ │ • getCertificates() │ │ +│ │ • CRUD Operations │ │ +│ │ • Error Handling │ │ +│ │ • Logging │ │ +│ │ } │ │ +│ └───────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ↓ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ MODELS (Entities) │ │ +│ │ │ │ +│ │ • ProjectModel • SkillModel │ │ +│ │ • ExperienceModel • CertificateModel │ │ +│ │ │ │ +│ │ Responsibilities: │ │ +│ │ - Data structure definition │ │ +│ │ - JSON serialization (toMap/fromMap) │ │ +│ │ - Type validation │ │ +│ └───────────────────────┬─────────────────────────────────┘ │ +│ │ │ +└───────────────────────────┼───────────────────────────────────────┘ + │ + ↓ +┌─────────────────────────────────────────────────────────────────────┐ +│ EXTERNAL DATA SOURCE │ +│ │ +│ ┌─────────────────────┐ │ +│ │ SUPABASE │ │ +│ │ │ │ +│ │ • PostgreSQL DB │ │ +│ │ • Auth System │ │ +│ │ • Real-time Sync │ │ +│ │ • Row Level Sec. │ │ +│ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### **Fluxo de Dados** + +``` +USER INTERACTION + ↓ +┌──────────────────────┐ +│ View (Widget) │ → User taps button, enters text +└──────────┬───────────┘ + │ + ↓ Event (onPressed, onChange) +┌──────────────────────┐ +│ ViewModel │ → Receives event, updates state +│ (Controller) │ → Calls repository methods +└──────────┬───────────┘ + │ + ↓ Method call +┌──────────────────────┐ +│ Repository │ → Handles data operations +│ (Data Layer) │ → Interacts with Supabase +└──────────┬───────────┘ + │ + ↓ HTTP/gRPC +┌──────────────────────┐ +│ Supabase API │ → Returns data +└──────────┬───────────┘ + │ + ↓ Response +┌──────────────────────┐ +│ Repository │ → Converts to Models +└──────────┬───────────┘ + │ + ↓ Models +┌──────────────────────┐ +│ ViewModel │ → Updates state +│ (Controller) │ → notifyListeners() +└──────────┬───────────┘ + │ + ↓ State change +┌──────────────────────┐ +│ View (Widget) │ → Rebuilds with new data +└──────────────────────┘ +``` + +### **Princípios Aplicados** + +#### ✅ **SOLID Principles** + +- **S** - Single Responsibility: Cada classe tem uma responsabilidade única +- **O** - Open/Closed: Extensível via interfaces (IPortfolioRepository) +- **L** - Liskov Substitution: SupabaseRepository pode ser substituído por MockRepository +- **I** - Interface Segregation: Interfaces específicas para cada tipo de repositório +- **D** - Dependency Inversion: Controllers dependem de abstrações (interfaces) + +#### ✅ **Design Patterns** + +- **Repository Pattern**: Abstração da camada de dados +- **MVVM**: Separação entre View e lógica de negócio +- **Dependency Injection**: Provider para injeção de dependências +- **Observer Pattern**: ChangeNotifier para reatividade +- **Atomic Design**: Componentização hierárquica de UI + +### **Benefícios da Arquitetura** + +| Benefício | Descrição | +|-----------|-----------| +| **🧪 Testabilidade** | Fácil criar mocks para testes unitários | +| **🔧 Manutenibilidade** | Mudanças isoladas não afetam outras camadas | +| **📈 Escalabilidade** | Fácil adicionar novas features sem quebrar código existente | +| **🔄 Reusabilidade** | Componentes podem ser reutilizados em diferentes contextos | +| **👥 Colaboração** | Estrutura clara facilita trabalho em equipe | +| **🐛 Debugging** | Fluxo de dados previsível facilita identificação de bugs | + +--- + ## 🚀 Como Rodar o Projeto ### Pré-requisitos From a50475f104cbfa29654c1b32efca8d5df0b33d32 Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:39:00 -0300 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20melhorar=20formata=C3=A7=C3=A3o?= =?UTF-8?q?=20da=20tabela=20de=20benef=C3=ADcios=20na=20se=C3=A7=C3=A3o=20?= =?UTF-8?q?de=20arquitetura=20do=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dec0974..381a207 100644 --- a/README.md +++ b/README.md @@ -228,14 +228,14 @@ USER INTERACTION ### **Benefícios da Arquitetura** -| Benefício | Descrição | -|-----------|-----------| -| **🧪 Testabilidade** | Fácil criar mocks para testes unitários | -| **🔧 Manutenibilidade** | Mudanças isoladas não afetam outras camadas | -| **📈 Escalabilidade** | Fácil adicionar novas features sem quebrar código existente | -| **🔄 Reusabilidade** | Componentes podem ser reutilizados em diferentes contextos | -| **👥 Colaboração** | Estrutura clara facilita trabalho em equipe | -| **🐛 Debugging** | Fluxo de dados previsível facilita identificação de bugs | +| Benefício | Descrição | +| ----------------------- | ----------------------------------------------------------- | +| **🧪 Testabilidade** | Fácil criar mocks para testes unitários | +| **🔧 Manutenibilidade** | Mudanças isoladas não afetam outras camadas | +| **📈 Escalabilidade** | Fácil adicionar novas features sem quebrar código existente | +| **🔄 Reusabilidade** | Componentes podem ser reutilizados em diferentes contextos | +| **👥 Colaboração** | Estrutura clara facilita trabalho em equipe | +| **🐛 Debugging** | Fluxo de dados previsível facilita identificação de bugs | --- From 183d3afe8d8cc07845ee797be8e9860d8b48a60d Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:41:06 -0300 Subject: [PATCH 11/12] feat: reorganizar imports nos arquivos app_utils.dart e projects_section.dart --- lib/core/utils/app_utils.dart | 4 +++- lib/presentation/widgets/organisms/projects_section.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/core/utils/app_utils.dart b/lib/core/utils/app_utils.dart index 9859bc6..8b4f2b0 100644 --- a/lib/core/utils/app_utils.dart +++ b/lib/core/utils/app_utils.dart @@ -1,10 +1,12 @@ // Flutter imports: import 'package:flutter/material.dart'; -import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; // Package imports: import 'package:url_launcher/url_launcher.dart'; +// Project imports: +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; + class AppUtils { /// Abre uma URL no navegador externo. /// Retorna false se não conseguir abrir. diff --git a/lib/presentation/widgets/organisms/projects_section.dart b/lib/presentation/widgets/organisms/projects_section.dart index 5992dd3..9d4492d 100644 --- a/lib/presentation/widgets/organisms/projects_section.dart +++ b/lib/presentation/widgets/organisms/projects_section.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_animate/flutter_animate.dart'; -import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: +import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; import '../../../data/models/project_model.dart'; import '../molecules/project_card.dart'; From 56864d4debdb77cc743f5116db9fe594e2df130f Mon Sep 17 00:00:00 2001 From: Franklyn Roberto Date: Wed, 10 Dec 2025 23:45:08 -0300 Subject: [PATCH 12/12] Refactor widget build methods and constructor parameters for consistency - Updated constructor parameter order in CustomTextField, MagneticElement, SocialButton, TechAutocompleteField, TechChip, TypewriterText, CertificateForm, ExperienceForm, ProjectForm, SkillForm, CertificateCard, ExperienceCard, ProjectCard, CertificatesSection, ExperienceSection, GlassHeader, HeroSection, IntroOverlay, ProjectsSection, SkillsSection, and widget tests to place `super.key` at the end. - Changed the build method signature to include `final` keyword for parameters in multiple widgets for improved readability. - Replaced string literals with single quotes in various places for consistency. - Adjusted some numeric values for clarity and consistency. --- analysis_options.yaml | 50 +++++++++++-- lib/core/theme/app_theme.dart | 10 +-- lib/core/utils/app_logger.dart | 16 ++--- lib/core/utils/app_utils.dart | 6 +- lib/data/mocks/mock_data.dart | 8 +-- lib/data/models/certificate_model.dart | 11 +-- lib/data/models/experience_model.dart | 8 +-- lib/data/models/project_model.dart | 2 +- lib/data/models/skill_model.dart | 8 +-- .../repositories/portfolio_repository.dart | 10 +-- .../repositories/supabase_repository.dart | 32 ++++----- lib/main.dart | 26 +++---- .../controllers/auth_controller.dart | 4 +- .../controllers/portfolio_controller.dart | 14 ++-- .../pages/admin/admin_dashboard_page.dart | 72 +++++++++---------- lib/presentation/pages/admin/login_page.dart | 8 +-- lib/presentation/pages/home_page.dart | 40 +++++------ .../widgets/atoms/background_pattern.dart | 16 ++--- .../widgets/atoms/custom_text_field.dart | 9 +-- .../widgets/atoms/magnetic_element.dart | 10 +-- .../widgets/atoms/social_button.dart | 10 ++- .../atoms/tech_autocomplete_field.dart | 43 ++++++----- lib/presentation/widgets/atoms/tech_chip.dart | 4 +- .../widgets/atoms/typewriter_text.dart | 11 ++- .../widgets/forms/certificate_form.dart | 14 ++-- .../widgets/forms/experience_form.dart | 26 +++---- .../widgets/forms/project_form.dart | 20 +++--- .../widgets/forms/skill_form.dart | 20 +++--- .../widgets/molecules/certificate_card.dart | 18 ++--- .../widgets/molecules/experience_card.dart | 8 +-- .../widgets/molecules/project_card.dart | 16 ++--- .../organisms/certificates_section.dart | 16 ++--- .../widgets/organisms/experience_section.dart | 12 ++-- .../widgets/organisms/glass_header.dart | 22 +++--- .../widgets/organisms/hero_section.dart | 26 +++---- .../widgets/organisms/intro_overlay.dart | 42 ++++++----- .../widgets/organisms/projects_section.dart | 22 +++--- .../widgets/organisms/skills_section.dart | 30 ++++---- test/widget_test.dart | 4 +- 39 files changed, 370 insertions(+), 354 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 7e63d64..0b1d9dd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,10 +1,48 @@ include: package:flutter_lints/flutter.yaml +code_style: + line-length: 120 + linter: rules: - prefer_const_constructors: true - prefer_final_locals: true - always_declare_return_types: true - annotate_overrides: true - avoid_print: true # Use logger ou debugPrint - + avoid_print: true # Evita o uso de print, que pode ser útil para depuração, mas deve ser removido em produção. + prefer_single_quotes: true # Usa aspas simples em strings para manter a consistência. + prefer_const_constructors: true # Encoraja o uso de construtores constantes quando possível para otimização. + prefer_final_fields: true # Use final para campos que não são reatribuídos, garantindo imutabilidade. + prefer_is_not_empty: true # Incentiva o uso de isNotEmpty em vez de !isEmpty para clareza. + always_use_package_imports: true # Garante que todos os imports sejam feitos com package: para evitar conflitos de nome. + avoid_empty_else: true # Evita blocos else vazios que podem confundir a leitura do código. + avoid_unnecessary_containers: true # Evita o uso desnecessário de Container quando outro widget pode ser mais apropriado. + avoid_redundant_argument_values: false # Evita passar valores padrão redundantes para parâmetros. + prefer_const_literals_to_create_immutables: true # Incentiva o uso de literais const para listas e mapas imutáveis. + prefer_final_locals: true # Use final para variáveis locais que não são reatribuídas, melhorando a legibilidade. + file_names: true # Garante que os nomes de arquivos sigam as convenções de nomenclatura. + unnecessary_new: true # Remove a palavra-chave 'new', que é desnecessária nas versões mais recentes do Dart. + prefer_adjacent_string_concatenation: true # Incentiva a concatenação de strings adjacentes em uma única operação. + prefer_inlined_adds: true # Incentiva o uso de adição em linha em vez de métodos separados, quando possível. + unnecessary_this: true # Remove o uso desnecessário do 'this', que pode ser redundante em alguns contextos. + avoid_return_types_on_setters: true # Evita o uso de tipos de retorno em setters, que devem ser void. + prefer_final_parameters: true # Use final para parâmetros que não são reatribuídos, promovendo a imutabilidade. + omit_local_variable_types: true # Incentiva a omissão de tipos de variáveis locais para permitir a inferência de tipo. + always_declare_return_types: true # Sempre declare tipos de retorno em funções para melhor legibilidade. + prefer_typing_uninitialized_variables: true # Exige que variáveis não inicializadas sejam tipadas. + avoid_dynamic_calls: true # Desencoraja o uso de chamadas dinâmicas, que podem introduzir erros. + use_key_in_widget_constructors: true # Incentiva o uso de chaves nos construtores de widgets para otimização de desempenho. + prefer_const_constructors_in_immutables: true # Incentiva construtores constantes em classes imutáveis. + always_put_required_named_parameters_first: true # Garante que os parâmetros nomeados obrigatórios venham primeiro. + sort_pub_dependencies: true # Ordena as dependências no pubspec.yaml para melhor organização. + unnecessary_const: true # Remove a palavra-chave 'const' onde não é necessária para simplificação do código. + use_setters_to_change_properties: true # Incentiva o uso de setters para modificar propriedades de objetos. + type_annotate_public_apis: true # Exige anotação de tipo para APIs públicas para maior clareza. + prefer_asserts_with_message: true # Incentiva o uso de mensagens em assertivas para facilitar a depuração. + prefer_spread_collections: true # Incentiva o uso de spread em coleções para simplificar a sintaxe. + prefer_mixin: true # Incentiva o uso de mixins em vez de herança para reutilização de código. + avoid_void_async: true # Desencoraja o uso de funções assíncronas que retornam void. + prefer_collection_literals: true # Incentiva o uso de literais de coleção em vez de construtores de coleção. + prefer_is_empty: true # Usa isEmpty em vez de verificar se o comprimento é 0, promovendo a legibilidade. + avoid_escaping_inner_quotes: true # Desencoraja a fuga de aspas internas em strings, que pode tornar o código confuso. + no_leading_underscores_for_local_identifiers: false # Proíbe sublinhados iniciais para identificadores locais, melhorando a clareza. + prefer_null_aware_operators: true # Incentiva o uso de operadores nulos para simplificar verificações nulas. + avoid_positional_boolean_parameters: true # Desencoraja o uso de parâmetros booleanos posicionais, que podem ser confusos. + avoid_implementing_value_types: true # Evita a implementação de tipos de valor, que pode causar problemas de desempenho. + prefer_int_literals: true # Incentiva o uso de literais int para números inteiros para maior clareza. diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 58a8782..ae97441 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; // Project imports: -import '../constants/app_constants.dart'; +import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; class AppTheme { // Cores Light @@ -33,10 +33,10 @@ class AppTheme { ); static ThemeData _buildTheme({ - required Brightness brightness, - required Color background, - required Color surface, - required Color textColor, + required final Brightness brightness, + required final Color background, + required final Color surface, + required final Color textColor, }) { return ThemeData( useMaterial3: true, diff --git a/lib/core/utils/app_logger.dart b/lib/core/utils/app_logger.dart index 4bda5c2..eafa02b 100644 --- a/lib/core/utils/app_logger.dart +++ b/lib/core/utils/app_logger.dart @@ -5,10 +5,10 @@ final supabase = Supabase.instance.client; class AppLogger { static Future log({ - required String level, - required String message, - String? stack, - String? userId, + required final String level, + required final String message, + final String? stack, + final String? userId, }) async { try { // Se não passar userId, tenta pegar o usuário autenticado @@ -28,19 +28,19 @@ class AppLogger { } // Métodos auxiliares para facilitar o uso - static Future info(String message, {String? stack}) async { + static Future info(final String message, {final String? stack}) async { await log(level: 'info', message: message, stack: stack); } - static Future debug(String message, {String? stack}) async { + static Future debug(final String message, {final String? stack}) async { await log(level: 'debug', message: message, stack: stack); } - static Future error(String message, {String? stack}) async { + static Future error(final String message, {final String? stack}) async { await log(level: 'error', message: message, stack: stack); } - static Future warning(String message, {String? stack}) async { + static Future warning(final String message, {final String? stack}) async { await log(level: 'warning', message: message, stack: stack); } } diff --git a/lib/core/utils/app_utils.dart b/lib/core/utils/app_utils.dart index 8b4f2b0..0656117 100644 --- a/lib/core/utils/app_utils.dart +++ b/lib/core/utils/app_utils.dart @@ -10,8 +10,8 @@ import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; class AppUtils { /// Abre uma URL no navegador externo. /// Retorna false se não conseguir abrir. - static Future launchURL(String url, {BuildContext? context}) async { - final Uri uri = Uri.parse(url); + static Future launchURL(final String url, {final BuildContext? context}) async { + final uri = Uri.parse(url); try { if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) { throw Exception('Could not launch $url'); @@ -27,7 +27,7 @@ class AppUtils { SnackBar(content: Text('Não foi possível abrir o link: $e')), ); } - debugPrint("Erro ao abrir URL: $e"); + debugPrint('Erro ao abrir URL: $e'); } } } diff --git a/lib/data/mocks/mock_data.dart b/lib/data/mocks/mock_data.dart index 817fab5..df3d9ef 100644 --- a/lib/data/mocks/mock_data.dart +++ b/lib/data/mocks/mock_data.dart @@ -1,8 +1,8 @@ // Project imports: -import '../models/certificate_model.dart'; -import '../models/experience_model.dart'; -import '../models/project_model.dart'; -import '../models/skill_model.dart'; +import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; +import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; +import 'package:meu_curriculo_flutter/data/models/project_model.dart'; +import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; class MockData { // --- EXPERIÊNCIAS, PROJETOS E SKILLS ATUALIZADOS --- diff --git a/lib/data/models/certificate_model.dart b/lib/data/models/certificate_model.dart index 7ee6677..1258acd 100644 --- a/lib/data/models/certificate_model.dart +++ b/lib/data/models/certificate_model.dart @@ -9,17 +9,10 @@ class CertificateModel { final String date; // Novo const CertificateModel({ - this.id, - required this.title, - required this.description, - required this.credentialUrl, - required this.language, - required this.framework, - required this.issuer, - required this.date, + required this.title, required this.description, required this.credentialUrl, required this.language, required this.framework, required this.issuer, required this.date, this.id, }); - factory CertificateModel.fromMap(Map map) { + factory CertificateModel.fromMap(final Map map) { return CertificateModel( id: map['id'], title: map['title'] ?? '', diff --git a/lib/data/models/experience_model.dart b/lib/data/models/experience_model.dart index 02900df..cca2daa 100644 --- a/lib/data/models/experience_model.dart +++ b/lib/data/models/experience_model.dart @@ -7,15 +7,11 @@ class ExperienceModel { final bool isCurrent; // Para destacar se é o emprego atual const ExperienceModel({ - this.id, - required this.role, - required this.company, - required this.period, - required this.description, + required this.role, required this.company, required this.period, required this.description, this.id, this.isCurrent = false, }); - factory ExperienceModel.fromMap(Map map) { + factory ExperienceModel.fromMap(final Map map) { return ExperienceModel( id: map['id'], role: map['role'] ?? '', diff --git a/lib/data/models/project_model.dart b/lib/data/models/project_model.dart index bad9228..87571a5 100644 --- a/lib/data/models/project_model.dart +++ b/lib/data/models/project_model.dart @@ -17,7 +17,7 @@ class ProjectModel { this.id, }); - factory ProjectModel.fromMap(Map map) { + factory ProjectModel.fromMap(final Map map) { return ProjectModel( id: map['id'], title: map['title'] ?? '', diff --git a/lib/data/models/skill_model.dart b/lib/data/models/skill_model.dart index 4cfc317..830400c 100644 --- a/lib/data/models/skill_model.dart +++ b/lib/data/models/skill_model.dart @@ -8,21 +8,21 @@ class SkillModel { final SkillType type; // Nova propriedade const SkillModel({ - this.id, required this.name, + required this.type, + this.id, this.iconAsset, this.isHighlight = false, - required this.type, // Obrigatório agora }); - factory SkillModel.fromMap(Map map) { + factory SkillModel.fromMap(final Map map) { return SkillModel( id: map['id'], name: map['name'] ?? '', iconAsset: map['icon_asset'], isHighlight: map['is_highlight'] ?? false, type: SkillType.values.firstWhere( - (e) => e.name == map['type'], + (final e) => e.name == map['type'], orElse: () => SkillType.tools, ), ); diff --git a/lib/data/repositories/portfolio_repository.dart b/lib/data/repositories/portfolio_repository.dart index c55b7db..f82e18b 100644 --- a/lib/data/repositories/portfolio_repository.dart +++ b/lib/data/repositories/portfolio_repository.dart @@ -1,9 +1,9 @@ // Project imports: -import '../mocks/mock_data.dart'; -import '../models/certificate_model.dart'; -import '../models/experience_model.dart'; -import '../models/project_model.dart'; -import '../models/skill_model.dart'; +import 'package:meu_curriculo_flutter/data/mocks/mock_data.dart'; +import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; +import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; +import 'package:meu_curriculo_flutter/data/models/project_model.dart'; +import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; abstract class IPortfolioRepository { Future> getProjects(); diff --git a/lib/data/repositories/supabase_repository.dart b/lib/data/repositories/supabase_repository.dart index 5a58144..107dbaf 100644 --- a/lib/data/repositories/supabase_repository.dart +++ b/lib/data/repositories/supabase_repository.dart @@ -5,18 +5,18 @@ import 'dart:developer'; import 'package:supabase_flutter/supabase_flutter.dart'; // Project imports: -import '../../core/utils/app_logger.dart'; -import '../models/certificate_model.dart'; -import '../models/experience_model.dart'; -import '../models/project_model.dart'; -import '../models/skill_model.dart'; -import 'portfolio_repository.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; +import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; +import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; +import 'package:meu_curriculo_flutter/data/models/project_model.dart'; +import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; +import 'package:meu_curriculo_flutter/data/repositories/portfolio_repository.dart'; class SupabaseRepository implements IPortfolioRepository { final SupabaseClient _client = Supabase.instance.client; // --- AUTH --- - Future signIn(String email, String password) async { + Future signIn(final String email, final String password) async { try { final response = await _client.auth.signInWithPassword( email: email, @@ -43,21 +43,21 @@ class SupabaseRepository implements IPortfolioRepository { // --- CRUD GENÉRICO (Para evitar repetição) --- // Create - Future createItem(String table, Map data) async { + Future createItem(final String table, final Map data) async { await _client.from(table).insert(data); } // Update Future updateItem( - String table, - int id, - Map data, + final String table, + final int id, + final Map data, ) async { await _client.from(table).update(data).eq('id', id); } // Delete - Future deleteItem(String table, int id) async { + Future deleteItem(final String table, final int id) async { await _client.from(table).delete().eq('id', id); } @@ -71,7 +71,7 @@ class SupabaseRepository implements IPortfolioRepository { return (response as List) .map( - (e) => ProjectModel.fromMap(e), + (final e) => ProjectModel.fromMap(e), ) // Certifique-se que ProjectModel tem o método fromMap .toList(); } catch (e, stack) { @@ -96,7 +96,7 @@ class SupabaseRepository implements IPortfolioRepository { return (response as List) .map( - (e) => ExperienceModel.fromMap(e), + (final e) => ExperienceModel.fromMap(e), ) // Certifique-se que ExperienceModel tem fromMap .toList(); } catch (e, stack) { @@ -120,7 +120,7 @@ class SupabaseRepository implements IPortfolioRepository { return (response as List) .map( - (e) => SkillModel.fromMap(e), + (final e) => SkillModel.fromMap(e), ) // Certifique-se que SkillModel tem fromMap .toList(); } catch (e, stack) { @@ -143,7 +143,7 @@ class SupabaseRepository implements IPortfolioRepository { .order('created_at', ascending: false); return (response as List) - .map((e) => CertificateModel.fromMap(e)) + .map((final e) => CertificateModel.fromMap(e)) .toList(); } catch (e, stack) { await AppLogger.log( diff --git a/lib/main.dart b/lib/main.dart index d91feb7..5add00b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,19 +8,19 @@ import 'package:provider/provider.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; // Project imports: +import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; +import 'package:meu_curriculo_flutter/core/theme/app_theme.dart'; +import 'package:meu_curriculo_flutter/data/repositories/portfolio_repository.dart'; +import 'package:meu_curriculo_flutter/data/repositories/supabase_repository.dart'; import 'package:meu_curriculo_flutter/l10n/arb/app_localizations.dart'; -import 'core/constants/app_constants.dart'; -import 'core/theme/app_theme.dart'; -import 'data/repositories/portfolio_repository.dart'; -import 'data/repositories/supabase_repository.dart'; -import 'presentation/controllers/auth_controller.dart'; -import 'presentation/controllers/portfolio_controller.dart'; -import 'presentation/pages/home_page.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/auth_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/pages/home_page.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - await dotenv.load(fileName: ".env"); + await dotenv.load(fileName: '.env'); await Supabase.initialize( url: AppConstants.supabaseUrl, @@ -34,7 +34,7 @@ class MeuCurriculoApp extends StatelessWidget { const MeuCurriculoApp({super.key}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return MultiProvider( providers: [ // 1. Injeta o Repositório (SupabaseRepository) @@ -42,13 +42,13 @@ class MeuCurriculoApp extends StatelessWidget { // 2. Injeta o PortfolioController (Usa o repositório acima) ChangeNotifierProvider( - create: (context) => + create: (final context) => PortfolioController(context.read()), ), // 3. Injeta o AuthController (NOVO! Faltava isso) ChangeNotifierProvider( - create: (context) { + create: (final context) { // Pegamos o repositório injetado e convertemos para SupabaseRepository // pois o AuthController precisa dos métodos de login (que não estão na interface genérica) final repo = @@ -58,9 +58,9 @@ class MeuCurriculoApp extends StatelessWidget { ), ], child: Consumer( - builder: (context, controller, child) { + builder: (final context, final controller, final child) { return MaterialApp( - onGenerateTitle: (context) => + onGenerateTitle: (final context) => AppLocalizations.of(context)!.appTitle, debugShowCheckedModeBanner: false, localizationsDelegates: const [ diff --git a/lib/presentation/controllers/auth_controller.dart b/lib/presentation/controllers/auth_controller.dart index fd4c41a..22184a9 100644 --- a/lib/presentation/controllers/auth_controller.dart +++ b/lib/presentation/controllers/auth_controller.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; // Project imports: -import '../../data/repositories/supabase_repository.dart'; +import 'package:meu_curriculo_flutter/data/repositories/supabase_repository.dart'; class AuthController extends ChangeNotifier { final SupabaseRepository repository; @@ -22,7 +22,7 @@ class AuthController extends ChangeNotifier { } } - Future login(String email, String pass) async { + Future login(final String email, final String pass) async { final success = await repository.signIn(email, pass); if (success) notifyListeners(); return success; diff --git a/lib/presentation/controllers/portfolio_controller.dart b/lib/presentation/controllers/portfolio_controller.dart index 8e0ecf0..113c4f2 100644 --- a/lib/presentation/controllers/portfolio_controller.dart +++ b/lib/presentation/controllers/portfolio_controller.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; // Project imports: -import '../../core/utils/app_logger.dart'; -import '../../data/models/certificate_model.dart'; -import '../../data/models/experience_model.dart'; -import '../../data/models/project_model.dart'; -import '../../data/models/skill_model.dart'; -import '../../data/repositories/portfolio_repository.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; +import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; +import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; +import 'package:meu_curriculo_flutter/data/models/project_model.dart'; +import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; +import 'package:meu_curriculo_flutter/data/repositories/portfolio_repository.dart'; class PortfolioController extends ChangeNotifier { final IPortfolioRepository repository; @@ -41,7 +41,7 @@ class PortfolioController extends ChangeNotifier { final projectsKey = GlobalKey(); final certificatesKey = GlobalKey(); - Future scrollToSection(GlobalKey key) async { + Future scrollToSection(final GlobalKey key) async { final context = key.currentContext; if (context != null) { await Scrollable.ensureVisible( diff --git a/lib/presentation/pages/admin/admin_dashboard_page.dart b/lib/presentation/pages/admin/admin_dashboard_page.dart index 9b49ffd..5429766 100644 --- a/lib/presentation/pages/admin/admin_dashboard_page.dart +++ b/lib/presentation/pages/admin/admin_dashboard_page.dart @@ -5,17 +5,17 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; import 'package:meu_curriculo_flutter/data/models/project_model.dart'; import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; -import '../../../core/utils/app_logger.dart'; -import '../../controllers/auth_controller.dart'; -import '../../controllers/portfolio_controller.dart'; -import '../../widgets/forms/certificate_form.dart'; -import '../../widgets/forms/experience_form.dart'; -import '../../widgets/forms/project_form.dart'; -import '../../widgets/forms/skill_form.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/auth_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/forms/certificate_form.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/forms/experience_form.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/forms/project_form.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/forms/skill_form.dart'; class AdminDashboardPage extends StatefulWidget { const AdminDashboardPage({super.key}); @@ -40,12 +40,12 @@ class _AdminDashboardPageState extends State super.dispose(); } - void _deleteItem(String table, int? id) async { + Future _deleteItem(final String table, final int? id) async { if (id == null) return; final confirm = await showDialog( context: context, - builder: (ctx) => AlertDialog( + builder: (final ctx) => AlertDialog( title: const Text('Confirmar Exclusão'), content: const Text('Tem certeza que deseja remover este item?'), actions: [ @@ -88,7 +88,7 @@ class _AdminDashboardPageState extends State } } - void _showAddDialog(BuildContext context, int index) { + void _showAddDialog(final BuildContext context, final int index) { Widget? dialog; switch (index) { @@ -112,13 +112,13 @@ class _AdminDashboardPageState extends State } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final controller = context.watch(); final authController = context.read(); return Scaffold( appBar: AppBar( - title: const Text("Painel Administrativo"), + title: const Text('Painel Administrativo'), centerTitle: true, elevation: 2, actions: [ @@ -140,10 +140,10 @@ class _AdminDashboardPageState extends State controller: _tabController, indicatorWeight: 3, tabs: const [ - Tab(icon: Icon(Icons.work_outline), text: "Projetos"), - Tab(icon: Icon(Icons.business), text: "Experiência"), - Tab(icon: Icon(Icons.code), text: "Skills"), - Tab(icon: Icon(Icons.school_outlined), text: "Certificados"), + Tab(icon: Icon(Icons.work_outline), text: 'Projetos'), + Tab(icon: Icon(Icons.business), text: 'Experiência'), + Tab(icon: Icon(Icons.code), text: 'Skills'), + Tab(icon: Icon(Icons.school_outlined), text: 'Certificados'), ], ), ), @@ -158,19 +158,19 @@ class _AdminDashboardPageState extends State ), floatingActionButton: FloatingActionButton.extended( onPressed: () => _showAddDialog(context, _tabController.index), - label: const Text("Adicionar Novo"), + label: const Text('Adicionar Novo'), icon: const Icon(Icons.add), ), ); } - Widget _buildProjectList(List items) { - if (items.isEmpty) return _buildEmptyState("Nenhum projeto encontrado."); + Widget _buildProjectList(final List items) { + if (items.isEmpty) return _buildEmptyState('Nenhum projeto encontrado.'); return ListView.separated( padding: const EdgeInsets.all(16), itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 12), - itemBuilder: (context, index) { + itemBuilder: (final context, final index) { final item = items[index]; return Card( elevation: 2, @@ -201,7 +201,7 @@ class _AdminDashboardPageState extends State spacing: 4, children: item.techStack .map( - (t) => Chip( + (final t) => Chip( label: Text(t, style: const TextStyle(fontSize: 10)), padding: EdgeInsets.zero, materialTapTargetSize: @@ -234,15 +234,15 @@ class _AdminDashboardPageState extends State ); } - Widget _buildExperienceList(List items) { + Widget _buildExperienceList(final List items) { if (items.isEmpty) { - return _buildEmptyState("Nenhuma experiência encontrada."); + return _buildEmptyState('Nenhuma experiência encontrada.'); } return ListView.separated( padding: const EdgeInsets.all(16), itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 12), - itemBuilder: (context, index) { + itemBuilder: (final context, final index) { final item = items[index]; return Card( elevation: 2, @@ -262,7 +262,7 @@ class _AdminDashboardPageState extends State subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("${item.company} • ${item.period}"), + Text('${item.company} • ${item.period}'), if (item.isCurrent) Container( margin: const EdgeInsets.only(top: 4), @@ -275,7 +275,7 @@ class _AdminDashboardPageState extends State borderRadius: BorderRadius.circular(4), ), child: Text( - "Atual", + 'Atual', style: TextStyle( fontSize: 10, color: Colors.green.shade800, @@ -306,13 +306,13 @@ class _AdminDashboardPageState extends State ); } - Widget _buildSkillList(List items) { - if (items.isEmpty) return _buildEmptyState("Nenhuma skill encontrada."); + Widget _buildSkillList(final List items) { + if (items.isEmpty) return _buildEmptyState('Nenhuma skill encontrada.'); return ListView.separated( padding: const EdgeInsets.all(16), itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 12), - itemBuilder: (context, index) { + itemBuilder: (final context, final index) { final item = items[index]; return Card( elevation: 2, @@ -329,7 +329,7 @@ class _AdminDashboardPageState extends State item.name, style: const TextStyle(fontWeight: FontWeight.bold), ), - subtitle: Text("Tipo: ${item.type.name.toUpperCase()}"), + subtitle: Text('Tipo: ${item.type.name.toUpperCase()}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -352,15 +352,15 @@ class _AdminDashboardPageState extends State ); } - Widget _buildCertificateList(List items) { + Widget _buildCertificateList(final List items) { if (items.isEmpty) { - return _buildEmptyState("Nenhum certificado encontrado."); + return _buildEmptyState('Nenhum certificado encontrado.'); } return ListView.separated( padding: const EdgeInsets.all(16), itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 12), - itemBuilder: (context, index) { + itemBuilder: (final context, final index) { final item = items[index]; return Card( elevation: 2, @@ -380,8 +380,8 @@ class _AdminDashboardPageState extends State subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Emissor: ${item.issuer}"), - Text("Data: ${item.date}"), + Text('Emissor: ${item.issuer}'), + Text('Data: ${item.date}'), ], ), trailing: Row( @@ -406,7 +406,7 @@ class _AdminDashboardPageState extends State ); } - Widget _buildEmptyState(String message) { + Widget _buildEmptyState(final String message) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/presentation/pages/admin/login_page.dart b/lib/presentation/pages/admin/login_page.dart index 31b9290..44a2602 100644 --- a/lib/presentation/pages/admin/login_page.dart +++ b/lib/presentation/pages/admin/login_page.dart @@ -21,7 +21,7 @@ class _LoginPageState extends State { bool _isLoading = false; bool _obscurePassword = true; - void _submit() async { + Future _submit() async { setState(() => _isLoading = true); final auth = context.read(); final success = await auth.login(_emailCtrl.text, _passCtrl.text); @@ -44,7 +44,7 @@ class _LoginPageState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Scaffold( body: Center( child: Container( @@ -54,7 +54,7 @@ class _LoginPageState extends State { mainAxisSize: MainAxisSize.min, children: [ const Text( - "Admin Access", + 'Admin Access', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 24), @@ -94,7 +94,7 @@ class _LoginPageState extends State { style: ElevatedButton.styleFrom( minimumSize: const Size(double.infinity, 50), ), - child: const Text("Entrar"), + child: const Text('Entrar'), ), ], ), diff --git a/lib/presentation/pages/home_page.dart b/lib/presentation/pages/home_page.dart index 453e2de..40cd785 100644 --- a/lib/presentation/pages/home_page.dart +++ b/lib/presentation/pages/home_page.dart @@ -9,21 +9,21 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: +import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; import 'package:meu_curriculo_flutter/l10n/arb/app_localizations.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/auth_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; import 'package:meu_curriculo_flutter/presentation/pages/admin/admin_dashboard_page.dart'; import 'package:meu_curriculo_flutter/presentation/pages/admin/login_page.dart'; -import '../../../core/constants/app_constants.dart'; -import '../controllers/auth_controller.dart'; -import '../controllers/portfolio_controller.dart'; -import '../widgets/organisms/certificates_section.dart'; -import '../widgets/organisms/experience_section.dart'; -import '../widgets/organisms/glass_header.dart'; -import '../widgets/organisms/hero_section.dart'; -import '../widgets/organisms/projects_section.dart'; -import '../widgets/organisms/skills_section.dart'; - -import '../widgets/atoms/background_pattern.dart'; // Importe a textura -import '../widgets/organisms/intro_overlay.dart'; // Importe o IntroOverlay +import 'package:meu_curriculo_flutter/presentation/widgets/organisms/certificates_section.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/organisms/experience_section.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/organisms/glass_header.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/organisms/hero_section.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/organisms/projects_section.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/organisms/skills_section.dart'; + +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/background_pattern.dart'; // Importe a textura +import 'package:meu_curriculo_flutter/presentation/widgets/organisms/intro_overlay.dart'; // Importe o IntroOverlay class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -71,13 +71,13 @@ class _HomePageState extends State { // Navega para o Login Navigator.push( context, - MaterialPageRoute(builder: (context) => const LoginPage()), + MaterialPageRoute(builder: (final context) => const LoginPage()), ); } } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final controller = context.watch(); return Scaffold( @@ -162,17 +162,17 @@ class _HomePageState extends State { .translucent, // Garante que o toque funcione bem child: Padding( padding: const EdgeInsets.all( - 8.0, + 8, ), // Aumenta a área de toque child: Column( children: [ const Text( - "Feito com Flutter 3.27 & 💙", + 'Feito com Flutter 3.27 & 💙', style: TextStyle(color: Colors.grey), ), const SizedBox(height: 8), Text( - "© ${DateTime.now().year} Franklyn Roberto", + '© ${DateTime.now().year} Franklyn Roberto', style: const TextStyle( color: Colors.grey, fontSize: 12, @@ -211,7 +211,7 @@ class _HomePageState extends State { ); } - Widget _buildFloatingActionButtons(BuildContext context) { + Widget _buildFloatingActionButtons(final BuildContext context) { final authController = context.watch(); final isLoggedIn = authController.isLogged; @@ -227,7 +227,7 @@ class _HomePageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => const AdminDashboardPage(), + builder: (final context) => const AdminDashboardPage(), ), ); }, @@ -241,7 +241,7 @@ class _HomePageState extends State { FloatingActionButton.extended( heroTag: 'download_fab', onPressed: () => launchUrl(Uri.parse(AppAssets.cvPtBr)), - label: const Text("Baixar CV"), + label: const Text('Baixar CV'), icon: const Icon(Icons.download_rounded), backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, diff --git a/lib/presentation/widgets/atoms/background_pattern.dart b/lib/presentation/widgets/atoms/background_pattern.dart index d985324..0d192b7 100644 --- a/lib/presentation/widgets/atoms/background_pattern.dart +++ b/lib/presentation/widgets/atoms/background_pattern.dart @@ -10,7 +10,7 @@ import 'package:flutter_animate/flutter_animate.dart'; class BackgroundPattern extends StatefulWidget { final Widget child; - const BackgroundPattern({super.key, required this.child}); + const BackgroundPattern({required this.child, super.key}); @override State createState() => _BackgroundPatternState(); @@ -26,11 +26,11 @@ class _BackgroundPatternState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return MouseRegion( - onHover: (details) { + onHover: (final details) { _mousePos.value = details.position; }, child: Stack( @@ -38,7 +38,7 @@ class _BackgroundPatternState extends State { // Animated Blobs com Parallax ValueListenableBuilder( valueListenable: _mousePos, - builder: (context, mouse, _) { + builder: (final context, final mouse, _) { // Fator de movimento (quanto menor, mais sutil) final moveX = (mouse.dx / MediaQuery.of(context).size.width) * 50; final moveY = @@ -66,7 +66,7 @@ class _BackgroundPatternState extends State { ), ) .animate( - onPlay: (controller) => + onPlay: (final controller) => controller.repeat(reverse: true), ) .scale( @@ -96,7 +96,7 @@ class _BackgroundPatternState extends State { ), ) .animate( - onPlay: (controller) => + onPlay: (final controller) => controller.repeat(reverse: true), ) .scale( @@ -136,7 +136,7 @@ class DotGridPainter extends CustomPainter { DotGridPainter({required this.color, required this.spacing}); @override - void paint(Canvas canvas, Size size) { + void paint(final Canvas canvas, final Size size) { final paint = Paint() ..color = color ..strokeWidth = 2 @@ -150,5 +150,5 @@ class DotGridPainter extends CustomPainter { } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; + bool shouldRepaint(covariant final CustomPainter oldDelegate) => false; } diff --git a/lib/presentation/widgets/atoms/custom_text_field.dart b/lib/presentation/widgets/atoms/custom_text_field.dart index 37548ae..0b95444 100644 --- a/lib/presentation/widgets/atoms/custom_text_field.dart +++ b/lib/presentation/widgets/atoms/custom_text_field.dart @@ -11,10 +11,7 @@ class CustomTextField extends StatelessWidget { final TextInputType? keyboardType; const CustomTextField({ - super.key, - required this.controller, - required this.label, - required this.icon, + required this.controller, required this.label, required this.icon, super.key, this.hint, this.maxLines = 1, this.required = false, @@ -22,7 +19,7 @@ class CustomTextField extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return TextFormField( controller: controller, maxLines: maxLines, @@ -36,7 +33,7 @@ class CustomTextField extends StatelessWidget { fillColor: Colors.grey.shade50, ), validator: required - ? (v) => v?.isEmpty == true ? 'Campo obrigatório' : null + ? (final v) => v?.isEmpty == true ? 'Campo obrigatório' : null : null, ); } diff --git a/lib/presentation/widgets/atoms/magnetic_element.dart b/lib/presentation/widgets/atoms/magnetic_element.dart index 04eb82d..eceff3d 100644 --- a/lib/presentation/widgets/atoms/magnetic_element.dart +++ b/lib/presentation/widgets/atoms/magnetic_element.dart @@ -8,7 +8,7 @@ class MagneticElement extends StatefulWidget { final Widget child; final double strength; // Força da repulsão - const MagneticElement({super.key, required this.child, this.strength = 1.0}); + const MagneticElement({required this.child, super.key, this.strength = 1.0}); @override State createState() => _MagneticElementState(); @@ -36,7 +36,7 @@ class _MagneticElementState extends State super.dispose(); } - void _updatePosition(PointerEvent details, Size size) { + void _updatePosition(final PointerEvent details, final Size size) { final center = Offset(size.width / 2, size.height / 2); final mousePos = details.localPosition; final distance = (mousePos - center).distance; @@ -59,17 +59,17 @@ class _MagneticElementState extends State } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { // Interpolação suave (Lerp) para o movimento não ser travado _currentPosition = Offset.lerp(_currentPosition, _targetPosition, 0.1) ?? Offset.zero; return MouseRegion( - onHover: (details) => _updatePosition(details, context.size ?? Size.zero), + onHover: (final details) => _updatePosition(details, context.size ?? Size.zero), onExit: (_) => setState(() => _targetPosition = Offset.zero), child: AnimatedBuilder( animation: _controller, - builder: (context, child) { + builder: (final context, final child) { // Adiciona um movimento de "respiração" (flutuar) aleatório final floatY = math.sin(_controller.value * 2 * math.pi) * 5; diff --git a/lib/presentation/widgets/atoms/social_button.dart b/lib/presentation/widgets/atoms/social_button.dart index a12f174..b824eae 100644 --- a/lib/presentation/widgets/atoms/social_button.dart +++ b/lib/presentation/widgets/atoms/social_button.dart @@ -12,21 +12,19 @@ class SocialButton extends StatelessWidget { final Color? color; const SocialButton({ - super.key, - required this.icon, - required this.url, + required this.icon, required this.url, super.key, this.color, }); Future _launchUrl() async { - final Uri uri = Uri.parse(url); + final uri = Uri.parse(url); if (!await launchUrl(uri)) { debugPrint('Could not launch $url'); } } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return IconButton( onPressed: _launchUrl, icon: FaIcon(icon, size: 28), @@ -37,7 +35,7 @@ class SocialButton extends StatelessWidget { ).colorScheme.primary.withValues(alpha: 0.1), ), ) - .animate(onPlay: (controller) => controller.repeat(reverse: true)) + .animate(onPlay: (final controller) => controller.repeat(reverse: true)) .scaleXY( end: 1.1, duration: 1.seconds, diff --git a/lib/presentation/widgets/atoms/tech_autocomplete_field.dart b/lib/presentation/widgets/atoms/tech_autocomplete_field.dart index f4183d8..7b716bb 100644 --- a/lib/presentation/widgets/atoms/tech_autocomplete_field.dart +++ b/lib/presentation/widgets/atoms/tech_autocomplete_field.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; // Project imports: -import '../../../core/constants/tech_suggestions.dart'; +import 'package:meu_curriculo_flutter/core/constants/tech_suggestions.dart'; class TechAutocompleteField extends StatelessWidget { final TextEditingController controller; @@ -14,10 +14,7 @@ class TechAutocompleteField extends StatelessWidget { final List? excludeItems; const TechAutocompleteField({ - super.key, - required this.controller, - required this.label, - required this.icon, + required this.controller, required this.label, required this.icon, super.key, this.required = false, this.onSelected, this.onFieldSubmitted, @@ -25,16 +22,16 @@ class TechAutocompleteField extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return LayoutBuilder( - builder: (context, constraints) { + builder: (final context, final constraints) { return Autocomplete( initialValue: TextEditingValue(text: controller.text), - optionsBuilder: (TextEditingValue textEditingValue) { + optionsBuilder: (final TextEditingValue textEditingValue) { if (textEditingValue.text == '') { return const Iterable.empty(); } - return kTechSuggestions.where((String option) { + return kTechSuggestions.where((final String option) { final matches = option.toLowerCase().contains( textEditingValue.text.toLowerCase(), ); @@ -42,16 +39,16 @@ class TechAutocompleteField extends StatelessWidget { return matches && !isExcluded; }); }, - onSelected: (String selection) { + onSelected: (final String selection) { controller.text = selection; onSelected?.call(selection); }, fieldViewBuilder: ( - BuildContext context, - TextEditingController fieldTextEditingController, - FocusNode fieldFocusNode, - VoidCallback onFieldSubmitted, + final BuildContext context, + final TextEditingController fieldTextEditingController, + final FocusNode fieldFocusNode, + final VoidCallback onFieldSubmitted, ) { // Sync external controller with internal one if needed // But here we want the external controller to be the source of truth eventually. @@ -89,12 +86,12 @@ class TechAutocompleteField extends StatelessWidget { : null, ), validator: required - ? (v) => v?.isEmpty == true ? 'Campo obrigatório' : null + ? (final v) => v?.isEmpty == true ? 'Campo obrigatório' : null : null, - onChanged: (val) { + onChanged: (final val) { controller.text = val; }, - onFieldSubmitted: (val) { + onFieldSubmitted: (final val) { onFieldSubmitted(); // Call Autocomplete's onFieldSubmitted to close overlay this.onFieldSubmitted?.call(val); }, @@ -102,22 +99,22 @@ class TechAutocompleteField extends StatelessWidget { }, optionsViewBuilder: ( - BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options, + final BuildContext context, + final AutocompleteOnSelected onSelected, + final Iterable options, ) { return Align( alignment: Alignment.topLeft, child: Material( - elevation: 4.0, + elevation: 4, child: SizedBox( width: constraints.maxWidth, child: ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, itemCount: options.length, - itemBuilder: (BuildContext context, int index) { - final String option = options.elementAt(index); + itemBuilder: (final BuildContext context, final int index) { + final option = options.elementAt(index); return ListTile( title: Text(option), onTap: () { diff --git a/lib/presentation/widgets/atoms/tech_chip.dart b/lib/presentation/widgets/atoms/tech_chip.dart index f0937a5..dd060f7 100644 --- a/lib/presentation/widgets/atoms/tech_chip.dart +++ b/lib/presentation/widgets/atoms/tech_chip.dart @@ -5,10 +5,10 @@ class TechChip extends StatelessWidget { final String label; final bool isHighlight; - const TechChip({super.key, required this.label, this.isHighlight = false}); + const TechChip({required this.label, super.key, this.isHighlight = false}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final theme = Theme.of(context); return Container( diff --git a/lib/presentation/widgets/atoms/typewriter_text.dart b/lib/presentation/widgets/atoms/typewriter_text.dart index 3e88e97..c34db6d 100644 --- a/lib/presentation/widgets/atoms/typewriter_text.dart +++ b/lib/presentation/widgets/atoms/typewriter_text.dart @@ -12,8 +12,7 @@ class TypewriterText extends StatefulWidget { final Duration holdDelay; const TypewriterText({ - super.key, - required this.texts, + required this.texts, super.key, this.style, this.typingSpeed = const Duration(milliseconds: 100), this.deletingSpeed = const Duration(milliseconds: 50), @@ -25,7 +24,7 @@ class TypewriterText extends StatefulWidget { } class _TypewriterTextState extends State { - String _displayedText = ""; + String _displayedText = ''; int _currentIndex = 0; int _charIndex = 0; bool _isDeleting = false; @@ -46,7 +45,7 @@ class _TypewriterTextState extends State { void _startTyping() { _timer = Timer.periodic( _isDeleting ? widget.deletingSpeed : widget.typingSpeed, - (timer) { + (final timer) { if (!mounted) return; final currentFullText = widget.texts[_currentIndex]; @@ -80,9 +79,9 @@ class _TypewriterTextState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Text( - "$_displayedText|", + '$_displayedText|', style: widget.style, textAlign: TextAlign.center, ); diff --git a/lib/presentation/widgets/forms/certificate_form.dart b/lib/presentation/widgets/forms/certificate_form.dart index 1be7824..c2537b7 100644 --- a/lib/presentation/widgets/forms/certificate_form.dart +++ b/lib/presentation/widgets/forms/certificate_form.dart @@ -5,12 +5,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: -import '../../../core/utils/app_logger.dart'; -import '../../../data/models/certificate_model.dart'; -import '../../controllers/auth_controller.dart'; -import '../../controllers/portfolio_controller.dart'; -import '../atoms/custom_text_field.dart'; -import '../atoms/tech_autocomplete_field.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; +import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/auth_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/custom_text_field.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/tech_autocomplete_field.dart'; class CertificateForm extends StatefulWidget { final CertificateModel? certificate; @@ -116,7 +116,7 @@ class _CertificateFormState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( diff --git a/lib/presentation/widgets/forms/experience_form.dart b/lib/presentation/widgets/forms/experience_form.dart index 2213605..e282f04 100644 --- a/lib/presentation/widgets/forms/experience_form.dart +++ b/lib/presentation/widgets/forms/experience_form.dart @@ -5,10 +5,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: -import '../../../core/utils/app_logger.dart'; -import '../../../data/models/experience_model.dart'; -import '../../controllers/auth_controller.dart'; -import '../../controllers/portfolio_controller.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; +import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/auth_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; class ExperienceForm extends StatefulWidget { final ExperienceModel? experience; @@ -103,7 +103,7 @@ class _ExperienceFormState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( @@ -180,7 +180,7 @@ class _ExperienceFormState extends State { title: const Text('Trabalho Atual?'), secondary: const Icon(Icons.check_circle_outline), value: _isCurrent, - onChanged: (val) => setState(() => _isCurrent = val), + onChanged: (final val) => setState(() => _isCurrent = val), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.grey.shade300), @@ -229,12 +229,12 @@ class _ExperienceFormState extends State { } Widget _buildTextField({ - required TextEditingController controller, - required String label, - required IconData icon, - String? hint, - int maxLines = 1, - bool required = false, + required final TextEditingController controller, + required final String label, + required final IconData icon, + final String? hint, + final int maxLines = 1, + final bool required = false, }) { return TextFormField( controller: controller, @@ -248,7 +248,7 @@ class _ExperienceFormState extends State { fillColor: Colors.grey.shade50, ), validator: required - ? (v) => v?.isEmpty == true ? 'Campo obrigatório' : null + ? (final v) => v?.isEmpty == true ? 'Campo obrigatório' : null : null, ); } diff --git a/lib/presentation/widgets/forms/project_form.dart b/lib/presentation/widgets/forms/project_form.dart index 911c279..e1541ea 100644 --- a/lib/presentation/widgets/forms/project_form.dart +++ b/lib/presentation/widgets/forms/project_form.dart @@ -5,12 +5,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: -import '../../../core/utils/app_logger.dart'; -import '../../../data/models/project_model.dart'; -import '../../controllers/auth_controller.dart'; -import '../../controllers/portfolio_controller.dart'; -import '../atoms/custom_text_field.dart'; -import '../atoms/tech_autocomplete_field.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; +import 'package:meu_curriculo_flutter/data/models/project_model.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/auth_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/custom_text_field.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/tech_autocomplete_field.dart'; class ProjectForm extends StatefulWidget { final ProjectModel? project; @@ -107,7 +107,7 @@ class _ProjectFormState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( @@ -167,7 +167,7 @@ class _ProjectFormState extends State { Wrap( spacing: 8, runSpacing: 8, - children: _selectedTechs.map((tech) { + children: _selectedTechs.map((final tech) { return Chip( label: Text(tech), onDeleted: () { @@ -184,13 +184,13 @@ class _ProjectFormState extends State { label: 'Adicionar Tecnologia', icon: Icons.code, excludeItems: _selectedTechs, - onSelected: (val) { + onSelected: (final val) { setState(() { _selectedTechs.add(val); _techInputCtrl.clear(); }); }, - onFieldSubmitted: (val) { + onFieldSubmitted: (final val) { if (val.isNotEmpty) { setState(() { _selectedTechs.add(val); diff --git a/lib/presentation/widgets/forms/skill_form.dart b/lib/presentation/widgets/forms/skill_form.dart index dd3fad5..f45979f 100644 --- a/lib/presentation/widgets/forms/skill_form.dart +++ b/lib/presentation/widgets/forms/skill_form.dart @@ -5,12 +5,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // Project imports: -import '../../../core/utils/app_logger.dart'; -import '../../../data/models/skill_model.dart'; -import '../../controllers/auth_controller.dart'; -import '../../controllers/portfolio_controller.dart'; -import '../atoms/custom_text_field.dart'; -import '../atoms/tech_autocomplete_field.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_logger.dart'; +import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/auth_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/custom_text_field.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/tech_autocomplete_field.dart'; class SkillForm extends StatefulWidget { final SkillModel? skill; @@ -96,7 +96,7 @@ class _SkillFormState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( @@ -153,13 +153,13 @@ class _SkillFormState extends State { filled: true, fillColor: Colors.grey.shade50, ), - items: SkillType.values.map((t) { + items: SkillType.values.map((final t) { return DropdownMenuItem( value: t, child: Text(t.name.toUpperCase()), ); }).toList(), - onChanged: (val) { + onChanged: (final val) { if (val != null) setState(() => _type = val); }, ), @@ -168,7 +168,7 @@ class _SkillFormState extends State { title: const Text('Destaque?'), secondary: const Icon(Icons.star), value: _isHighlight, - onChanged: (val) => setState(() => _isHighlight = val), + onChanged: (final val) => setState(() => _isHighlight = val), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.grey.shade300), diff --git a/lib/presentation/widgets/molecules/certificate_card.dart b/lib/presentation/widgets/molecules/certificate_card.dart index 4d3f176..bba8292 100644 --- a/lib/presentation/widgets/molecules/certificate_card.dart +++ b/lib/presentation/widgets/molecules/certificate_card.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; // Project imports: -import '../../../core/utils/app_utils.dart'; -import '../../../data/models/certificate_model.dart'; -import '../atoms/tech_chip.dart'; +import 'package:meu_curriculo_flutter/core/utils/app_utils.dart'; +import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/tech_chip.dart'; class CertificateCard extends StatefulWidget { final CertificateModel certificate; - const CertificateCard({super.key, required this.certificate}); + const CertificateCard({required this.certificate, super.key}); @override State createState() => _CertificateCardState(); @@ -27,7 +27,7 @@ class _CertificateCardState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; @@ -37,7 +37,7 @@ class _CertificateCardState extends State { _isHovered.value = false; _mousePos.value = Offset.zero; }, - onHover: (details) { + onHover: (final details) { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; final center = Offset(size.width / 2, size.height / 2); @@ -45,7 +45,7 @@ class _CertificateCardState extends State { }, child: AnimatedBuilder( animation: Listenable.merge([_mousePos, _isHovered]), - builder: (context, child) { + builder: (final context, final child) { final hovered = _isHovered.value; final mouse = _mousePos.value; @@ -115,7 +115,7 @@ class _CertificateCardState extends State { Icons.open_in_new_rounded, size: 20, ), - tooltip: "Ver Certificado", + tooltip: 'Ver Certificado', style: IconButton.styleFrom( foregroundColor: theme.colorScheme.primary, ), @@ -153,7 +153,7 @@ class _CertificateCardState extends State { horizontal: 6, ), child: Text( - "•", + '•', style: theme.textTheme.bodySmall?.copyWith( color: Colors.grey, ), diff --git a/lib/presentation/widgets/molecules/experience_card.dart b/lib/presentation/widgets/molecules/experience_card.dart index 896eba5..e544286 100644 --- a/lib/presentation/widgets/molecules/experience_card.dart +++ b/lib/presentation/widgets/molecules/experience_card.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; // Project imports: -import '../../../data/models/experience_model.dart'; +import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; class ExperienceCard extends StatefulWidget { final ExperienceModel experience; - const ExperienceCard({super.key, required this.experience}); + const ExperienceCard({required this.experience, super.key}); @override State createState() => _ExperienceCardState(); @@ -17,7 +17,7 @@ class _ExperienceCardState extends State { bool _isHovered = false; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; @@ -29,7 +29,7 @@ class _ExperienceCardState extends State { curve: Curves.easeOut, margin: const EdgeInsets.only(bottom: 24, left: 16), padding: const EdgeInsets.all(24), - transform: Matrix4.translationValues(0.0, _isHovered ? -4.0 : 0.0, 0.0), + transform: Matrix4.translationValues(0, _isHovered ? -4.0 : 0.0, 0), decoration: BoxDecoration( color: theme.cardTheme.color ?? diff --git a/lib/presentation/widgets/molecules/project_card.dart b/lib/presentation/widgets/molecules/project_card.dart index 267db01..1fa94e5 100644 --- a/lib/presentation/widgets/molecules/project_card.dart +++ b/lib/presentation/widgets/molecules/project_card.dart @@ -6,14 +6,14 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: -import '../../../core/constants/app_constants.dart'; -import '../../../data/models/project_model.dart'; -import '../atoms/tech_chip.dart'; +import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; +import 'package:meu_curriculo_flutter/data/models/project_model.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/tech_chip.dart'; class ProjectCard extends StatefulWidget { final ProjectModel project; - const ProjectCard({super.key, required this.project}); + const ProjectCard({required this.project, super.key}); @override State createState() => _ProjectCardState(); @@ -32,7 +32,7 @@ class _ProjectCardState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; @@ -42,7 +42,7 @@ class _ProjectCardState extends State { _isHovered.value = false; _mousePos.value = Offset.zero; }, - onHover: (details) { + onHover: (final details) { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; final center = Offset(size.width / 2, size.height / 2); @@ -50,7 +50,7 @@ class _ProjectCardState extends State { }, child: AnimatedBuilder( animation: Listenable.merge([_mousePos, _isHovered]), - builder: (context, child) { + builder: (final context, final child) { final hovered = _isHovered.value; final mouse = _mousePos.value; @@ -138,7 +138,7 @@ class _ProjectCardState extends State { runSpacing: 6, children: widget.project.techStack .take(3) - .map((t) => TechChip(label: t)) + .map((final t) => TechChip(label: t)) .toList(), ), const SizedBox(height: 12), diff --git a/lib/presentation/widgets/organisms/certificates_section.dart b/lib/presentation/widgets/organisms/certificates_section.dart index 265360e..e3c9eb4 100644 --- a/lib/presentation/widgets/organisms/certificates_section.dart +++ b/lib/presentation/widgets/organisms/certificates_section.dart @@ -2,21 +2,21 @@ import 'package:flutter/material.dart'; // Project imports: -import '../../../data/models/certificate_model.dart'; -import '../molecules/certificate_card.dart'; +import 'package:meu_curriculo_flutter/data/models/certificate_model.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/molecules/certificate_card.dart'; class CertificatesSection extends StatelessWidget { final List certificates; - const CertificatesSection({super.key, required this.certificates}); + const CertificatesSection({required this.certificates, super.key}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "CERTIFICADOS", + 'CERTIFICADOS', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.2, @@ -25,8 +25,8 @@ class CertificatesSection extends StatelessWidget { ), const SizedBox(height: 32), LayoutBuilder( - builder: (context, constraints) { - int crossAxisCount = 1; + builder: (final context, final constraints) { + var crossAxisCount = 1; if (constraints.maxWidth > 1100) { crossAxisCount = 3; } else if (constraints.maxWidth > 700) { @@ -45,7 +45,7 @@ class CertificatesSection extends StatelessWidget { 280, // Aumentado de 200 para 280 para evitar overflow ), itemCount: certificates.length, - itemBuilder: (context, index) { + itemBuilder: (final context, final index) { return CertificateCard(certificate: certificates[index]); }, ); diff --git a/lib/presentation/widgets/organisms/experience_section.dart b/lib/presentation/widgets/organisms/experience_section.dart index 9c17964..9d54eb8 100644 --- a/lib/presentation/widgets/organisms/experience_section.dart +++ b/lib/presentation/widgets/organisms/experience_section.dart @@ -5,21 +5,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; // Project imports: -import '../../../data/models/experience_model.dart'; -import '../molecules/experience_card.dart'; +import 'package:meu_curriculo_flutter/data/models/experience_model.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/molecules/experience_card.dart'; class ExperienceSection extends StatelessWidget { final List experiences; - const ExperienceSection({super.key, required this.experiences}); + const ExperienceSection({required this.experiences, super.key}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "EXPERIÊNCIA PROFISSIONAL", + 'EXPERIÊNCIA PROFISSIONAL', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.2, @@ -54,7 +54,7 @@ class ExperienceSection extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: experiences.length, - itemBuilder: (context, index) { + itemBuilder: (final context, final index) { return Padding( padding: const EdgeInsets.only(left: 24, bottom: 32), child: Stack( diff --git a/lib/presentation/widgets/organisms/glass_header.dart b/lib/presentation/widgets/organisms/glass_header.dart index 5858f52..56a208e 100644 --- a/lib/presentation/widgets/organisms/glass_header.dart +++ b/lib/presentation/widgets/organisms/glass_header.dart @@ -9,13 +9,13 @@ import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; // Project imports: -import '../../controllers/portfolio_controller.dart'; +import 'package:meu_curriculo_flutter/presentation/controllers/portfolio_controller.dart'; class GlassHeader extends StatelessWidget { const GlassHeader({super.key}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final controller = context.read(); final isDesktop = MediaQuery.of(context).size.width > 700; final isDark = Theme.of(context).brightness == Brightness.dark; @@ -53,7 +53,7 @@ class GlassHeader extends StatelessWidget { onTap: () => controller.scrollToSection(controller.heroKey), child: Text( - "", + '', style: TextStyle( fontFamily: 'Code', fontWeight: FontWeight.w900, @@ -67,28 +67,28 @@ class GlassHeader extends StatelessWidget { Row( children: [ _HeaderItem( - title: "Skills", + title: 'Skills', onTap: () => controller.scrollToSection( controller.skillsKey, ), ), const SizedBox(width: 20), _HeaderItem( - title: "Experiência", + title: 'Experiência', onTap: () => controller.scrollToSection( controller.experienceKey, ), ), const SizedBox(width: 20), _HeaderItem( - title: "Projetos", + title: 'Projetos', onTap: () => controller.scrollToSection( controller.projectsKey, ), ), const SizedBox(width: 20), _HeaderItem( - title: "Certificados", + title: 'Certificados', onTap: () => controller.scrollToSection( controller.certificatesKey, ), @@ -102,7 +102,7 @@ class GlassHeader extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), onPressed: controller.toggleTheme, - tooltip: "Alternar Tema", + tooltip: 'Alternar Tema', ).animate().rotate(duration: 500.ms), ], ) @@ -116,7 +116,7 @@ class GlassHeader extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), - onSelected: (value) { + onSelected: (final value) { if (value == 'theme') { controller.toggleTheme(); } else if (value == 'skills') { @@ -135,7 +135,7 @@ class GlassHeader extends StatelessWidget { ); } }, - itemBuilder: (BuildContext context) => [ + itemBuilder: (final BuildContext context) => [ const PopupMenuItem( value: 'skills', child: Text('Skills'), @@ -199,7 +199,7 @@ class _HeaderItemState extends State<_HeaderItem> { bool isHovered = false; @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return MouseRegion( diff --git a/lib/presentation/widgets/organisms/hero_section.dart b/lib/presentation/widgets/organisms/hero_section.dart index e5337ce..8ab6f65 100644 --- a/lib/presentation/widgets/organisms/hero_section.dart +++ b/lib/presentation/widgets/organisms/hero_section.dart @@ -6,16 +6,16 @@ import 'package:flutter_animate/flutter_animate.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // Project imports: -import '../../../core/constants/app_constants.dart'; -import '../atoms/magnetic_element.dart'; -import '../atoms/social_button.dart'; -import '../atoms/typewriter_text.dart'; +import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/magnetic_element.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/social_button.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/typewriter_text.dart'; class HeroSection extends StatelessWidget { const HeroSection({super.key}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final size = MediaQuery.of(context).size; final isMobile = size.width < 800; @@ -68,7 +68,7 @@ class HeroSection extends StatelessWidget { top: 60, right: 120, child: MagneticElement( - strength: 2.0, + strength: 2, child: FaIcon( FontAwesomeIcons.react, size: 50, @@ -156,7 +156,7 @@ class HeroSection extends StatelessWidget { bottom: 130, right: 280, child: MagneticElement( - strength: 1.0, + strength: 1, child: FaIcon( FontAwesomeIcons.gitAlt, size: 35, @@ -228,7 +228,7 @@ class HeroSection extends StatelessWidget { bottom: 220, right: 60, child: MagneticElement( - strength: 1.0, + strength: 1, child: FaIcon( FontAwesomeIcons.linux, size: 34, @@ -308,7 +308,7 @@ class HeroSection extends StatelessWidget { ).withValues(alpha: 0.3), ) .animate( - onPlay: (controller) => + onPlay: (final controller) => controller.repeat(period: 5.seconds), ) .tint( @@ -324,10 +324,10 @@ class HeroSection extends StatelessWidget { // Typewriter Effect for Role TypewriterText( texts: const [ - "MOBILE DEVELOPER (FLUTTER)", - "FULLSTACK ENGINEER", - "CREATIVE CODER", - "TECH ENTHUSIAST", + 'MOBILE DEVELOPER (FLUTTER)', + 'FULLSTACK ENGINEER', + 'CREATIVE CODER', + 'TECH ENTHUSIAST', ], style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, diff --git a/lib/presentation/widgets/organisms/intro_overlay.dart b/lib/presentation/widgets/organisms/intro_overlay.dart index 809785f..7362fa6 100644 --- a/lib/presentation/widgets/organisms/intro_overlay.dart +++ b/lib/presentation/widgets/organisms/intro_overlay.dart @@ -13,9 +13,7 @@ class IntroOverlay extends StatefulWidget { final VoidCallback onFinished; const IntroOverlay({ - super.key, - required this.isLoading, - required this.onFinished, + required this.isLoading, required this.onFinished, super.key, }); @override @@ -25,13 +23,13 @@ class IntroOverlay extends StatefulWidget { class _IntroOverlayState extends State { final List _logs = []; final List _allLogs = [ - "Carregando módulos do sistema...", - "Conectando ao servidor neural...", - "Otimizando shaders...", - "Compilando experiência...", - "Carregando portfólio...", - "Verificando integridade...", - "Acesso autorizado.", + 'Carregando módulos do sistema...', + 'Conectando ao servidor neural...', + 'Otimizando shaders...', + 'Compilando experiência...', + 'Carregando portfólio...', + 'Verificando integridade...', + 'Acesso autorizado.', ]; Timer? _logTimer; @@ -45,7 +43,7 @@ class _IntroOverlayState extends State { } @override - void didUpdateWidget(covariant IntroOverlay oldWidget) { + void didUpdateWidget(covariant final IntroOverlay oldWidget) { super.didUpdateWidget(oldWidget); // Se terminou de carregar e não estamos saindo ainda, iniciar saída if (oldWidget.isLoading && !widget.isLoading && !_isExiting) { @@ -60,8 +58,8 @@ class _IntroOverlayState extends State { } void _startLogs() { - int index = 0; - _logTimer = Timer.periodic(const Duration(milliseconds: 400), (timer) { + var index = 0; + _logTimer = Timer.periodic(const Duration(milliseconds: 400), (final timer) { if (index < _allLogs.length) { setState(() { _logs.add(_allLogs[index]); @@ -79,7 +77,7 @@ class _IntroOverlayState extends State { }); } - void _startExitSequence() async { + Future _startExitSequence() async { if (_isExiting) return; // Aguarda um pouco para mostrar "Acesso Autorizado" ou similar se quiser @@ -101,7 +99,7 @@ class _IntroOverlayState extends State { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { final size = MediaQuery.of(context).size; final primaryColor = Theme.of(context).colorScheme.primary; @@ -141,7 +139,7 @@ class _IntroOverlayState extends State { size: 60, color: primaryColor, ) - .animate(onPlay: (c) => c.repeat()) + .animate(onPlay: (final c) => c.repeat()) .shimmer(duration: 2.seconds, color: Colors.white) .scale( begin: const Offset(1, 1), @@ -157,7 +155,7 @@ class _IntroOverlayState extends State { ), const SizedBox(height: 20), Text( - "SYSTEM INITIALIZATION", + 'SYSTEM INITIALIZATION', style: TextStyle( fontFamily: 'Code', color: primaryColor, @@ -217,7 +215,7 @@ class _IntroOverlayState extends State { ) else Text( - "ACCESS GRANTED", + 'ACCESS GRANTED', style: TextStyle( color: Colors.greenAccent, fontSize: 20, @@ -241,13 +239,13 @@ class _IntroOverlayState extends State { SizedBox( height: 120, child: Column( - children: _logs.map((log) { + children: _logs.map((final log) { return Padding( padding: const EdgeInsets.symmetric( vertical: 2, ), child: Text( - "> $log", + '> $log', style: TextStyle( fontFamily: 'Courier', color: primaryColor.withValues( @@ -292,7 +290,7 @@ class GridPainter extends CustomPainter { GridPainter({required this.color}); @override - void paint(Canvas canvas, Size size) { + void paint(final Canvas canvas, final Size size) { final paint = Paint() ..color = color ..strokeWidth = 1; @@ -309,5 +307,5 @@ class GridPainter extends CustomPainter { } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; + bool shouldRepaint(covariant final CustomPainter oldDelegate) => false; } diff --git a/lib/presentation/widgets/organisms/projects_section.dart b/lib/presentation/widgets/organisms/projects_section.dart index 9d4492d..a983d35 100644 --- a/lib/presentation/widgets/organisms/projects_section.dart +++ b/lib/presentation/widgets/organisms/projects_section.dart @@ -7,16 +7,16 @@ import 'package:url_launcher/url_launcher.dart'; // Project imports: import 'package:meu_curriculo_flutter/core/constants/app_constants.dart'; -import '../../../data/models/project_model.dart'; -import '../molecules/project_card.dart'; +import 'package:meu_curriculo_flutter/data/models/project_model.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/molecules/project_card.dart'; class ProjectsSection extends StatelessWidget { final List projects; - const ProjectsSection({super.key, required this.projects}); + const ProjectsSection({required this.projects, super.key}); - void _launchGitHub() async { - final Uri url = Uri.parse(AppStrings.gitHubRepositoriesUrl); + Future _launchGitHub() async { + final url = Uri.parse(AppStrings.gitHubRepositoriesUrl); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { throw 'Could not launch $url'; @@ -24,7 +24,7 @@ class ProjectsSection extends StatelessWidget { } @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -32,7 +32,7 @@ class ProjectsSection extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "PROJETOS EM DESTAQUE", + 'PROJETOS EM DESTAQUE', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.2, @@ -42,16 +42,16 @@ class ProjectsSection extends StatelessWidget { TextButton.icon( onPressed: _launchGitHub, icon: const Icon(Icons.arrow_forward), - label: const Text("Ver todos no GitHub"), + label: const Text('Ver todos no GitHub'), ), ], ), const SizedBox(height: 24), LayoutBuilder( - builder: (context, constraints) { + builder: (final context, final constraints) { // Lógica responsiva para o Grid - int crossAxisCount = 1; + var crossAxisCount = 1; if (constraints.maxWidth > 1100) { crossAxisCount = 3; } else if (constraints.maxWidth > 700) { @@ -68,7 +68,7 @@ class ProjectsSection extends StatelessWidget { childAspectRatio: 1.3, ), itemCount: projects.length, - itemBuilder: (context, index) { + itemBuilder: (final context, final index) { return ProjectCard(project: projects[index]) .animate() .fadeIn(delay: (index * 100).ms) diff --git a/lib/presentation/widgets/organisms/skills_section.dart b/lib/presentation/widgets/organisms/skills_section.dart index 8bf8c45..c1c4b33 100644 --- a/lib/presentation/widgets/organisms/skills_section.dart +++ b/lib/presentation/widgets/organisms/skills_section.dart @@ -5,40 +5,40 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; // Project imports: -import '../../../data/models/skill_model.dart'; -import '../atoms/tech_chip.dart'; +import 'package:meu_curriculo_flutter/data/models/skill_model.dart'; +import 'package:meu_curriculo_flutter/presentation/widgets/atoms/tech_chip.dart'; class SkillsSection extends StatelessWidget { final List skills; - const SkillsSection({super.key, required this.skills}); + const SkillsSection({required this.skills, super.key}); @override - Widget build(BuildContext context) { + Widget build(final BuildContext context) { // Filtra as listas final mobileSkills = skills - .where((s) => s.type == SkillType.mobile) + .where((final s) => s.type == SkillType.mobile) .toList(); - final webSkills = skills.where((s) => s.type == SkillType.web).toList(); - final toolsSkills = skills.where((s) => s.type == SkillType.tools).toList(); + final webSkills = skills.where((final s) => s.type == SkillType.web).toList(); + final toolsSkills = skills.where((final s) => s.type == SkillType.tools).toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildCategory(context, "📱 MOBILE (CORE)", mobileSkills, 0), + _buildCategory(context, '📱 MOBILE (CORE)', mobileSkills, 0), const SizedBox(height: 40), - _buildCategory(context, "💻 WEB, BACKEND & DATA", webSkills, 200), + _buildCategory(context, '💻 WEB, BACKEND & DATA', webSkills, 200), const SizedBox(height: 40), - _buildCategory(context, "⚙️ TOOLS & DEVOPS", toolsSkills, 400), + _buildCategory(context, '⚙️ TOOLS & DEVOPS', toolsSkills, 400), ], ); } Widget _buildCategory( - BuildContext context, - String title, - List items, - int delayMs, + final BuildContext context, + final String title, + final List items, + final int delayMs, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -57,7 +57,7 @@ class SkillsSection extends StatelessWidget { Wrap( spacing: 12, runSpacing: 12, - children: items.map((skill) { + children: items.map((final skill) { return TechChip(label: skill.name, isHighlight: skill.isHighlight); }).toList(), ).animate(delay: delayMs.ms).fadeIn(duration: 500.ms), diff --git a/test/widget_test.dart b/test/widget_test.dart index a9c4c93..10bff5d 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -48,7 +48,7 @@ void main() { ); } - testWidgets('HomePage loads data on startup', (WidgetTester tester) async { + testWidgets('HomePage loads data on startup', (final WidgetTester tester) async { // Set a large screen size to avoid overflows in desktop layout tester.view.physicalSize = const Size(1920, 1080); tester.view.devicePixelRatio = 1.0; @@ -70,7 +70,7 @@ void main() { }); testWidgets('HomePage shows error message when loading fails', ( - WidgetTester tester, + final WidgetTester tester, ) async { // Set a large screen size to avoid overflows in desktop layout tester.view.physicalSize = const Size(1920, 1080);