add flutter

This commit is contained in:
Ariska
2026-03-11 15:29:37 +07:00
parent c253e1a370
commit 619d758027
9490 changed files with 135801 additions and 1353 deletions
@@ -0,0 +1,77 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:ontime_merchant_flutter/data/api/api_client.dart';
import 'package:ontime_merchant_flutter/features/auth/data/merchant_auth_api.dart';
import 'package:ontime_merchant_flutter/features/auth/data/models/merchant_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthState {
const AuthState({
this.merchant,
this.isLoading = false,
this.errorMessage,
});
final MerchantModel? merchant;
final bool isLoading;
final String? errorMessage;
AuthState copyWith({
MerchantModel? merchant,
bool? isLoading,
String? errorMessage,
}) {
return AuthState(
merchant: merchant ?? this.merchant,
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage,
);
}
}
final authControllerProvider =
StateNotifierProvider<AuthController, AuthState>((ref) => AuthController());
class AuthController extends StateNotifier<AuthState> {
AuthController() : super(const AuthState());
late final MerchantAuthApi _api =
MerchantAuthApi(ApiClient(basicAuthUser: null, basicAuthPassword: null));
Future<void> login({
required String noTelepon,
required String password,
String? fcmToken,
}) async {
state = state.copyWith(isLoading: true, errorMessage: null);
try {
final merchant = await _api.login(
noTelepon: noTelepon,
password: password,
fcmToken: fcmToken,
);
state = state.copyWith(merchant: merchant, isLoading: false);
final prefs = await SharedPreferences.getInstance();
await prefs.setString('merchant_id_mitra', merchant.idMitra);
await prefs.setString('merchant_id_merchant', merchant.idMerchant);
await prefs.setString('merchant_phone', merchant.teleponMitra);
await prefs.setString('merchant_token', merchant.tokenMerchant);
} catch (e) {
state = state.copyWith(
isLoading: false,
errorMessage: e.toString().replaceFirst('Exception: ', ''),
);
}
}
void logout() {
state = const AuthState();
}
static Future<void> clearStored() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('merchant_id_mitra');
await prefs.remove('merchant_id_merchant');
await prefs.remove('merchant_phone');
await prefs.remove('merchant_token');
}
}
@@ -0,0 +1,35 @@
import 'package:dio/dio.dart';
import 'package:ontime_merchant_flutter/data/api/api_client.dart';
import 'package:ontime_merchant_flutter/features/auth/data/models/merchant_model.dart';
class MerchantAuthApi {
MerchantAuthApi(this._client);
final ApiClient _client;
Future<MerchantModel> login({
required String noTelepon,
required String password,
String? fcmToken,
}) async {
final payload = <String, dynamic>{
'no_telepon': noTelepon,
'password': password,
};
if (fcmToken != null && fcmToken.isNotEmpty) payload['token'] = fcmToken;
final Response<dynamic> response =
await _client.raw.post('Merchant/login', data: payload);
final data = response.data is Map<String, dynamic>
? response.data as Map<String, dynamic>
: <String, dynamic>{};
if (data['message'] == 'banned') throw Exception('banned');
if (data['code']?.toString() != '200') {
throw Exception(data['message'] ?? 'Login failed');
}
final list = data['data'] as List<dynamic>? ?? [];
if (list.isEmpty) throw Exception('Merchant data not found');
return MerchantModel.fromJson(list.first as Map<String, dynamic>);
}
}
@@ -0,0 +1,37 @@
import 'package:json_annotation/json_annotation.dart';
part 'merchant_model.g.dart';
@JsonSerializable()
class MerchantModel {
MerchantModel({
required this.idMitra,
required this.idMerchant,
required this.teleponMitra,
required this.tokenMerchant,
this.namaMerchant,
this.emailMitra,
});
@JsonKey(name: 'id_mitra')
final String idMitra;
@JsonKey(name: 'id_merchant')
final String idMerchant;
@JsonKey(name: 'telepon_mitra')
final String teleponMitra;
@JsonKey(name: 'token_merchant')
final String tokenMerchant;
@JsonKey(name: 'nama_merchant')
final String? namaMerchant;
@JsonKey(name: 'email_mitra')
final String? emailMitra;
factory MerchantModel.fromJson(Map<String, dynamic> json) =>
_$MerchantModelFromJson(json);
Map<String, dynamic> toJson() => _$MerchantModelToJson(this);
}
@@ -0,0 +1,22 @@
part of 'merchant_model.dart';
MerchantModel _$MerchantModelFromJson(Map<String, dynamic> json) {
return MerchantModel(
idMitra: json['id_mitra'] as String? ?? '',
idMerchant: json['id_merchant'] as String? ?? '',
teleponMitra: json['telepon_mitra'] as String? ?? '',
tokenMerchant: json['token_merchant'] as String? ?? '',
namaMerchant: json['nama_merchant'] as String?,
emailMitra: json['email_mitra'] as String?,
);
}
Map<String, dynamic> _$MerchantModelToJson(MerchantModel instance) =>
<String, dynamic>{
'id_mitra': instance.idMitra,
'id_merchant': instance.idMerchant,
'telepon_mitra': instance.teleponMitra,
'token_merchant': instance.tokenMerchant,
'nama_merchant': instance.namaMerchant,
'email_mitra': instance.emailMitra,
};
@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:ontime_merchant_flutter/core/fcm_service.dart';
import 'package:ontime_merchant_flutter/features/auth/application/auth_controller.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _phoneController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_phoneController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _submit() async {
if (!_formKey.currentState!.validate()) return;
final fcmToken = await FcmService.getToken();
final auth = ref.read(authControllerProvider.notifier);
await auth.login(
noTelepon: _phoneController.text.trim(),
password: _passwordController.text,
fcmToken: fcmToken,
);
final state = ref.read(authControllerProvider);
if (state.merchant != null && mounted) context.go('/home');
}
@override
Widget build(BuildContext context) {
final state = ref.watch(authControllerProvider);
return Scaffold(
appBar: AppBar(title: const Text('Masuk Merchant')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _phoneController,
decoration: const InputDecoration(labelText: 'No. Telepon'),
keyboardType: TextInputType.phone,
validator: (v) =>
(v == null || v.isEmpty) ? 'No. telepon wajib diisi' : null,
),
const SizedBox(height: 12),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Kata sandi'),
obscureText: true,
validator: (v) =>
(v == null || v.isEmpty) ? 'Password wajib diisi' : null,
),
if (state.errorMessage != null) ...[
const SizedBox(height: 8),
Text(
state.errorMessage!,
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
],
const Spacer(),
ElevatedButton(
onPressed: state.isLoading ? null : _submit,
child: state.isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Masuk'),
),
],
),
),
),
),
);
}
}