278 lines
9.6 KiB
Dart
278 lines
9.6 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 = 'pelanggan/login';
|
|
const String _kDial = '62';
|
|
|
|
Future<void> main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await Firebase.initializeApp();
|
|
runApp(const UserCloneApp());
|
|
}
|
|
|
|
class UserCloneApp extends StatelessWidget {
|
|
const UserCloneApp({super.key});
|
|
|
|
static const Color primary = Color(0xFF5BC8DA);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
debugShowCheckedModeBanner: false,
|
|
title: 'OnTime User Clone',
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(seedColor: primary),
|
|
scaffoldBackgroundColor: Colors.white,
|
|
useMaterial3: true,
|
|
),
|
|
home: const UserLoginPage(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class UserLoginPage extends StatefulWidget {
|
|
const UserLoginPage({super.key});
|
|
|
|
@override
|
|
State<UserLoginPage> createState() => _UserLoginPageState();
|
|
}
|
|
|
|
class _UserLoginPageState extends State<UserLoginPage> {
|
|
final _phone = TextEditingController();
|
|
final _password = TextEditingController();
|
|
bool _showPassword = false;
|
|
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 Customer'),
|
|
),
|
|
);
|
|
} 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: Column(
|
|
children: [
|
|
Container(
|
|
height: 290,
|
|
width: double.infinity,
|
|
color: const Color(0xFFE2F2FE),
|
|
child: const Center(
|
|
child: Icon(Icons.shopping_bag_outlined, size: 120, color: UserCloneApp.primary),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(15, 0, 15, 24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 12),
|
|
const Padding(
|
|
padding: EdgeInsets.only(left: 15),
|
|
child: Text('Log in dulu yuk!', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
|
),
|
|
const Padding(
|
|
padding: EdgeInsets.fromLTRB(15, 0, 15, 10),
|
|
child: Text(
|
|
'Nikmati belanja kebutuhan, makanan, dan service lainnya, langsung dari hapemu',
|
|
style: TextStyle(fontSize: 11),
|
|
),
|
|
),
|
|
const Padding(
|
|
padding: EdgeInsets.only(left: 15, top: 8),
|
|
child: Text('Nomor Telepon', style: TextStyle(fontSize: 11)),
|
|
),
|
|
const SizedBox(height: 10),
|
|
_PhoneBox(controller: _phone),
|
|
const Padding(
|
|
padding: EdgeInsets.only(left: 15, top: 8),
|
|
child: Text('Password', style: TextStyle(fontSize: 11)),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: const Color(0xFFC4C4C4)),
|
|
),
|
|
child: TextField(
|
|
controller: _password,
|
|
obscureText: !_showPassword,
|
|
decoration: InputDecoration(
|
|
hintText: 'Password',
|
|
border: InputBorder.none,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
suffixIcon: IconButton(
|
|
icon: Icon(_showPassword ? Icons.visibility_off : Icons.visibility),
|
|
onPressed: () => setState(() => _showPassword = !_showPassword),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Text('Lupa Password?', style: TextStyle(color: UserCloneApp.primary, fontSize: 14)),
|
|
const SizedBox(height: 12),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: _busy ? null : _signIn,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: UserCloneApp.primary,
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
),
|
|
child: _busy
|
|
? const SizedBox(
|
|
height: 22,
|
|
width: 22,
|
|
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
|
|
)
|
|
: const Text('Sign In'),
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
OutlinedButton(
|
|
onPressed: () {},
|
|
style: OutlinedButton.styleFrom(
|
|
minimumSize: const Size(double.infinity, 44),
|
|
foregroundColor: Colors.black,
|
|
side: const BorderSide(color: Colors.black26),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
),
|
|
child: const Text('Belum punya akun? Daftar dulu yuk'),
|
|
),
|
|
const SizedBox(height: 12),
|
|
const Center(
|
|
child: Text(
|
|
'Dengan masuk, Anda menyetujui kebijakan privasi.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 11),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Positioned(
|
|
top: 15,
|
|
left: 15,
|
|
child: SizedBox(width: 40, height: 40, child: Icon(Icons.arrow_back)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _PhoneBox extends StatelessWidget {
|
|
const _PhoneBox({required this.controller});
|
|
|
|
final TextEditingController controller;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: const Color(0xFFC4C4C4)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const SizedBox(width: 80, child: Center(child: Text('+62', style: TextStyle(color: UserCloneApp.primary)))),
|
|
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 _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')),
|
|
);
|
|
}
|
|
}
|