Initial Flutter scaffold: Riverpod + GoRouter + Dio

This commit is contained in:
2026-01-27 13:20:01 +00:00
commit c8ac4ec8bc
18 changed files with 2728 additions and 0 deletions

143
lib/app/router.dart Normal file
View File

@@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../features/auth/presentation/login_screen.dart';
import '../features/auth/presentation/register_screen.dart';
import '../features/clients/presentation/clients_screen.dart';
import '../features/clients/presentation/client_detail_screen.dart';
import '../features/clients/presentation/client_form_screen.dart';
import '../features/emails/presentation/emails_screen.dart';
import '../features/emails/presentation/email_compose_screen.dart';
import '../features/events/presentation/events_screen.dart';
import '../shared/providers/auth_provider.dart';
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authStateProvider);
return GoRouter(
initialLocation: '/',
redirect: (context, state) {
final isLoggedIn = authState.valueOrNull?.isAuthenticated ?? false;
final isAuthRoute = state.matchedLocation == '/login' ||
state.matchedLocation == '/register';
if (!isLoggedIn && !isAuthRoute) {
return '/login';
}
if (isLoggedIn && isAuthRoute) {
return '/';
}
return null;
},
routes: [
// Auth routes
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/register',
builder: (context, state) => const RegisterScreen(),
),
// Main app routes
ShellRoute(
builder: (context, state, child) => MainShell(child: child),
routes: [
GoRoute(
path: '/',
builder: (context, state) => const ClientsScreen(),
),
GoRoute(
path: '/clients/new',
builder: (context, state) => const ClientFormScreen(),
),
GoRoute(
path: '/clients/:id',
builder: (context, state) => ClientDetailScreen(
clientId: state.pathParameters['id']!,
),
),
GoRoute(
path: '/clients/:id/edit',
builder: (context, state) => ClientFormScreen(
clientId: state.pathParameters['id'],
),
),
GoRoute(
path: '/emails',
builder: (context, state) => const EmailsScreen(),
),
GoRoute(
path: '/emails/compose',
builder: (context, state) => EmailComposeScreen(
clientId: state.uri.queryParameters['clientId'],
),
),
GoRoute(
path: '/events',
builder: (context, state) => const EventsScreen(),
),
],
),
],
);
});
class MainShell extends StatelessWidget {
final Widget child;
const MainShell({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
selectedIndex: _getSelectedIndex(context),
onDestinationSelected: (index) => _onDestinationSelected(context, index),
destinations: const [
NavigationDestination(
icon: Icon(Icons.people_outline),
selectedIcon: Icon(Icons.people),
label: 'Clients',
),
NavigationDestination(
icon: Icon(Icons.email_outline),
selectedIcon: Icon(Icons.email),
label: 'Emails',
),
NavigationDestination(
icon: Icon(Icons.event_outline),
selectedIcon: Icon(Icons.event),
label: 'Events',
),
],
),
);
}
int _getSelectedIndex(BuildContext context) {
final location = GoRouterState.of(context).matchedLocation;
if (location.startsWith('/emails')) return 1;
if (location.startsWith('/events')) return 2;
return 0;
}
void _onDestinationSelected(BuildContext context, int index) {
switch (index) {
case 0:
context.go('/');
break;
case 1:
context.go('/emails');
break;
case 2:
context.go('/events');
break;
}
}
}