This commit is contained in:
2026-04-07 11:30:22 +02:00
parent 4bbd1edf48
commit 130780cbb8
20 changed files with 426 additions and 131 deletions

View File

@@ -0,0 +1,57 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
part 'auth_events.dart';
part 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final _supabase = GetIt.instance<SupabaseClient>();
AuthBloc()
: super(const AuthState(status: AuthStatus.initial, isLoginMode: true)) {
on<ToggleAuthMode>(
(event, emit) => emit(state.copyWith(isLoginMode: !state.isLoginMode)),
);
on<LoginRequested>((event, emit) async {
emit(state.copyWith(status: AuthStatus.loading));
try {
if (state.isLoginMode) {
// --- LOGICA LOGIN ---
await _supabase.auth.signInWithPassword(
email: event.email,
password: event.password,
);
// Non serve emettere success qui, ci pensa il SessionBloc!
} else {
// --- LOGICA SIGNUP ---
await _supabase.auth.signUp(
email: event.email,
password: event.password,
// Qui potresti passare il "Codice Negozio" nei data dell'utente
data: {'store_code': event.storeCode},
);
// Nota: Se Supabase richiede conferma email, l'utente non sarà
// loggato subito. Gestiamolo con un messaggio.
emit(
state.copyWith(
status: AuthStatus.success,
error: "Controlla la tua email per confermare l'account!",
),
);
}
} on AuthException catch (e) {
emit(state.copyWith(status: AuthStatus.failure, error: e.message));
} catch (e) {
emit(
state.copyWith(
status: AuthStatus.failure,
error: "Errore imprevisto: $e",
),
);
}
});
}
}

View File

@@ -0,0 +1,24 @@
part of 'auth_bloc.dart';
abstract class AuthEvent extends Equatable {
const AuthEvent();
@override
List<Object?> get props => [];
}
class ToggleAuthMode extends AuthEvent {} // Passa da Login a Registrazione
class LoginRequested extends AuthEvent {
final String email;
final String password;
final String? storeCode;
const LoginRequested({
required this.email,
required this.password,
this.storeCode,
});
@override
List<Object?> get props => [email, password, storeCode];
}

View File

@@ -0,0 +1,26 @@
part of 'auth_bloc.dart';
enum AuthStatus { initial, loading, success, failure }
class AuthState extends Equatable {
const AuthState({
required this.status,
this.error,
required this.isLoginMode,
});
final AuthStatus status;
final String? error;
final bool isLoginMode;
@override
List<Object?> get props => [status, error, isLoginMode];
AuthState copyWith({AuthStatus? status, String? error, bool? isLoginMode}) {
return AuthState(
status: status ?? this.status,
error: error,
isLoginMode: isLoginMode ?? this.isLoginMode,
);
}
}

View File

@@ -0,0 +1,103 @@
// lib/ui/auth/auth_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/auth/bloc/auth_bloc.dart';
import 'package:flux/core/theme/theme.dart';
import 'package:flux/core/widgets/flux_text_field.dart';
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _storeController = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state.status == AuthStatus.failure) {
// Mostra l'errore che arriva da Supabase (es. "Invalid login credentials")
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.error!), backgroundColor: Colors.red),
);
}
},
builder: (context, state) {
return Column(
children: [
FluxTextField(
label: 'Email',
icon: Icons.email,
controller: _emailController,
),
FluxTextField(
label: 'Password',
icon: Icons.lock,
isPassword: true,
controller: _passwordController,
),
if (!state.isLoginMode)
FluxTextField(
label: 'Codice Negozio',
icon: Icons.store,
controller: _storeController,
),
ElevatedButton(
onPressed: state.status == AuthStatus.loading
? null
: () {
context.read<AuthBloc>().add(
LoginRequested(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
storeCode: _storeController.text.trim(),
),
);
},
child: state.status == AuthStatus.loading
? const CircularProgressIndicator()
: Text(state.isLoginMode ? 'ACCEDI' : 'REGISTRATI'),
),
],
);
},
);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_storeController.dispose();
super.dispose();
}
}
class _FluxLogo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Icon(
Icons.all_inclusive,
size: 80,
color: context.accent,
), // Simbolo Flux/Infinito
Text(
'FLUX',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w900,
letterSpacing: 8,
),
),
],
);
}
}