301 lines
9.8 KiB
Dart
301 lines
9.8 KiB
Dart
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<void> 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<DriverLoginPage> createState() => _DriverLoginPageState();
|
|
}
|
|
|
|
class _DriverLoginPageState extends State<DriverLoginPage> {
|
|
final _phone = TextEditingController();
|
|
final _password = TextEditingController();
|
|
bool _busy = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_phone.dispose();
|
|
_password.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _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<void>(
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|