From a4a12c67630ac0a96c0fbba0e621ab5a7cfce8e6 Mon Sep 17 00:00:00 2001 From: Efril Date: Tue, 12 May 2026 02:00:38 +0700 Subject: [PATCH] add device info at login --- .../auth/login_form/login_form_bloc.dart | 24 +++++- lib/common/utils/device_info_service.dart | 82 +++++++++++++++++++ .../auth/repositories/i_auth_repository.dart | 7 ++ .../datasources/remote_data_provider.dart | 19 ++++- .../auth/repositories/auth_repository.dart | 14 ++++ lib/injection.config.dart | 13 ++- .../components/assets/assets.gen.dart | 6 +- 7 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 lib/common/utils/device_info_service.dart diff --git a/lib/application/auth/login_form/login_form_bloc.dart b/lib/application/auth/login_form/login_form_bloc.dart index abded69..e7e0b25 100644 --- a/lib/application/auth/login_form/login_form_bloc.dart +++ b/lib/application/auth/login_form/login_form_bloc.dart @@ -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 { 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(_onLoginFormEvent); } @@ -36,9 +42,25 @@ class LoginFormBloc extends Bloc { 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( diff --git a/lib/common/utils/device_info_service.dart b/lib/common/utils/device_info_service.dart new file mode 100644 index 0000000..0791a03 --- /dev/null +++ b/lib/common/utils/device_info_service.dart @@ -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 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 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'; + } +} diff --git a/lib/domain/auth/repositories/i_auth_repository.dart b/lib/domain/auth/repositories/i_auth_repository.dart index 4bb8594..5cfce76 100644 --- a/lib/domain/auth/repositories/i_auth_repository.dart +++ b/lib/domain/auth/repositories/i_auth_repository.dart @@ -4,6 +4,13 @@ abstract class IAuthRepository { Future> 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 hasToken(); Future> currentUser(); diff --git a/lib/infrastructure/auth/datasources/remote_data_provider.dart b/lib/infrastructure/auth/datasources/remote_data_provider.dart index d81bd78..70c0cc1 100644 --- a/lib/infrastructure/auth/datasources/remote_data_provider.dart +++ b/lib/infrastructure/auth/datasources/remote_data_provider.dart @@ -21,11 +21,28 @@ class AuthRemoteDataProvider { Future> 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) { diff --git a/lib/infrastructure/auth/repositories/auth_repository.dart b/lib/infrastructure/auth/repositories/auth_repository.dart index 3d0f8df..7e1e12d 100644 --- a/lib/infrastructure/auth/repositories/auth_repository.dart +++ b/lib/infrastructure/auth/repositories/auth_repository.dart @@ -21,11 +21,25 @@ class AuthRepository implements IAuthRepository { Future> 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) { diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 40b1d03..4c8628d 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -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>(), diff --git a/lib/presentation/components/assets/assets.gen.dart b/lib/presentation/components/assets/assets.gen.dart index c528e32..d2a149c 100644 --- a/lib/presentation/components/assets/assets.gen.dart +++ b/lib/presentation/components/assets/assets.gen.dart @@ -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 get values => [logo]; + List get values => [icNotification, logo]; } class Assets {