a version da app estavel e pronto para producao
This commit is contained in:
parent
f009368bf5
commit
1c9d43f19e
|
|
@ -4,6 +4,7 @@ import 'package:intl/intl.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
// Importações originais mantidas
|
||||
import 'package:kzeduca_app/models/transaction.dart';
|
||||
import 'package:kzeduca_app/widgets/transaction_calculator.dart';
|
||||
import 'package:kzeduca_app/services/i18n_service.dart';
|
||||
|
|
@ -96,6 +97,10 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
void _onAmountChanged(double newAmount) {
|
||||
setState(() {
|
||||
_currentAmount = newAmount;
|
||||
// UX: Garante que a calculadora esteja visível se o valor for alterado.
|
||||
if (newAmount > 0.0) {
|
||||
_showCalculator = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -133,18 +138,20 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
}
|
||||
|
||||
void _saveTransaction() async {
|
||||
// Usamos 'listen: false' para evitar rebuilds desnecessários no método.
|
||||
final i18n = Provider.of<I18nService>(context, listen: false);
|
||||
|
||||
if (_currentAmount <= 0) {
|
||||
_showSnackBar(i18n.t('error_enter_amount'), isError: true);
|
||||
_showSnackBar(i18n.t('error campo de entrada valor monetario'), isError: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_titleController.text.trim().isEmpty) {
|
||||
_showSnackBar(i18n.t('error_enter_title'), isError: true);
|
||||
_showSnackBar(i18n.t('erro campo entrada titulo '), isError: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// A classe Transaction deve vir do seu arquivo 'models/transaction.dart'
|
||||
final newTransaction = Transaction(
|
||||
title: _titleController.text.trim(),
|
||||
amount: _currentAmount,
|
||||
|
|
@ -159,10 +166,10 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
await txnList.addTransaction(newTransaction);
|
||||
final userStateService = Provider.of<UserStateService>(context, listen: false);
|
||||
await userStateService.recalculateBalanceFromHive();
|
||||
_showSnackBar(i18n.t('transaction_saved_success'));
|
||||
_showSnackBar(i18n.t('transaction salva com success'));
|
||||
Navigator.pop(context, true);
|
||||
} catch (e) {
|
||||
_showSnackBar('${i18n.t('transaction_save_error')}: $e', isError: true);
|
||||
_showSnackBar('${i18n.t('erro ao salvar transaction')}: $e', isError: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,9 +178,10 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
SnackBar(
|
||||
content: Text(
|
||||
message,
|
||||
style: GoogleFonts.montserrat(color: Colors.white),
|
||||
style: GoogleFonts.montserrat(color: Colors.white, fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: isError ? _kExpenseColor : _kIncomeColor,
|
||||
// UX: Usar a cor de destaque para sucesso e a cor de erro para falha
|
||||
backgroundColor: isError ? _kExpenseColor.withOpacity(0.9) : _kIncomeColor.withOpacity(0.9),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
|
|
@ -249,23 +257,25 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
// MELHORIA UX: Usar AnimatedSize para transição mais suave da Calculadora
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
opacity: _showCalculator ? 1.0 : 0.0,
|
||||
child: Visibility(
|
||||
visible: _showCalculator,
|
||||
child: Column(
|
||||
curve: Curves.easeOut,
|
||||
child: _showCalculator
|
||||
? Column(
|
||||
children: [
|
||||
TransactionCalculator(
|
||||
// A calculadora é responsável por chamar _onAmountChanged
|
||||
onAmountChanged: _onAmountChanged,
|
||||
onSave: _saveTransaction,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_buildSaveButton(i18n),
|
||||
],
|
||||
)
|
||||
: const SizedBox(height: 0), // Oculta a calculadora
|
||||
),
|
||||
),
|
||||
),
|
||||
// O botão Salvar aparece sempre, exceto se for integrado na calculadora
|
||||
_buildSaveButton(i18n),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -278,6 +288,13 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
}
|
||||
|
||||
Widget _buildSaveButton(I18nService i18n) {
|
||||
// Se a calculadora estiver visível, ela deve ter seu próprio botão de salvar
|
||||
// A implementação do seu código original a colocava fora do bloco da calculadora, mantive assim para consistência
|
||||
// No entanto, se o TransactionCalculator tiver um botão "Salvar" interno, este aqui pode ser oculto
|
||||
|
||||
// Se você não quiser o botão de salvar duplicado quando a calculadora estiver visível:
|
||||
// if (_showCalculator) return const SizedBox.shrink();
|
||||
|
||||
return ElevatedButton(
|
||||
onPressed: _saveTransaction,
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
|
@ -326,9 +343,13 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
onTap: () {
|
||||
setState(() {
|
||||
_selectedType = type;
|
||||
// Garante que a categoria inicial do novo tipo seja selecionada
|
||||
_selectedCategory = type == TransactionType.expense
|
||||
? _expenseCategoryNames.keys.first
|
||||
: _incomeCategoryNames.keys.first;
|
||||
// UX: Zera o valor ao mudar o tipo de transação
|
||||
_currentAmount = 0.0;
|
||||
_showCalculator = false;
|
||||
});
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
|
|
@ -371,6 +392,7 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
|
||||
Widget _buildAmountDisplay(I18nService i18n) {
|
||||
return GestureDetector(
|
||||
// UX: Clicar no valor alterna a visibilidade da calculadora
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_showCalculator = !_showCalculator;
|
||||
|
|
@ -421,11 +443,17 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
required IconData icon,
|
||||
int maxLines = 1,
|
||||
}) {
|
||||
// MELHORIA UX: Cores de texto, ícone e hint ajustadas para o tema escuro.
|
||||
const Color lightTextColor = Colors.white;
|
||||
final Color iconColor = lightTextColor.withOpacity(0.7);
|
||||
final Color hintColor = lightTextColor.withOpacity(0.5);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: const Color.fromARGB(255, 20, 19, 19).withOpacity(0.2)),
|
||||
// MELHORIA UX: Borda consistente com o restante da interface
|
||||
border: Border.all(color: Colors.white.withOpacity(0.2)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
|
|
@ -438,13 +466,16 @@ class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
|||
controller: controller,
|
||||
maxLines: maxLines,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
style: GoogleFonts.montserrat(color: const Color.fromARGB(255, 0, 0, 0), fontWeight: FontWeight.w500),
|
||||
// MELHORIA UX: Cor do texto digitado ajustada para branco
|
||||
style: GoogleFonts.montserrat(color: lightTextColor, fontWeight: FontWeight.w500),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
hintText: hint,
|
||||
prefixIcon: Icon(icon, color: const Color.fromARGB(255, 15, 14, 14).withOpacity(0.7)),
|
||||
labelStyle: GoogleFonts.montserrat(color: const Color.fromARGB(255, 0, 0, 0).withOpacity(0.7)),
|
||||
hintStyle: GoogleFonts.montserrat(color: const Color.fromARGB(255, 0, 0, 0).withOpacity(0.5)),
|
||||
// MELHORIA UX: Cor do ícone ajustada para o tema escuro
|
||||
prefixIcon: Icon(icon, color: iconColor),
|
||||
// MELHORIA UX: Cor do label e hint ajustada para o tema escuro
|
||||
labelStyle: GoogleFonts.montserrat(color: iconColor),
|
||||
hintStyle: GoogleFonts.montserrat(color: hintColor),
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,11 +8,14 @@ import 'package:provider/provider.dart';
|
|||
import 'package:video_player/video_player.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import '../models/app_models.dart';
|
||||
import '../services/i18n_service.dart';
|
||||
import '../services/toast_service.dart';
|
||||
import '../screens/pdf_viewer_screen.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
|
||||
class LessonDetailSheet extends StatefulWidget {
|
||||
final QuickLesson lesson;
|
||||
|
|
@ -302,9 +305,10 @@ class _LessonDetailSheetState extends State<LessonDetailSheet> with WidgetsBindi
|
|||
Future<void> _downloadFile(String url, String fileName) async {
|
||||
final i18n = Provider.of<I18nService>(context, listen: false);
|
||||
|
||||
// 1. Bloqueio de YouTube
|
||||
if (yt_iframe.YoutubePlayerController.convertUrlToId(url) != null) {
|
||||
ToastService.show(
|
||||
message: 'Não é possível fazer o download de vídeos do YouTube diretamente.',
|
||||
message: i18n.t('Não é possível fazer o download de vídeos do YouTube diretamente.'),
|
||||
type: ToastType.error,
|
||||
);
|
||||
return;
|
||||
|
|
@ -312,50 +316,82 @@ class _LessonDetailSheetState extends State<LessonDetailSheet> with WidgetsBindi
|
|||
|
||||
if (url.isEmpty) {
|
||||
ToastService.show(
|
||||
message: 'A URL do recurso não está disponível.',
|
||||
message: i18n.t('A URL do recurso não está disponível.'),
|
||||
type: ToastType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Theme.of(context).platform == TargetPlatform.android ||
|
||||
Theme.of(context).platform == TargetPlatform.iOS) {
|
||||
// 2. Lógica para Dispositivos (Android/iOS)
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
|
||||
// SOLUÇÃO: Pede permissão e obtém o caminho de salvamento
|
||||
final status = await Permission.storage.request();
|
||||
if (status.isGranted) {
|
||||
|
||||
String? externalStorageDir;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// Em Android modernos, usamos getExternalStorageDirectory()
|
||||
// ou getDownloadsDirectory() que é específico para downloads no Android SDK 29+.
|
||||
final dir = await getDownloadsDirectory();
|
||||
externalStorageDir = dir?.path;
|
||||
} else if (Platform.isIOS) {
|
||||
// Para iOS, usa o diretório de documentos do aplicativo
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
externalStorageDir = dir.path;
|
||||
}
|
||||
|
||||
|
||||
if (externalStorageDir != null) {
|
||||
try {
|
||||
await FlutterDownloader.enqueue(
|
||||
url: url,
|
||||
savedDir: '/storage/emulated/0/Download',
|
||||
savedDir: externalStorageDir, // <--- NOVO CAMINHO CORRETO
|
||||
fileName: fileName,
|
||||
showNotification: true,
|
||||
openFileFromNotification: true,
|
||||
// saveInPublicStorage: true, // Remova este se causar erro, nem sempre é necessário.
|
||||
);
|
||||
ToastService.show(message: 'Download iniciado');
|
||||
} else {
|
||||
ToastService.show(message: i18n.t('Download iniciado'));
|
||||
} catch (e) {
|
||||
ToastService.show(
|
||||
message: 'Permissão de armazenamento negada',
|
||||
message: i18n.t('Falha ao iniciar o download: $e'),
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ToastService.show(
|
||||
message: i18n.t('Não foi possível encontrar o diretório de download.'),
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ToastService.show(
|
||||
message: i18n.t('Permissão de armazenamento negada'),
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// (Lógica da Web / Desktop)
|
||||
try {
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
|
||||
ToastService.show(message: 'Download iniciado no navegador');
|
||||
ToastService.show(message: i18n.t('Download iniciado no navegador'));
|
||||
} else {
|
||||
ToastService.show(
|
||||
message: 'Não foi possível iniciar o download. Verifique a URL.',
|
||||
message: i18n.t('Não foi possível iniciar o download. Verifique a URL.'),
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ToastService.show(
|
||||
message: 'Ocorreu um erro ao iniciar o download: $e',
|
||||
message: i18n.t('Ocorreu um erro ao iniciar o download: $e'),
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Widgets de UI ---
|
||||
|
||||
Widget _buildLessonHeader(I18nService i18n) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue