first commit
This commit is contained in:
38
lib/features/auth/data/services/auth_service.dart
Normal file
38
lib/features/auth/data/services/auth_service.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class AuthService {
|
||||
const AuthService(this._client);
|
||||
final SupabaseClient _client;
|
||||
|
||||
User? get currentUser => _client.auth.currentUser;
|
||||
Session? get currentSession => _client.auth.currentSession;
|
||||
Stream<AuthState> get onAuthStateChange => _client.auth.onAuthStateChange;
|
||||
|
||||
Future<AuthResponse> signUp({
|
||||
required String email,
|
||||
required String password,
|
||||
required String username,
|
||||
}) async {
|
||||
return await _client.auth.signUp(
|
||||
email: email,
|
||||
password: password,
|
||||
data: {'username': username},
|
||||
);
|
||||
}
|
||||
|
||||
Future<AuthResponse> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
return await _client.auth.signInWithPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> logout() async => await _client.auth.signOut();
|
||||
|
||||
Future<void> forgotPassword(String email) async {
|
||||
await _client.auth.resetPasswordForEmail(email);
|
||||
}
|
||||
}
|
||||
126
lib/features/auth/presentation/pages/forgot_password_page.dart
Normal file
126
lib/features/auth/presentation/pages/forgot_password_page.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/router/app_routes.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/widgets/riotz_scaffold.dart';
|
||||
import '../../../../core/widgets/riotz_button.dart';
|
||||
import '../providers/auth_providers.dart';
|
||||
|
||||
class ForgotPasswordPage extends ConsumerStatefulWidget {
|
||||
const ForgotPasswordPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ForgotPasswordPage> createState() => _ForgotPasswordPageState();
|
||||
}
|
||||
|
||||
class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
ref.listen(authControllerProvider, (_, next) {
|
||||
next.whenOrNull(
|
||||
data: (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
backgroundColor: AppColors.success,
|
||||
content: Text(
|
||||
'RECOVERY SIGNAL SENT. CHECK YOUR INBOX.',
|
||||
style: TextStyle(color: AppColors.black, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (mounted) context.go(AppRoutes.login);
|
||||
},
|
||||
error: (error, _) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: AppColors.bloodRed,
|
||||
content: Text(
|
||||
error.toString().toUpperCase(),
|
||||
style: const TextStyle(color: AppColors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
final authState = ref.watch(authControllerProvider);
|
||||
final loading = authState.isLoading;
|
||||
|
||||
return RiotzScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('RECOVER IDENTITY'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
||||
onPressed: () => context.go(AppRoutes.login),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'LOST SIGNAL',
|
||||
style: theme.textTheme.displayMedium?.copyWith(height: 1),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'ENTER YOUR EMAIL TO RESTORE ACCESS.',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.neonRed),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'EMAIL ADDRESS',
|
||||
prefixIcon: Icon(Icons.email_outlined, color: AppColors.grey),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) => (v == null || !v.contains('@'))
|
||||
? 'INVALID EMAIL'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
RiotzButton(
|
||||
label: 'SEND RECOVERY LINK',
|
||||
isLoading: loading,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await ref
|
||||
.read(authControllerProvider.notifier)
|
||||
.forgotPassword(_emailController.text);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () => context.go(AppRoutes.login),
|
||||
child: Text(
|
||||
'REMEMBERED? BACK TO LOGIN',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
142
lib/features/auth/presentation/pages/login_page.dart
Normal file
142
lib/features/auth/presentation/pages/login_page.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/router/app_routes.dart';
|
||||
import '../../../../core/widgets/riotz_scaffold.dart';
|
||||
import '../../../../core/widgets/riotz_button.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../providers/auth_providers.dart';
|
||||
|
||||
class LoginPage extends ConsumerStatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
ref.listen(authControllerProvider, (_, next) {
|
||||
next.whenOrNull(
|
||||
data: (_) {
|
||||
if (mounted) context.go(AppRoutes.home);
|
||||
},
|
||||
error: (error, _) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: AppColors.bloodRed,
|
||||
content: Text(
|
||||
error.toString().toUpperCase(),
|
||||
style: const TextStyle(color: AppColors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
final authState = ref.watch(authControllerProvider);
|
||||
final loading = authState.isLoading;
|
||||
|
||||
return RiotzScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('IDENTIFY YOURSELF'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
||||
onPressed: () => context.go(AppRoutes.splash),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'LOGIN',
|
||||
style: theme.textTheme.displayMedium?.copyWith(height: 1),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'ACCESS THE UNDERGROUND.',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.neonRed),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'EMAIL ADDRESS',
|
||||
prefixIcon: Icon(Icons.email_outlined, color: AppColors.grey),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) => (v == null || !v.contains('@'))
|
||||
? 'INVALID EMAIL'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'PASSWORD',
|
||||
prefixIcon: Icon(Icons.lock_outline, color: AppColors.grey),
|
||||
),
|
||||
obscureText: true,
|
||||
validator: (v) =>
|
||||
(v == null || v.length < 6) ? 'PASSWORD TOO SHORT' : null,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
RiotzButton(
|
||||
label: 'ACCESS GRANTED',
|
||||
isLoading: loading,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await ref.read(authControllerProvider.notifier).login(
|
||||
email: _emailController.text,
|
||||
password: _passwordController.text,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading
|
||||
? null
|
||||
: () => context.push(AppRoutes.forgotPassword),
|
||||
child: Text(
|
||||
'FORGOT CREDENTIALS?',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () => context.go(AppRoutes.signup),
|
||||
child: Text(
|
||||
'NO IDENTITY? SIGN UP',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
153
lib/features/auth/presentation/pages/signup_page.dart
Normal file
153
lib/features/auth/presentation/pages/signup_page.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/router/app_routes.dart';
|
||||
import '../../../../core/widgets/riotz_scaffold.dart';
|
||||
import '../../../../core/widgets/riotz_button.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../providers/auth_providers.dart';
|
||||
|
||||
class SignupPage extends ConsumerStatefulWidget {
|
||||
const SignupPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SignupPage> createState() => _SignupPageState();
|
||||
}
|
||||
|
||||
class _SignupPageState extends ConsumerState<SignupPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _usernameController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_usernameController.dispose();
|
||||
_emailController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
ref.listen(authControllerProvider, (_, next) {
|
||||
next.whenOrNull(
|
||||
data: (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
backgroundColor: AppColors.success,
|
||||
content: Text(
|
||||
'IDENTITY CREATED. CHECK EMAIL FOR CONFIRMATION.',
|
||||
style: TextStyle(color: AppColors.black, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (mounted) context.go(AppRoutes.login);
|
||||
},
|
||||
error: (error, _) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: AppColors.bloodRed,
|
||||
content: Text(
|
||||
error.toString().toUpperCase(),
|
||||
style: const TextStyle(color: AppColors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
final authState = ref.watch(authControllerProvider);
|
||||
final loading = authState.isLoading;
|
||||
|
||||
return RiotzScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('CREATE IDENTITY'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
||||
onPressed: () => context.go(AppRoutes.login),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'SIGN UP',
|
||||
style: theme.textTheme.displayMedium?.copyWith(height: 1),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'JOIN THE UNDERGROUND MOVEMENT.',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.neonRed),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
TextFormField(
|
||||
controller: _usernameController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'USERNAME',
|
||||
prefixIcon: Icon(Icons.person_outline, color: AppColors.grey),
|
||||
),
|
||||
validator: (v) =>
|
||||
(v == null || v.trim().length < 3) ? 'USERNAME TOO SHORT' : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'EMAIL ADDRESS',
|
||||
prefixIcon: Icon(Icons.email_outlined, color: AppColors.grey),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) => (v == null || !v.contains('@'))
|
||||
? 'INVALID EMAIL'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'PASSWORD',
|
||||
prefixIcon: Icon(Icons.lock_outline, color: AppColors.grey),
|
||||
),
|
||||
obscureText: true,
|
||||
validator: (v) =>
|
||||
(v == null || v.length < 6) ? 'PASSWORD TOO SHORT' : null,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
RiotzButton(
|
||||
label: 'INITIALIZE RIOT',
|
||||
isLoading: loading,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await ref.read(authControllerProvider.notifier).signup(
|
||||
email: _emailController.text,
|
||||
password: _passwordController.text,
|
||||
username: _usernameController.text,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () => context.go(AppRoutes.login),
|
||||
child: Text(
|
||||
'ALREADY HAVE AN IDENTITY? LOGIN',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
65
lib/features/auth/presentation/providers/auth_provider.dart
Normal file
65
lib/features/auth/presentation/providers/auth_provider.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import '../../../../core/supabase/supabase_providers.dart';
|
||||
import '../../data/services/auth_service.dart';
|
||||
|
||||
/// Provider for the AuthService.
|
||||
final authServiceProvider = Provider<AuthService>((ref) {
|
||||
final client = ref.watch(supabaseProvider);
|
||||
return AuthService(client);
|
||||
});
|
||||
|
||||
/// Provider for the current user's authentication state.
|
||||
final authStateProvider = StreamProvider<AuthState>((ref) {
|
||||
return ref.watch(authServiceProvider).onAuthStateChange;
|
||||
});
|
||||
|
||||
/// Controller for authentication actions.
|
||||
final authControllerProvider = AutoDisposeAsyncNotifierProvider<AuthController, void>(
|
||||
AuthController.new,
|
||||
);
|
||||
|
||||
class AuthController extends AutoDisposeAsyncNotifier<void> {
|
||||
@override
|
||||
Future<void> build() async {}
|
||||
|
||||
Future<void> login({required String email, required String password}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).login(
|
||||
email: email.trim(),
|
||||
password: password,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> signup({
|
||||
required String email,
|
||||
required String password,
|
||||
required String username,
|
||||
}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).signUp(
|
||||
email: email.trim(),
|
||||
password: password,
|
||||
username: username.trim(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).logout();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> forgotPassword(String email) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).forgotPassword(email.trim());
|
||||
});
|
||||
}
|
||||
}
|
||||
80
lib/features/auth/presentation/providers/auth_providers.dart
Normal file
80
lib/features/auth/presentation/providers/auth_providers.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import '../../../../core/supabase/supabase_providers.dart';
|
||||
import '../../data/services/auth_service.dart';
|
||||
|
||||
final authServiceProvider = Provider<AuthService>((ref) {
|
||||
final client = ref.watch(supabaseProvider);
|
||||
return AuthService(client);
|
||||
});
|
||||
|
||||
final authStateChangesProvider = StreamProvider<AuthState>((ref) {
|
||||
final service = ref.watch(authServiceProvider);
|
||||
return service.authStateChanges();
|
||||
});
|
||||
|
||||
final currentSessionProvider = StreamProvider<Session?>((ref) async* {
|
||||
final service = ref.watch(authServiceProvider);
|
||||
yield service.currentSession;
|
||||
await for (final event in service.authStateChanges()) {
|
||||
yield event.session;
|
||||
}
|
||||
});
|
||||
|
||||
final currentUserProvider = Provider<User?>((ref) {
|
||||
final service = ref.watch(authServiceProvider);
|
||||
return service.currentUser;
|
||||
});
|
||||
|
||||
final authControllerProvider =
|
||||
AutoDisposeAsyncNotifierProvider<AuthController, void>(
|
||||
AuthController.new,
|
||||
);
|
||||
|
||||
class AuthController extends AutoDisposeAsyncNotifier<void> {
|
||||
@override
|
||||
Future<void> build() async {}
|
||||
|
||||
Future<void> signup({
|
||||
required String email,
|
||||
required String password,
|
||||
required String username,
|
||||
}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).signUp(
|
||||
email: email.trim(),
|
||||
password: password,
|
||||
username: username.trim(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).login(
|
||||
email: email.trim(),
|
||||
password: password,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).logout();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> forgotPassword(String email) async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
await ref.read(authServiceProvider).forgotPassword(email.trim());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/router/app_routes.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/widgets/riotz_scaffold.dart';
|
||||
import '../../../../core/widgets/riotz_button.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
|
||||
class ForgotPasswordScreen extends ConsumerStatefulWidget {
|
||||
const ForgotPasswordScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ForgotPasswordScreen> createState() => _ForgotPasswordScreenState();
|
||||
}
|
||||
|
||||
class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
ref.listen(authControllerProvider, (_, next) {
|
||||
next.whenOrNull(
|
||||
data: (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
backgroundColor: AppColors.success,
|
||||
content: Text(
|
||||
'RECOVERY SIGNAL SENT. CHECK YOUR INBOX.',
|
||||
style: TextStyle(color: AppColors.black, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (mounted) context.go(AppRoutes.login);
|
||||
},
|
||||
error: (error, _) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: AppColors.bloodRed,
|
||||
content: Text(
|
||||
error.toString().toUpperCase(),
|
||||
style: const TextStyle(color: AppColors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
final authState = ref.watch(authControllerProvider);
|
||||
final loading = authState.isLoading;
|
||||
|
||||
return RiotzScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('RECOVER IDENTITY'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
||||
onPressed: () => context.go(AppRoutes.login),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'LOST SIGNAL',
|
||||
style: theme.textTheme.displayMedium?.copyWith(height: 1),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'ENTER YOUR EMAIL TO RESTORE ACCESS.',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.neonRed),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'EMAIL ADDRESS',
|
||||
prefixIcon: Icon(Icons.email_outlined, color: AppColors.grey),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) => (v == null || !v.contains('@'))
|
||||
? 'INVALID EMAIL'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
RiotzButton(
|
||||
label: 'SEND RECOVERY LINK',
|
||||
isLoading: loading,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await ref
|
||||
.read(authControllerProvider.notifier)
|
||||
.forgotPassword(_emailController.text);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () => context.go(AppRoutes.login),
|
||||
child: Text(
|
||||
'REMEMBERED? BACK TO LOGIN',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
149
lib/features/auth/presentation/screens/login_screen.dart
Normal file
149
lib/features/auth/presentation/screens/login_screen.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/router/app_routes.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/widgets/riotz_button.dart';
|
||||
import '../../../../core/widgets/riotz_scaffold.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
|
||||
class LoginScreen extends ConsumerStatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
ref.listen(authControllerProvider, (_, next) {
|
||||
next.whenOrNull(
|
||||
error: (error, _) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: AppColors.bloodRed,
|
||||
content: Text(
|
||||
error.toString().toUpperCase(),
|
||||
style: const TextStyle(
|
||||
color: AppColors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
final authState = ref.watch(authControllerProvider);
|
||||
final loading = authState.isLoading;
|
||||
|
||||
return RiotzScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('IDENTIFY'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
||||
onPressed: () => context.go(AppRoutes.splash),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'LOGIN',
|
||||
style: theme.textTheme.displayMedium?.copyWith(height: 1),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'ENTER THE VOID.',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: AppColors.neonRed,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'EMAIL',
|
||||
prefixIcon: Icon(Icons.email_outlined, color: AppColors.grey),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) => (v == null || !v.contains('@'))
|
||||
? 'INVALID EMAIL'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'PASSWORD',
|
||||
prefixIcon: Icon(Icons.lock_outline, color: AppColors.grey),
|
||||
),
|
||||
obscureText: true,
|
||||
validator: (v) => (v == null || v.length < 6)
|
||||
? 'PASSWORD TOO SHORT'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
RiotzButton(
|
||||
label: 'ACCESS GRANTED',
|
||||
isLoading: loading,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await ref.read(authControllerProvider.notifier).login(
|
||||
email: _emailController.text,
|
||||
password: _passwordController.text,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading
|
||||
? null
|
||||
: () => context.push(AppRoutes.forgotPassword),
|
||||
child: Text(
|
||||
'FORGOT CREDENTIALS?',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: AppColors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () => context.go(AppRoutes.signup),
|
||||
child: Text(
|
||||
'NO IDENTITY? SIGN UP',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: AppColors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
153
lib/features/auth/presentation/screens/signup_screen.dart
Normal file
153
lib/features/auth/presentation/screens/signup_screen.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/router/app_routes.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/widgets/riotz_button.dart';
|
||||
import '../../../../core/widgets/riotz_scaffold.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
|
||||
class SignupScreen extends ConsumerStatefulWidget {
|
||||
const SignupScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SignupScreen> createState() => _SignupScreenState();
|
||||
}
|
||||
|
||||
class _SignupScreenState extends ConsumerState<SignupScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _usernameController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_usernameController.dispose();
|
||||
_emailController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
ref.listen(authControllerProvider, (_, next) {
|
||||
next.whenOrNull(
|
||||
data: (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
backgroundColor: AppColors.success,
|
||||
content: Text(
|
||||
'IDENTITY INITIALIZED. CHECK EMAIL FOR CONFIRMATION.',
|
||||
style: TextStyle(color: AppColors.black, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (mounted) context.go(AppRoutes.login);
|
||||
},
|
||||
error: (error, _) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: AppColors.bloodRed,
|
||||
content: Text(
|
||||
error.toString().toUpperCase(),
|
||||
style: const TextStyle(color: AppColors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
final authState = ref.watch(authControllerProvider);
|
||||
final loading = authState.isLoading;
|
||||
|
||||
return RiotzScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('CREATE IDENTITY'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
||||
onPressed: () => context.go(AppRoutes.login),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'SIGN UP',
|
||||
style: theme.textTheme.displayMedium?.copyWith(height: 1),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'JOIN THE RIOT.',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.neonRed),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
TextFormField(
|
||||
controller: _usernameController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'USERNAME',
|
||||
prefixIcon: Icon(Icons.person_outline, color: AppColors.grey),
|
||||
),
|
||||
validator: (v) =>
|
||||
(v == null || v.trim().length < 3) ? 'USERNAME TOO SHORT' : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'EMAIL ADDRESS',
|
||||
prefixIcon: Icon(Icons.email_outlined, color: AppColors.grey),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) => (v == null || !v.contains('@'))
|
||||
? 'INVALID EMAIL'
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _passwordController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'PASSWORD',
|
||||
prefixIcon: Icon(Icons.lock_outline, color: AppColors.grey),
|
||||
),
|
||||
obscureText: true,
|
||||
validator: (v) =>
|
||||
(v == null || v.length < 6) ? 'PASSWORD TOO SHORT' : null,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
RiotzButton(
|
||||
label: 'INITIALIZE',
|
||||
isLoading: loading,
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await ref.read(authControllerProvider.notifier).signup(
|
||||
email: _emailController.text,
|
||||
password: _passwordController.text,
|
||||
username: _usernameController.text,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loading ? null : () => context.go(AppRoutes.login),
|
||||
child: Text(
|
||||
'ALREADY HAVE AN IDENTITY? LOGIN',
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user