POLISHING STUFF and Smarter AI, and alot i mean.. ALOT OF BUG FIX AND OPTIMIZE
This commit is contained in:
@@ -19,6 +19,9 @@ import '../../core/services/offline_queue_service.dart';
|
||||
import '../../core/services/tts_service.dart';
|
||||
import '../../core/services/websocket_service.dart';
|
||||
import '../../core/storage/secure_storage.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_decorations.dart';
|
||||
import '../../core/theme/app_text_styles.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LoginScreen
|
||||
@@ -79,7 +82,8 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
},
|
||||
onError: (message) => _snack(context, message),
|
||||
fallback: 'Login gagal. Periksa email dan password kamu.',
|
||||
connectionHint: 'Tidak bisa ke server. Pakai URL backend publik/aktif.',
|
||||
connectionHint:
|
||||
'Tidak bisa ke server. Cek URL backend di Connect Server dan pastikan server aktif.',
|
||||
);
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
@@ -150,37 +154,25 @@ class _AuthFrame extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFEAF4FF),
|
||||
backgroundColor: AppColors.softBlueBg,
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final compact =
|
||||
constraints.maxWidth < 480 || constraints.maxHeight < 720;
|
||||
return Stack(
|
||||
children: [
|
||||
const Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFD9EEFF), Color(0xFFF7FAFC)],
|
||||
),
|
||||
),
|
||||
),
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
AppColors.softBlueBg,
|
||||
Colors.white,
|
||||
AppColors.softPinkBg,
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
top: compact ? -70 : -90,
|
||||
right: compact ? -70 : -60,
|
||||
child: Container(
|
||||
width: compact ? 180 : 260,
|
||||
height: compact ? 180 : 260,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2563EB).withValues(alpha: 0.12),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
keyboardDismissBehavior:
|
||||
ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
@@ -206,19 +198,10 @@ class _AuthFrame extends StatelessWidget {
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.96),
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius:
|
||||
BorderRadius.circular(compact ? 22 : 30),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.8)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF1E3A8A)
|
||||
.withValues(alpha: 0.14),
|
||||
blurRadius: compact ? 24 : 40,
|
||||
offset: const Offset(0, 18),
|
||||
),
|
||||
],
|
||||
BorderRadius.circular(compact ? 22 : 28),
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
@@ -236,13 +219,15 @@ class _AuthFrame extends StatelessWidget {
|
||||
width: compact ? 44 : 56,
|
||||
height: compact ? 44 : 56,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color(0xFF2563EB),
|
||||
Color(0xFF0891B2)
|
||||
],
|
||||
),
|
||||
gradient: AppDecorations.blueGradient,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x334A90D9),
|
||||
blurRadius: 18,
|
||||
offset: Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(Icons.navigation_rounded,
|
||||
color: Colors.white,
|
||||
@@ -256,8 +241,8 @@ class _AuthFrame extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFF0F172A),
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -269,21 +254,22 @@ class _AuthFrame extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF6FF),
|
||||
color: AppColors.softBlueBg,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.shield_outlined,
|
||||
size: 14, color: Color(0xFF1D4ED8)),
|
||||
size: 14,
|
||||
color: AppColors.primaryBlue),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Secure Assistive Navigation',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF1D4ED8),
|
||||
color: AppColors.primaryBlue,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -294,14 +280,10 @@ class _AuthFrame extends StatelessWidget {
|
||||
title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
?.copyWith(
|
||||
fontSize: compact ? 26 : null,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: const Color(0xFF0F172A),
|
||||
),
|
||||
style: AppTextStyles.heading.copyWith(
|
||||
fontSize: compact ? 26 : null,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
@@ -309,7 +291,7 @@ class _AuthFrame extends StatelessWidget {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B),
|
||||
color: AppColors.muted,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
@@ -324,7 +306,7 @@ class _AuthFrame extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -7,6 +7,10 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../app/injection_container.dart';
|
||||
import '../../core/errors/friendly_error.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_decorations.dart';
|
||||
import '../../core/theme/app_text_styles.dart';
|
||||
import '../../shared/widgets/animations/animations.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RegisterScreen
|
||||
@@ -69,7 +73,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
},
|
||||
onError: (message) => _snack(context, message),
|
||||
fallback: 'Registrasi gagal. Periksa data akun kamu.',
|
||||
connectionHint: 'Tidak bisa ke server. Pakai URL backend publik/aktif.',
|
||||
connectionHint:
|
||||
'Tidak bisa ke server. Cek URL backend di Connect Server dan pastikan server aktif.',
|
||||
);
|
||||
if (mounted) setState(() => _loading = false);
|
||||
}
|
||||
@@ -128,7 +133,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _role == 'USER'
|
||||
? const Color(0xFFEFF6FF)
|
||||
? AppColors.softBlueBg
|
||||
: const Color(0xFFF0FDF4),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
@@ -234,18 +239,19 @@ class _RoleCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
return BounceTap(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? const Color(0xFFEFF6FF) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
color: selected ? AppColors.softBlueBg : Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: selected ? const Color(0xFF1A56DB) : const Color(0xFFE2E8F0),
|
||||
color: selected ? AppColors.primaryBlue : AppColors.border,
|
||||
width: selected ? 2 : 1,
|
||||
),
|
||||
boxShadow: selected ? AppDecorations.cardShadow : null,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -253,10 +259,9 @@ class _RoleCard extends StatelessWidget {
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? const Color(0xFF1A56DB)
|
||||
: const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color:
|
||||
selected ? AppColors.primaryBlue : const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: Icon(icon,
|
||||
color: selected ? Colors.white : const Color(0xFF64748B)),
|
||||
@@ -267,16 +272,16 @@ class _RoleCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w800, fontSize: 16)),
|
||||
style: AppTextStyles.subheading.copyWith(fontSize: 16)),
|
||||
Text(subtitle,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B), fontSize: 13)),
|
||||
color: AppColors.muted, fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (selected)
|
||||
const Icon(Icons.check_circle_rounded, color: Color(0xFF1A56DB)),
|
||||
const Icon(Icons.check_circle_rounded,
|
||||
color: AppColors.primaryBlue),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -298,37 +303,25 @@ class _AuthFrame extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFEAF4FF),
|
||||
backgroundColor: AppColors.softBlueBg,
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final compact =
|
||||
constraints.maxWidth < 480 || constraints.maxHeight < 720;
|
||||
return Stack(
|
||||
children: [
|
||||
const Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFD9EEFF), Color(0xFFF7FAFC)],
|
||||
),
|
||||
),
|
||||
),
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
AppColors.softBlueBg,
|
||||
Colors.white,
|
||||
AppColors.softPinkBg,
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
top: compact ? -70 : -90,
|
||||
right: compact ? -70 : -60,
|
||||
child: Container(
|
||||
width: compact ? 180 : 260,
|
||||
height: compact ? 180 : 260,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2563EB).withValues(alpha: 0.12),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
keyboardDismissBehavior:
|
||||
ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
@@ -354,19 +347,10 @@ class _AuthFrame extends StatelessWidget {
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.96),
|
||||
color: AppColors.cardWhite,
|
||||
borderRadius:
|
||||
BorderRadius.circular(compact ? 22 : 30),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.8)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF1E3A8A)
|
||||
.withValues(alpha: 0.14),
|
||||
blurRadius: compact ? 24 : 40,
|
||||
offset: const Offset(0, 18),
|
||||
),
|
||||
],
|
||||
BorderRadius.circular(compact ? 22 : 28),
|
||||
boxShadow: AppDecorations.cardShadow,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
@@ -384,8 +368,15 @@ class _AuthFrame extends StatelessWidget {
|
||||
width: compact ? 44 : 56,
|
||||
height: compact ? 44 : 56,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1D4ED8),
|
||||
gradient: AppDecorations.blueGradient,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x334A90D9),
|
||||
blurRadius: 18,
|
||||
offset: Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(Icons.navigation_rounded,
|
||||
color: Colors.white,
|
||||
@@ -399,8 +390,8 @@ class _AuthFrame extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFF0F172A),
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -411,14 +402,10 @@ class _AuthFrame extends StatelessWidget {
|
||||
title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
?.copyWith(
|
||||
fontSize: compact ? 26 : null,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: const Color(0xFF0F172A),
|
||||
),
|
||||
style: AppTextStyles.heading.copyWith(
|
||||
fontSize: compact ? 26 : null,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
@@ -426,7 +413,7 @@ class _AuthFrame extends StatelessWidget {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B),
|
||||
color: AppColors.muted,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
@@ -441,7 +428,7 @@ class _AuthFrame extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../../core/services/incoming_call_polling_service.dart';
|
||||
import '../../core/services/offline_queue_service.dart';
|
||||
import '../../core/services/websocket_service.dart';
|
||||
import '../../core/storage/secure_storage.dart';
|
||||
import '../../shared/widgets/walkguide_loading_screen.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SplashScreen
|
||||
@@ -39,32 +40,35 @@ class SplashScreen extends StatefulWidget {
|
||||
|
||||
class _SplashScreenState extends State<SplashScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _animCtrl;
|
||||
late final Animation<double> _fadeAnim;
|
||||
late final AnimationController _screenCtrl;
|
||||
late final Animation<double> _screenFade;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animCtrl = AnimationController(
|
||||
_screenCtrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 700),
|
||||
duration: const Duration(milliseconds: 260),
|
||||
value: 1,
|
||||
);
|
||||
_screenFade = CurvedAnimation(
|
||||
parent: _screenCtrl,
|
||||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
_fadeAnim = CurvedAnimation(parent: _animCtrl, curve: Curves.easeIn);
|
||||
_animCtrl.forward();
|
||||
_route();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animCtrl.dispose();
|
||||
_screenCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _route() async {
|
||||
final routed = await runFriendlyAction(
|
||||
() async {
|
||||
// Animasi logo selalu tampil minimal 500ms agar tidak langsung flash.
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
// Animasi logo selalu tampil minimal 900ms agar tidak langsung flash.
|
||||
await Future.delayed(const Duration(milliseconds: 900));
|
||||
|
||||
final storage = sl<SecureStorage>();
|
||||
final token = await storage.getAccessToken().timeout(
|
||||
@@ -77,7 +81,7 @@ class _SplashScreenState extends State<SplashScreen>
|
||||
if (!mounted) return;
|
||||
|
||||
if (token == null || role == null) {
|
||||
context.go('/login');
|
||||
await _fadeOutThenGo('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,67 +92,28 @@ class _SplashScreenState extends State<SplashScreen>
|
||||
sl<IncomingCallPollingService>().start();
|
||||
}
|
||||
// Auto-login: arahkan ke home sesuai role.
|
||||
context.go(
|
||||
await _fadeOutThenGo(
|
||||
role == 'ROLE_GUARDIAN' ? '/guardian/dashboard' : '/user/walkguide',
|
||||
);
|
||||
},
|
||||
onError: (_) {},
|
||||
fallback: 'Sesi belum bisa dipulihkan.',
|
||||
);
|
||||
if (!routed && mounted) context.go('/login');
|
||||
if (!routed && mounted) await _fadeOutThenGo('/login');
|
||||
}
|
||||
|
||||
Future<void> _fadeOutThenGo(String route) async {
|
||||
if (!mounted) return;
|
||||
await _screenCtrl.reverse();
|
||||
if (mounted) context.go(route);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF1A56DB),
|
||||
body: Center(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnim,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Logo / icon
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.navigation_rounded,
|
||||
color: Colors.white,
|
||||
size: 60,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'WalkGuide',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 34,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'AI-powered navigation for everyone',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
const SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 2.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
return FadeTransition(
|
||||
opacity: _screenFade,
|
||||
child: const WalkGuideLoadingScreen(
|
||||
subtitle: 'Restoring your session',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user