add device info at login

This commit is contained in:
Efril 2026-05-12 02:00:38 +07:00
parent 9b6e9c591d
commit a4a12c6763
7 changed files with 159 additions and 6 deletions

View File

@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../common/utils/device_info_service.dart';
import '../../../common/utils/fcm_service.dart';
import '../../../domain/auth/auth.dart';
part 'login_form_event.dart';
@ -13,7 +15,11 @@ part 'login_form_bloc.freezed.dart';
@injectable
class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
final IAuthRepository _repository;
LoginFormBloc(this._repository) : super(LoginFormState.initial()) {
final DeviceInfoService _deviceInfoService;
final FcmService _fcmService;
LoginFormBloc(this._repository, this._deviceInfoService, this._fcmService)
: super(LoginFormState.initial()) {
on<LoginFormEvent>(_onLoginFormEvent);
}
@ -36,9 +42,25 @@ class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
final passwordValid = state.password.isNotEmpty;
if (emailValid && passwordValid) {
// Ambil device info dan FCM token secara paralel
final results = await Future.wait([
_deviceInfoService.getDeviceInfo(),
_fcmService.getToken(),
]);
final deviceInfo = results[0] as DeviceInfo;
final fcmToken = results[1] as String?;
failureOrAuth = await _repository.login(
email: state.email,
password: state.password,
deviceId: deviceInfo.deviceId,
deviceName: deviceInfo.deviceName,
deviceType: deviceInfo.deviceType,
platform: deviceInfo.platform,
osVersion: deviceInfo.osVersion,
appVersion: deviceInfo.appVersion,
fcmToken: fcmToken,
);
emit(
state.copyWith(

View File

@ -0,0 +1,82 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:injectable/injectable.dart';
import 'package:package_info_plus/package_info_plus.dart';
class DeviceInfo {
final String deviceId;
final String deviceName;
final String deviceType;
final String platform;
final String osVersion;
final String appVersion;
const DeviceInfo({
required this.deviceId,
required this.deviceName,
required this.deviceType,
required this.platform,
required this.osVersion,
required this.appVersion,
});
Map<String, dynamic> toJson() => {
'device_id': deviceId,
'device_name': deviceName,
'device_type': deviceType,
'platform': platform,
'os_version': osVersion,
'app_version': appVersion,
};
}
@lazySingleton
class DeviceInfoService {
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
Future<DeviceInfo> getDeviceInfo() async {
final packageInfo = await PackageInfo.fromPlatform();
final appVersion = packageInfo.version;
if (Platform.isAndroid) {
final info = await _deviceInfo.androidInfo;
return DeviceInfo(
deviceId: info.id,
deviceName: '${info.manufacturer} ${info.model}',
deviceType: _resolveDeviceType(info.model),
platform: 'android',
osVersion: 'Android ${info.version.release}',
appVersion: appVersion,
);
} else if (Platform.isIOS) {
final info = await _deviceInfo.iosInfo;
return DeviceInfo(
deviceId: info.identifierForVendor ?? '',
deviceName: info.name,
deviceType: _resolveDeviceType(info.model),
platform: 'ios',
osVersion: '${info.systemName} ${info.systemVersion}',
appVersion: appVersion,
);
}
// Fallback (web/desktop tidak dipakai tapi aman)
return DeviceInfo(
deviceId: 'unknown',
deviceName: 'unknown',
deviceType: 'desktop',
platform: 'web',
osVersion: 'unknown',
appVersion: appVersion,
);
}
/// Tentukan device_type berdasarkan model name.
/// Nilai valid: 'mobile' | 'tablet' | 'desktop'
String _resolveDeviceType(String model) {
final lower = model.toLowerCase();
if (lower.contains('ipad') || lower.contains('tablet')) return 'tablet';
return 'mobile';
}
}

View File

@ -4,6 +4,13 @@ abstract class IAuthRepository {
Future<Either<AuthFailure, Auth>> login({
required String email,
required String password,
required String deviceId,
required String deviceName,
required String deviceType,
required String platform,
required String osVersion,
required String appVersion,
String? fcmToken,
});
Future<bool> hasToken();
Future<Either<AuthFailure, User>> currentUser();

View File

@ -21,11 +21,28 @@ class AuthRemoteDataProvider {
Future<DC<AuthFailure, AuthDto>> login({
required String email,
required String password,
required String deviceId,
required String deviceName,
required String deviceType,
required String platform,
required String osVersion,
required String appVersion,
String? fcmToken,
}) async {
try {
final response = await _apiClient.post(
ApiPath.login,
data: {'email': email, 'password': password},
data: {
'email': email,
'password': password,
'device_id': deviceId,
'device_name': deviceName,
'device_type': deviceType,
'platform': platform,
'os_version': osVersion,
'app_version': appVersion,
if (fcmToken != null) 'fcm_token': fcmToken,
},
);
if (response.data['code'] == 401) {

View File

@ -21,11 +21,25 @@ class AuthRepository implements IAuthRepository {
Future<Either<AuthFailure, Auth>> login({
required String email,
required String password,
required String deviceId,
required String deviceName,
required String deviceType,
required String platform,
required String osVersion,
required String appVersion,
String? fcmToken,
}) async {
try {
final result = await _remoteDataProvider.login(
email: email,
password: password,
deviceId: deviceId,
deviceName: deviceName,
deviceType: deviceType,
platform: platform,
osVersion: osVersion,
appVersion: appVersion,
fcmToken: fcmToken,
);
if (result.hasError) {

View File

@ -59,6 +59,8 @@ import 'package:apskel_owner_flutter/common/di/di_shared_preferences.dart'
as _i402;
import 'package:apskel_owner_flutter/common/network/network_client.dart'
as _i543;
import 'package:apskel_owner_flutter/common/utils/device_info_service.dart'
as _i902;
import 'package:apskel_owner_flutter/common/utils/fcm_service.dart' as _i179;
import 'package:apskel_owner_flutter/domain/analytic/repositories/i_analytic_repository.dart'
as _i477;
@ -146,6 +148,7 @@ extension GetItInjectableX on _i174.GetIt {
preResolve: true,
);
gh.lazySingleton<_i179.FcmService>(() => _i179.FcmService());
gh.lazySingleton<_i902.DeviceInfoService>(() => _i902.DeviceInfoService());
gh.lazySingleton<_i543.NetworkClient>(
() => _i543.NetworkClient(gh<_i895.Connectivity>()),
);
@ -261,9 +264,6 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i945.AuthBloc>(
() => _i945.AuthBloc(gh<_i49.IAuthRepository>()),
);
gh.factory<_i775.LoginFormBloc>(
() => _i775.LoginFormBloc(gh<_i49.IAuthRepository>()),
);
gh.factory<_i574.LogoutFormBloc>(
() => _i574.LogoutFormBloc(gh<_i49.IAuthRepository>()),
);
@ -276,6 +276,13 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i147.UserEditFormBloc>(
() => _i147.UserEditFormBloc(gh<_i635.IUserRepository>()),
);
gh.factory<_i775.LoginFormBloc>(
() => _i775.LoginFormBloc(
gh<_i49.IAuthRepository>(),
gh<_i902.DeviceInfoService>(),
gh<_i179.FcmService>(),
),
);
gh.factory<_i346.InventoryReportBloc>(
() => _i346.InventoryReportBloc(
gh<_i477.IAnalyticRepository>(),

View File

@ -14,11 +14,15 @@ import 'package:flutter/widgets.dart';
class $AssetsImagesGen {
const $AssetsImagesGen();
/// File path: assets/images/ic_notification.png
AssetGenImage get icNotification =>
const AssetGenImage('assets/images/ic_notification.png');
/// File path: assets/images/logo.png
AssetGenImage get logo => const AssetGenImage('assets/images/logo.png');
/// List of all assets
List<AssetGenImage> get values => [logo];
List<AssetGenImage> get values => [icNotification, logo];
}
class Assets {