gp_design 0.0.5
gp_design: ^0.0.5 copied to clipboard
Colección de componentes, temas, tokens visuales y utilidades responsivas para construir apps Flutter consistentes con el sistema de diseño GP.
example/lib/main.dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:gp_design/gp_design.dart';
import 'package:gp_design/network.dart';
void main() {
runApp(const DemoApp());
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) {
return Builder(
builder: (context) => MaterialApp(
title: 'GP Design Demo',
theme: GpTheme.light(context),
darkTheme: GpTheme.dark(context),
themeMode: ThemeMode.system,
home: const DemoHome(),
),
);
}
}
class DemoHome extends StatefulWidget {
const DemoHome({super.key});
@override
State<DemoHome> createState() => _DemoHomeState();
}
class _DemoHomeState extends State<DemoHome> {
final Dio _dio = Dio(BaseOptions(baseUrl: 'https://httpbin.org'))
..interceptors.addAll([
AuthInterceptor(() async => Future.value('demo-token')),
ErrorInterceptor(),
]);
final List<List<Object?>> _userRows = [
['u_001', 'Ana Perez', 'ana.perez@empresa.com', 'Activo'],
['u_002', 'Carlos Medina', 'carlos.medina@empresa.com', 'Pendiente'],
['u_003', 'Luisa Gomez', 'luisa.gomez@empresa.com', 'Suspendido'],
['u_004', 'Javier Rojas', 'javier.rojas@empresa.com', 'Activo'],
['u_005', 'Mariana Silva', 'mariana.silva@empresa.com', 'Activo'],
['u_006', 'Diego Torres', 'diego.torres@empresa.com', 'Pendiente'],
['u_007', 'Sara Ibarra', 'sara.ibarra@empresa.com', 'Activo'],
['u_008', 'Ramon Ruiz', 'ramon.ruiz@empresa.com', 'Suspendido'],
['u_009', 'Camila Nunez', 'camila.nunez@empresa.com', 'Activo'],
['u_010', 'Pablo Reyes', 'pablo.reyes@empresa.com', 'Pendiente'],
['u_011', 'Daniela Soto', 'daniela.soto@empresa.com', 'Activo'],
['u_012', 'Leo Vargas', 'leo.vargas@empresa.com', 'Suspendido'],
['u_013', 'Marta Rios', 'marta.rios@empresa.com', 'Activo'],
['u_014', 'Nicolas Paredes', 'nicolas.paredes@empresa.com', 'Pendiente'],
['u_015', 'Olivia Diaz', 'olivia.diaz@empresa.com', 'Activo'],
['u_016', 'Renato Castro', 'renato.castro@empresa.com', 'Suspendido'],
['u_017', 'Paula Flores', 'paula.flores@empresa.com', 'Activo'],
['u_018', 'Sergio Mora', 'sergio.mora@empresa.com', 'Pendiente'],
['u_019', 'Tamara Cruz', 'tamara.cruz@empresa.com', 'Activo'],
['u_020', 'Victor Salas', 'victor.salas@empresa.com', 'Suspendido'],
['u_021', 'Wendy Silva', 'wendy.silva@empresa.com', 'Activo'],
['u_022', 'Ximena Ramos', 'ximena.ramos@empresa.com', 'Pendiente'],
['u_023', 'Yago Ortiz', 'yago.ortiz@empresa.com', 'Activo'],
['u_024', 'Zoe Marin', 'zoe.marin@empresa.com', 'Suspendido'],
];
int _tablePage = 1;
final int _pageSize = 5;
bool _loading = false;
String? _responseMessage;
final GlobalKey _pinKey = GlobalKey();
Future<void> _simulateRequest() async {
setState(() {
_loading = true;
_responseMessage = null;
});
try {
await _dio.get('/status/500');
if (!mounted) return;
setState(() => _responseMessage = 'Petición simulada con token Bearer.');
} on DioException catch (error) {
final customError = error.error;
if (!mounted) return;
setState(() {
_responseMessage = customError is GpException
? customError.message
: (error.message ?? 'Error desconocido');
});
} finally {
if (mounted) {
setState(() => _loading = false);
}
}
}
List<List<Object?>> _currentPageRows() {
final start = (_tablePage - 1) * _pageSize;
final end = (start + _pageSize).clamp(0, _userRows.length);
return _userRows.sublist(start, end);
}
Widget _statusBadge(String status) {
final colors = Theme.of(context).colorScheme;
Color background;
Color textColor;
switch (status) {
case 'Activo':
background = colors.primary.withValues(alpha: 0.12);
textColor = colors.primary;
break;
case 'Pendiente':
background = colors.tertiary.withValues(alpha: 0.15);
textColor = colors.tertiary;
break;
case 'Suspendido':
default:
background = colors.error.withValues(alpha: 0.12);
textColor = colors.error;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(999),
),
child: GpText(
text: status,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.w600,
),
selectable: false,
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: const GpAppBar(title: 'GP Design Demo'),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
GpText(
text: 'Componentes principales',
style: theme.textTheme.titleLarge,
),
const SizedBox(height: 12),
GpButton(
text: 'Mostrar diálogo',
onPressed: () => showDialog<void>(
context: context,
builder: (_) => Dialog(
child: GpAlertDialog(
title: '¡Hola!',
message: 'Este es el diálogo de GP listo para usarse.',
onPressed: () {},
),
),
),
),
const SizedBox(height: 24),
GpRow(
gutter: 16,
children: const [
GpCol(
span: 4,
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: GpText(text: 'Span 4/12'),
),
),
),
GpCol(
span: 4,
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: GpText(text: 'Span 4/12'),
),
),
),
GpCol(
span: 4,
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: GpText(text: 'Span 4/12'),
),
),
),
],
),
const SizedBox(height: 16),
GpListTile(
title: 'ListTile GP',
subtitle: 'Ideal para settings o listados',
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {},
backgroundColor: theme.colorScheme.surface,
),
const SizedBox(height: 24),
GpButton(
text: _loading ? 'Enviando...' : 'Simular petición',
loading: _loading,
onPressed: _loading ? null : _simulateRequest,
),
const SizedBox(height: 24),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GpText(text: 'Ejemplo: PIN input dinámico'),
const SizedBox(height: 12),
GpPinInput(
key: _pinKey,
length: 4,
obscure: false,
validator: (pin) => pin == '1234' ? null : 'PIN incorrecto',
onCompleted: (pin) async {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('PIN verificado correctamente'),
),
);
},
),
const SizedBox(height: 12),
Row(
children: [
GpButton(
text: 'Forzar fallo',
onPressed: () {
(_pinKey.currentState as dynamic)?.reportFailure();
},
),
const SizedBox(width: 8),
GpButton(
text: 'Limpiar',
onPressed: () {
(_pinKey.currentState as dynamic)?.clear();
},
),
],
),
],
),
),
),
const SizedBox(height: 24),
GpResponsiveTable.simple(
title: 'Usuarios (paginado)',
headers: ['ID', 'Nombre', 'Correo', 'Estado'],
headerTypes: [
GpTableHeaderType.numeric,
GpTableHeaderType.text,
GpTableHeaderType.text,
GpTableHeaderType.status,
],
borderRadius: 8,
data: _currentPageRows()
.map(
(row) => [
row[0],
row[1],
row[2],
_statusBadge(row[3] as String),
],
)
.toList(),
enablePagination: true,
pageSize: _pageSize,
totalItems: _userRows.length,
currentPage: _tablePage,
onPageChange: (page) => setState(() => _tablePage = page),
nextPageText: 'Siguiente',
previousPageText: 'Anterior',
pageText: 'Pagina',
ofText: 'de',
recordsText: 'registros',
),
if (_responseMessage != null) ...[
const SizedBox(height: 12),
GpText(text: _responseMessage!),
],
],
),
);
}
}