Initial commit - app-padrao-1.0

This commit is contained in:
Erik Silva
2025-12-19 23:29:24 -03:00
commit ec76d3d633
205 changed files with 13131 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:barber_app/core/theme/app_theme.dart';
import 'package:barber_app/features/services/data/models/service_model.dart';
import 'package:barber_app/features/services/data/repositories/service_repository.dart';
import 'package:barber_app/shared/widgets/custom_text_field.dart';
import 'package:barber_app/shared/widgets/loading_button.dart';
import 'package:intl/intl.dart';
class AddServicePage extends StatefulWidget {
final ServiceModel? service;
const AddServicePage({super.key, this.service});
@override
State<AddServicePage> createState() => _AddServicePageState();
}
class _AddServicePageState extends State<AddServicePage> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _priceController = TextEditingController(); // Valor numérico raw
final _displayPriceController = TextEditingController(); // Valor formatado R$
final _durationController = TextEditingController(text: '30');
final _repository = ServiceRepository();
bool _isLoading = false;
final _currencyFormat = NumberFormat.currency(locale: 'pt_BR', symbol: 'R\$');
bool get _isEditing => widget.service != null;
@override
void initState() {
super.initState();
if (_isEditing) {
_nameController.text = widget.service!.name;
_priceController.text = widget.service!.price.toStringAsFixed(2);
_displayPriceController.text = _currencyFormat.format(widget.service!.price);
_durationController.text = widget.service!.durationMinutes.toString();
}
}
void _formatPrice(String value) {
if (value.isEmpty) return;
// Remove tudo que não é número
String numbers = value.replaceAll(RegExp(r'[^0-9]'), '');
if (numbers.isEmpty) return;
double val = double.parse(numbers) / 100;
_priceController.text = val.toStringAsFixed(2);
_displayPriceController.text = _currencyFormat.format(val);
// Mantém cursor no final
_displayPriceController.selection = TextSelection.fromPosition(
TextPosition(offset: _displayPriceController.text.length),
);
}
Future<void> _saveService() async {
if (!_formKey.currentState!.validate()) return;
if (_priceController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Por favor, informe o preço')),
);
return;
}
setState(() => _isLoading = true);
try {
final price = double.parse(_priceController.text);
final duration = int.tryParse(_durationController.text) ?? 30;
if (_isEditing) {
final updatedService = widget.service!.copyWith(
name: _nameController.text.trim(),
price: price,
durationMinutes: duration,
);
await _repository.updateService(updatedService);
} else {
await _repository.createService(
name: _nameController.text.trim(),
price: price,
durationMinutes: duration,
);
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_isEditing
? 'Serviço atualizado com sucesso!'
: 'Serviço criado com sucesso!'
),
backgroundColor: AppColors.success,
),
);
Navigator.pop(context, true);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erro ao salvar: $e'),
backgroundColor: AppColors.error,
),
);
}
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isEditing ? 'Editar Serviço' : 'Novo Serviço'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CustomTextField(
label: 'Nome do Serviço',
controller: _nameController,
hint: 'Ex: Corte Degradê, Barba Terapia...',
validator: (v) => v?.isEmpty == true ? 'Informe o nome' : null,
),
const SizedBox(height: 16),
CustomTextField(
label: 'Preço',
controller: _displayPriceController,
hint: 'R\$ 0,00',
keyboardType: TextInputType.number,
onChanged: _formatPrice,
validator: (v) => v?.isEmpty == true ? 'Informe o preço' : null,
),
const SizedBox(height: 16),
CustomTextField(
label: 'Duração Média (minutos)',
controller: _durationController,
keyboardType: TextInputType.number,
hint: '30',
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
),
const SizedBox(height: 32),
LoadingButton(
text: 'Salvar Serviço',
onPressed: _saveService,
isLoading: _isLoading,
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'package:barber_app/core/theme/app_theme.dart';
import 'package:barber_app/features/services/data/models/service_model.dart';
import 'package:barber_app/features/services/data/repositories/service_repository.dart';
import 'package:barber_app/features/services/presentation/pages/add_service_page.dart';
import 'package:intl/intl.dart';
class ServicesPage extends StatefulWidget {
const ServicesPage({super.key});
@override
State<ServicesPage> createState() => _ServicesPageState();
}
class _ServicesPageState extends State<ServicesPage> {
final _repository = ServiceRepository();
List<ServiceModel> _services = [];
final _currencyFormat = NumberFormat.currency(locale: 'pt_BR', symbol: 'R\$');
@override
void initState() {
super.initState();
_loadServices();
}
void _loadServices() {
setState(() {
_services = _repository.getAllServices();
});
}
Future<void> _deleteService(String id) async {
await _repository.deleteService(id);
_loadServices();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Serviço removido com sucesso!')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Meus Serviços'), centerTitle: true),
body: _services.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.content_cut_outlined,
size: 64,
color: AppColors.textSecondary.withValues(alpha: 0.5),
),
const SizedBox(height: 16),
const Text(
'Nenhum serviço cadastrado',
style: TextStyle(
color: AppColors.textSecondary,
fontSize: 18,
),
),
const SizedBox(height: 8),
TextButton(
onPressed: () => _navigateToAddService(),
child: const Text('Cadastrar Primeiro Serviço'),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _services.length,
itemBuilder: (context, index) {
final service = _services[index];
return Dismissible(
key: Key(service.id),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
color: AppColors.error,
child: const Icon(Icons.delete, color: Colors.white),
),
confirmDismiss: (direction) async {
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Confirmar exclusão'),
content: Text(
'Deseja remover o serviço "${service.name}"?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Cancelar'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text(
'Excluir',
style: TextStyle(color: AppColors.error),
),
),
],
),
);
},
onDismissed: (direction) => _deleteService(service.id),
child: Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
contentPadding: const EdgeInsets.all(16),
leading: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(Icons.cut, color: AppColors.primaryColor),
),
title: Text(
service.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
subtitle: Text(
'Duração média: ${service.durationMinutes} min',
style: const TextStyle(color: AppColors.textSecondary),
),
trailing: Text(
_currencyFormat.format(service.price),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: AppColors.success,
),
),
onTap: () => _navigateToAddService(service: service),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _navigateToAddService(),
child: const Icon(Icons.add),
),
);
}
Future<void> _navigateToAddService({ServiceModel? service}) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddServicePage(service: service)),
);
if (result == true) {
_loadServices();
}
}
}