import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'ontime_auth.dart'; const String _kLoginPath = 'driver/login'; const String _kDial = '62'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(const DriverCloneApp()); } class DriverCloneApp extends StatelessWidget { const DriverCloneApp({super.key}); static const Color primary = Color(0xFF5BC8DA); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'OnTime Driver Clone', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: primary), scaffoldBackgroundColor: Colors.white, useMaterial3: true, ), home: const DriverLoginPage(), ); } } class DriverLoginPage extends StatefulWidget { const DriverLoginPage({super.key}); @override State createState() => _DriverLoginPageState(); } class _DriverLoginPageState extends State { final _phone = TextEditingController(); final _password = TextEditingController(); bool _busy = false; @override void dispose() { _phone.dispose(); _password.dispose(); super.dispose(); } Future _signIn() async { final pass = _password.text; final digits = _phone.text.trim(); if (pass.isEmpty) { _toast('Isi password'); return; } if (digits.isEmpty) { _toast('Isi nomor telepon'); return; } setState(() => _busy = true); try { await FirebaseMessaging.instance.requestPermission(); final token = await FirebaseMessaging.instance.getToken(); if (token == null || token.length < 50) { _toast( 'Token FCM belum siap. Izinkan notifikasi & pastikan google-services.json / API key Firebase valid.', ); return; } final result = await OntimeAuth.login( path: _kLoginPath, password: pass, phoneDigits: digits, dialCodeWithoutPlus: _kDial, fcmToken: token, ); if (!mounted) return; if (result.success) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => const _HomePlaceholder(title: 'OnTime Driver'), ), ); } else { _toast(result.message); } } catch (e) { if (mounted) _toast('$e'); } finally { if (mounted) setState(() => _busy = false); } } void _toast(String msg) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Stack( children: [ SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(bottom: 92), child: Column( children: [ const SizedBox(height: 20), const SizedBox( height: 250, child: Center( child: Icon(Icons.local_shipping, size: 120, color: DriverCloneApp.primary), ), ), Container( margin: const EdgeInsets.fromLTRB(15, 0, 15, 15), padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: const [BoxShadow(color: Color(0x22000000), blurRadius: 8, offset: Offset(0, 4))], ), child: Column( children: [ const SizedBox(height: 20), _PhoneField(controller: _phone), const SizedBox(height: 10), _PasswordField(controller: _password), const SizedBox(height: 20), const Align( alignment: Alignment.center, child: Text('Lupa Password?', style: TextStyle(color: DriverCloneApp.primary, fontSize: 14)), ), const SizedBox(height: 10), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _busy ? null : _signIn, style: ElevatedButton.styleFrom( backgroundColor: DriverCloneApp.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), child: _busy ? const SizedBox( height: 22, width: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Text('Sign In', style: TextStyle(fontSize: 18)), ), ), const SizedBox(height: 16), const Text( 'Dengan masuk, Anda menyetujui kebijakan privasi.', textAlign: TextAlign.center, style: TextStyle(fontSize: 13), ), ], ), ), ], ), ), ), Positioned( top: 15, left: 15, child: Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all(color: const Color(0xFFDDDDDD)), ), child: const Icon(Icons.arrow_back, size: 20), ), ), Positioned( left: 0, right: 0, bottom: 0, child: Container( height: 80, color: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Belum punya akun? ', style: TextStyle(fontSize: 15)), Text('Sign Up', style: TextStyle(fontSize: 18, color: DriverCloneApp.primary, fontWeight: FontWeight.w600)), ], ), SizedBox(height: 8), Text( 'Note: Jika sudah mendaftar akun baru, tunggu konfirmasi admin untuk info lebih lanjut.', textAlign: TextAlign.center, style: TextStyle(fontSize: 11), ), ], ), ), ), ], ), ), ); } } class _HomePlaceholder extends StatelessWidget { const _HomePlaceholder({required this.title}); final String title; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: const Center(child: Text('Login berhasil')), ); } } class _PhoneField extends StatelessWidget { const _PhoneField({required this.controller}); final TextEditingController controller; @override Widget build(BuildContext context) { return Container( height: 50, decoration: BoxDecoration(border: Border.all(color: const Color(0xFFC4C4C4)), borderRadius: BorderRadius.circular(8)), child: Row( children: [ const SizedBox(width: 80, child: Center(child: Text('+62', style: TextStyle(color: DriverCloneApp.primary, fontSize: 18)))), Container(width: 1, margin: const EdgeInsets.symmetric(vertical: 6), color: const Color(0xFFC4C4C4)), Expanded( child: TextField( controller: controller, keyboardType: TextInputType.phone, decoration: const InputDecoration( hintText: 'Nomor Telepon', border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), ], ), ); } } class _PasswordField extends StatelessWidget { const _PasswordField({required this.controller}); final TextEditingController controller; @override Widget build(BuildContext context) { return Container( height: 50, decoration: BoxDecoration(border: Border.all(color: const Color(0xFFC4C4C4)), borderRadius: BorderRadius.circular(8)), child: Row( children: [ const SizedBox(width: 80, child: Icon(Icons.lock_outline, color: DriverCloneApp.primary)), const VerticalDivider(width: 1, thickness: 1, indent: 6, endIndent: 6), Expanded( child: TextField( controller: controller, obscureText: true, decoration: const InputDecoration( hintText: 'Password', border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), ], ), ); } }