ADD ALOT OF POLISHING AND UPDATES...
This commit is contained in:
@@ -12,19 +12,34 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
AuthRepositoryImpl(this.remoteDataSource, this.secureStorage);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, UserEntity>> login(String email, String password) async {
|
||||
Future<Either<Failure, UserEntity>> login(
|
||||
String email, String password) async {
|
||||
try {
|
||||
// 1. Suruh data source nembak API
|
||||
final userModel = await remoteDataSource.login(email, password);
|
||||
|
||||
|
||||
// 2. Simpan token langsung ke HP biar UI gausah ribet
|
||||
await secureStorage.saveToken(userModel.token);
|
||||
|
||||
|
||||
// 3. Balikin data sukses (Right)
|
||||
return Right(userModel);
|
||||
} catch (e) {
|
||||
// 4. Balikin pesan error (Left)
|
||||
return Left(ServerFailure(e.toString().replaceAll('Exception: ', '')));
|
||||
return Left(ServerFailure(_safeAuthFailure(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _safeAuthFailure(Object error) {
|
||||
final message = error.toString().replaceFirst(RegExp(r'^\w+:\s*'), '').trim();
|
||||
final lower = message.toLowerCase();
|
||||
final technical = lower.contains('exception') ||
|
||||
lower.contains('typeerror') ||
|
||||
lower.contains('stacktrace') ||
|
||||
lower.contains('instance of') ||
|
||||
lower.contains('package:');
|
||||
if (message.isEmpty || technical) {
|
||||
return 'Login gagal. Periksa email dan password kamu.';
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -11,6 +10,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../app/app_cubit.dart';
|
||||
import '../../app/injection_container.dart';
|
||||
import '../../core/constants/app_constants.dart';
|
||||
import '../../core/errors/friendly_error.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../../core/services/offline_queue_service.dart';
|
||||
import '../../core/services/tts_service.dart';
|
||||
@@ -65,27 +65,27 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
return;
|
||||
}
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
final res = await sl<ApiClient>().dio.post('/auth/login', data: {
|
||||
'email': _email.text.trim(),
|
||||
'password': _password.text,
|
||||
});
|
||||
await _saveAuthAndRoute(
|
||||
context, Map<String, dynamic>.from(res.data['data'] as Map));
|
||||
} on DioException catch (e) {
|
||||
_snack(context, _friendlyDioMessage(e, fallback: 'Login gagal'));
|
||||
} catch (e) {
|
||||
_snack(context, 'Login gagal: $e');
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
await runFriendlyAction(
|
||||
() async {
|
||||
final res = await sl<ApiClient>().dio.post('/auth/login', data: {
|
||||
'email': _email.text.trim(),
|
||||
'password': _password.text,
|
||||
});
|
||||
await _saveAuthAndRoute(
|
||||
context, Map<String, dynamic>.from(res.data['data'] as Map));
|
||||
},
|
||||
onError: (message) => _snack(context, message),
|
||||
fallback: 'Login gagal. Periksa email dan password kamu.',
|
||||
connectionHint: 'Tidak bisa ke server. Pakai URL backend publik/aktif.',
|
||||
);
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _AuthFrame(
|
||||
title: 'Sign in',
|
||||
subtitle: 'Masuk sebagai Guardian atau User.',
|
||||
subtitle: 'Masuk ke navigasi asistif realtime WalkGuide.',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@@ -147,42 +147,134 @@ class _AuthFrame extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 460),
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
side: const BorderSide(color: Color(0xFFE2E8F0))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Icon(Icons.navigation_rounded,
|
||||
color: Color(0xFF1A56DB), size: 42),
|
||||
const SizedBox(height: 14),
|
||||
Text(title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(fontWeight: FontWeight.w800)),
|
||||
const SizedBox(height: 4),
|
||||
Text(subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Color(0xFF64748B))),
|
||||
const SizedBox(height: 22),
|
||||
child,
|
||||
],
|
||||
backgroundColor: const Color(0xFFEAF4FF),
|
||||
body: Stack(
|
||||
children: [
|
||||
const Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFD9EEFF), Color(0xFFF7FAFC)],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: -90,
|
||||
right: -60,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.85, end: 1),
|
||||
duration: const Duration(milliseconds: 900),
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (_, value, child) => Transform.scale(
|
||||
scale: value,
|
||||
child: child,
|
||||
),
|
||||
child: Container(
|
||||
width: 260,
|
||||
height: 260,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2563EB).withValues(alpha: 0.14),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 430),
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 18, end: 0),
|
||||
duration: const Duration(milliseconds: 520),
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (_, offset, child) => Opacity(
|
||||
opacity: (1 - offset / 18).clamp(0.0, 1.0),
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, offset),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.96),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.8)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
const Color(0xFF1E3A8A).withValues(alpha: 0.14),
|
||||
blurRadius: 40,
|
||||
offset: const Offset(0, 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 26, 24, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1D4ED8),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: const Icon(Icons.navigation_rounded,
|
||||
color: Colors.white, size: 30),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'WalkGuide',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: const Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B),
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 26),
|
||||
child,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -219,16 +311,14 @@ Future<void> _saveAuthAndRoute(
|
||||
|
||||
void _startPostLoginServices(String serverUrl) {
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
await sl<WebSocketService>()
|
||||
.connect(serverUrl)
|
||||
.timeout(const Duration(seconds: 2));
|
||||
await sl<OfflineQueueService>()
|
||||
.syncPending(sl<ApiClient>())
|
||||
.timeout(const Duration(seconds: 3));
|
||||
} catch (e) {
|
||||
debugPrint('Post-login services skipped: $e');
|
||||
}
|
||||
await sl<WebSocketService>()
|
||||
.connect(serverUrl)
|
||||
.timeout(const Duration(seconds: 2));
|
||||
await sl<OfflineQueueService>()
|
||||
.syncPending(sl<ApiClient>())
|
||||
.timeout(const Duration(seconds: 3));
|
||||
}).catchError((Object e) {
|
||||
debugPrint('Post-login services skipped: $e');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -238,19 +328,3 @@ void _snack(BuildContext context, String message) {
|
||||
.showSnackBar(SnackBar(content: Text(message)));
|
||||
}
|
||||
}
|
||||
|
||||
String _friendlyDioMessage(DioException e, {required String fallback}) {
|
||||
final data = e.response?.data;
|
||||
if (data is Map && data['message'] != null) return data['message'].toString();
|
||||
if (e.response?.statusCode == 401) {
|
||||
return 'Email atau password salah.';
|
||||
}
|
||||
if (e.type == DioExceptionType.connectionTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout) {
|
||||
return 'Server terlalu lama merespons. Pastikan backend masih running dan URL server benar.';
|
||||
}
|
||||
if (e.type == DioExceptionType.connectionError) {
|
||||
return 'Tidak bisa ke server. Di Chrome pakai http://localhost:8080. Di HP pakai IP laptop/server, bukan localhost.';
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export '../../screens.dart';
|
||||
export '../login_screen.dart';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../app/injection_container.dart';
|
||||
import '../../core/errors/friendly_error.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -52,26 +52,26 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
return;
|
||||
}
|
||||
setState(() => _loading = true);
|
||||
try {
|
||||
final res = await sl<ApiClient>().dio.post('/auth/register', data: {
|
||||
'displayName': _name.text.trim(),
|
||||
'email': _email.text.trim(),
|
||||
'password': _password.text,
|
||||
'role': _role,
|
||||
});
|
||||
final data = Map<String, dynamic>.from(res.data['data'] as Map);
|
||||
if (!mounted) return;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('pending_login_email', _email.text.trim());
|
||||
await _showRegisterSuccess(context, data);
|
||||
if (mounted) context.go('/login');
|
||||
} on DioException catch (e) {
|
||||
_snack(context, _friendlyDioMessage(e, fallback: 'Registrasi gagal'));
|
||||
} catch (e) {
|
||||
_snack(context, 'Registrasi gagal: $e');
|
||||
} finally {
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
await runFriendlyAction(
|
||||
() async {
|
||||
final res = await sl<ApiClient>().dio.post('/auth/register', data: {
|
||||
'displayName': _name.text.trim(),
|
||||
'email': _email.text.trim(),
|
||||
'password': _password.text,
|
||||
'role': _role,
|
||||
});
|
||||
final data = Map<String, dynamic>.from(res.data['data'] as Map);
|
||||
if (!mounted) return;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('pending_login_email', _email.text.trim());
|
||||
await _showRegisterSuccess(context, data);
|
||||
if (mounted) context.go('/login');
|
||||
},
|
||||
onError: (message) => _snack(context, message),
|
||||
fallback: 'Registrasi gagal. Periksa data akun kamu.',
|
||||
connectionHint: 'Tidak bisa ke server. Pakai URL backend publik/aktif.',
|
||||
);
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -81,7 +81,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
subtitle: _step == 0
|
||||
? 'Who are you in the WalkGuide system?'
|
||||
: _role == 'USER'
|
||||
? 'User akan mendapat Unique ID untuk pairing.'
|
||||
? 'User bisa membuat Pairing Code sementara setelah login.'
|
||||
: 'Guardian dapat monitor dan konfigurasi User.',
|
||||
child: _step == 0 ? _buildRoleStep(context) : _buildFormStep(context),
|
||||
);
|
||||
@@ -298,42 +298,134 @@ class _AuthFrame extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 460),
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
side: const BorderSide(color: Color(0xFFE2E8F0))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Icon(Icons.navigation_rounded,
|
||||
color: Color(0xFF1A56DB), size: 42),
|
||||
const SizedBox(height: 14),
|
||||
Text(title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(fontWeight: FontWeight.w800)),
|
||||
const SizedBox(height: 4),
|
||||
Text(subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Color(0xFF64748B))),
|
||||
const SizedBox(height: 22),
|
||||
child,
|
||||
],
|
||||
backgroundColor: const Color(0xFFEAF4FF),
|
||||
body: Stack(
|
||||
children: [
|
||||
const Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFD9EEFF), Color(0xFFF7FAFC)],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: -90,
|
||||
right: -60,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.85, end: 1),
|
||||
duration: const Duration(milliseconds: 900),
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (_, value, child) => Transform.scale(
|
||||
scale: value,
|
||||
child: child,
|
||||
),
|
||||
child: Container(
|
||||
width: 260,
|
||||
height: 260,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2563EB).withValues(alpha: 0.14),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 430),
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 18, end: 0),
|
||||
duration: const Duration(milliseconds: 520),
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (_, offset, child) => Opacity(
|
||||
opacity: (1 - offset / 18).clamp(0.0, 1.0),
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, offset),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.96),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.8)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
const Color(0xFF1E3A8A).withValues(alpha: 0.14),
|
||||
blurRadius: 40,
|
||||
offset: const Offset(0, 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 26, 24, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1D4ED8),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: const Icon(Icons.navigation_rounded,
|
||||
color: Colors.white, size: 30),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'WalkGuide',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: const Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B),
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 26),
|
||||
child,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -348,12 +440,12 @@ Future<void> _showRegisterSuccess(
|
||||
final uniqueId = data['uniqueUserId']?.toString();
|
||||
final message = uniqueId == null || uniqueId.isEmpty
|
||||
? 'Registrasi berhasil. Silakan login.'
|
||||
: 'Registrasi berhasil!\n\nUnique User ID kamu:\n$uniqueId\n\nBagikan ID ini ke Guardian untuk pairing. Silakan login.';
|
||||
: 'Registrasi berhasil.\n\nSilakan login, lalu buka menu Pairing Code untuk membuat kode sementara yang bisa dibagikan ke Guardian.';
|
||||
_snack(
|
||||
context,
|
||||
uniqueId == null
|
||||
? 'Registrasi berhasil.'
|
||||
: 'Registrasi berhasil. ID: $uniqueId');
|
||||
: 'Registrasi berhasil. Buat Pairing Code setelah login.');
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
@@ -375,19 +467,3 @@ void _snack(BuildContext context, String message) {
|
||||
.showSnackBar(SnackBar(content: Text(message)));
|
||||
}
|
||||
}
|
||||
|
||||
String _friendlyDioMessage(DioException e, {required String fallback}) {
|
||||
final data = e.response?.data;
|
||||
if (data is Map && data['message'] != null) return data['message'].toString();
|
||||
if (e.response?.statusCode == 409) {
|
||||
return 'Email sudah terdaftar. Gunakan email lain atau login.';
|
||||
}
|
||||
if (e.type == DioExceptionType.connectionTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout) {
|
||||
return 'Server terlalu lama merespons. Pastikan backend masih running.';
|
||||
}
|
||||
if (e.type == DioExceptionType.connectionError) {
|
||||
return 'Tidak bisa ke server. Di HP pakai IP server, bukan localhost.';
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../app/injection_container.dart';
|
||||
import '../../core/errors/friendly_error.dart';
|
||||
import '../../core/storage/secure_storage.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -51,29 +52,33 @@ class _SplashScreenState extends State<SplashScreen>
|
||||
}
|
||||
|
||||
Future<void> _route() async {
|
||||
try {
|
||||
// Animasi logo selalu tampil minimal 500ms agar tidak langsung flash.
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
final routed = await runFriendlyAction(
|
||||
() async {
|
||||
// Animasi logo selalu tampil minimal 500ms agar tidak langsung flash.
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
final storage = sl<SecureStorage>();
|
||||
final token =
|
||||
await storage.getAccessToken().timeout(const Duration(seconds: 3));
|
||||
final role =
|
||||
await storage.getUserRole().timeout(const Duration(seconds: 3));
|
||||
final storage = sl<SecureStorage>();
|
||||
final token =
|
||||
await storage.getAccessToken().timeout(const Duration(seconds: 3));
|
||||
final role =
|
||||
await storage.getUserRole().timeout(const Duration(seconds: 3));
|
||||
|
||||
if (!mounted) return;
|
||||
if (!mounted) return;
|
||||
|
||||
if (token == null || role == null) {
|
||||
context.go('/login');
|
||||
return;
|
||||
}
|
||||
if (token == null || role == null) {
|
||||
context.go('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-login: arahkan ke home sesuai role.
|
||||
context.go(
|
||||
role == 'ROLE_GUARDIAN' ? '/guardian/dashboard' : '/user/walkguide');
|
||||
} catch (_) {
|
||||
if (mounted) context.go('/login');
|
||||
}
|
||||
// Auto-login: arahkan ke home sesuai role.
|
||||
context.go(role == 'ROLE_GUARDIAN'
|
||||
? '/guardian/dashboard'
|
||||
: '/user/walkguide');
|
||||
},
|
||||
onError: (_) {},
|
||||
fallback: 'Sesi belum bisa dipulihkan.',
|
||||
);
|
||||
if (!routed && mounted) context.go('/login');
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user