From cdbd5b7484d91bb7095fcd3f301ab4df2ad536bc Mon Sep 17 00:00:00 2001 From: anhtunz Date: Tue, 8 Jul 2025 16:56:30 +0700 Subject: [PATCH] Add SimDataScreen --- lib/bloc/sim_data_bloc.dart | 34 ++++ lib/feature/settings/settings_screen.dart | 10 +- .../sim_data/shared_sim_component.dart | 170 ++++++++++++++++++ .../settings/sim_data/sim_data_screen.dart | 55 ++++++ lib/main.dart | 5 +- lib/product/constant/app/app_constants.dart | 1 + .../constant/enums/app_route_enums.dart | 1 + .../navigation/navigation_router.dart | 13 ++ lib/product/lang/l10n/app_en.arb | 3 + lib/product/lang/l10n/app_localizations.dart | 18 ++ .../lang/l10n/app_localizations_en.dart | 9 + .../lang/l10n/app_localizations_vi.dart | 9 + lib/product/lang/l10n/app_vi.arb | 3 + .../services/notification_services.dart | 2 +- 14 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 lib/bloc/sim_data_bloc.dart create mode 100644 lib/feature/settings/sim_data/shared_sim_component.dart create mode 100644 lib/feature/settings/sim_data/sim_data_screen.dart diff --git a/lib/bloc/sim_data_bloc.dart b/lib/bloc/sim_data_bloc.dart new file mode 100644 index 0000000..4df8e31 --- /dev/null +++ b/lib/bloc/sim_data_bloc.dart @@ -0,0 +1,34 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; + +import '../product/services/api_services.dart'; +import '../feature/devices/device_model.dart'; +import '../product/base/bloc/base_bloc.dart'; + +class SimDataBloc extends BlocBase{ + + APIServices apiServices = APIServices(); + + final devices = StreamController>.broadcast(); + StreamSink> get sinkDevices => devices.sink; + Stream> get streamDevices => devices.stream; + + @override + void dispose() {} + + void getOwnerDevices(BuildContext context) async { + await apiServices.execute(context, () async { + List devices = []; + devices = await apiServices.getOwnerDevices(); + + List publicDevices = []; + for (var device in devices) { + if (device.visibility == "PUBLIC") { + publicDevices.add(device); + } + } + sinkDevices.add(publicDevices); + }); + } + +} \ No newline at end of file diff --git a/lib/feature/settings/settings_screen.dart b/lib/feature/settings/settings_screen.dart index 70fb175..60bbf49 100644 --- a/lib/feature/settings/settings_screen.dart +++ b/lib/feature/settings/settings_screen.dart @@ -92,6 +92,11 @@ class _SettingsScreenState extends State { appLocalization(context).profile_setting, userSnapshot.data ?? user), SizedBox(height: context.lowValue), + cardContent( + Icons.sim_card, + appLocalization(context).profile_sim_data, + userSnapshot.data ?? user), + SizedBox(height: context.lowValue), cardContent( Icons.logout_outlined, appLocalization(context).log_out, @@ -113,7 +118,10 @@ class _SettingsScreenState extends State { changeUserPassword(context, settingsBloc); } else if (icon == Icons.settings_outlined) { context.push(ApplicationConstants.DEVICE_NOTIFICATIONS_SETTINGS); - } else { + } else if(icon == Icons.sim_card){ + context.push(ApplicationConstants.SIM_DATA_SETTINGS); + } + else { await apiServices.logOut(context); } }, diff --git a/lib/feature/settings/sim_data/shared_sim_component.dart b/lib/feature/settings/sim_data/shared_sim_component.dart new file mode 100644 index 0000000..e8f2990 --- /dev/null +++ b/lib/feature/settings/sim_data/shared_sim_component.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../../devices/device_model.dart'; +import '../../../product/extension/context_extension.dart'; +import '../../../product/services/language_services.dart'; + +import '../../../product/utils/device_utils.dart'; + +class SharedSimComponent extends StatelessWidget { + + const SharedSimComponent({super.key, required this.device}); + final Device device; + + @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + double screenHeight = MediaQuery.of(context).size.height; + return Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: screenWidth, + height: screenHeight / 5.5, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + gradient: getGradientColor(getMonthLeft()) + ), + child: Padding( + padding: context.paddingLow, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + device.name ?? "name", + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + _buildStatusChip(context,device.state ?? -1), + ], + ), + SizedBox(height: context.lowValue), + // Time period + Text( + "${appLocalization(context).time_title}: ${convertStartTime()} - ${convertExpireTime()}", + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + SizedBox(height: context.lowValue), + _buildDurationDisplay(context), + ], + ), + ), + ), + ); + } + + Widget _buildStatusChip(BuildContext context,int state) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + border: Border.all(color: Colors.white), + borderRadius: BorderRadius.circular(15), + color: Colors.white, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.circle, + color: DeviceUtils.instance.getTableRowColor(context, state), + size: 12, + ), + const SizedBox(width: 5), + Text( + DeviceUtils.instance.checkStateDevice(context, state), + style: TextStyle( + color: DeviceUtils.instance.getTableRowColor(context, state), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } + + Widget _buildDurationDisplay(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + "${getMonthLeft()}", + style: const TextStyle( + fontSize: 40, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Text( + appLocalization(context).sim_data_month_left_message, + style: const TextStyle( + fontSize: 20, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + String convertStartTime(){ + return DateFormat('dd/MM/yyyy').format(device.createdAt!); + } + + String convertExpireTime() { + final expireDate = _calculateExpireDate(); + return DateFormat('dd/MM/yyyy').format(expireDate); + } + + int getMonthLeft() { + final expireDate = _calculateExpireDate(); + final now = DateTime.now(); + + int totalMonths = (expireDate.year - now.year) * 12 + (expireDate.month - now.month); + + if (expireDate.day < now.day) { + totalMonths -= 1; + } + + return totalMonths; + } + + DateTime _calculateExpireDate() { + return DateTime( + device.createdAt!.year + 3, + device.createdAt!.month, + device.createdAt!.day, + device.createdAt!.hour, + device.createdAt!.minute, + device.createdAt!.second, + device.createdAt!.millisecond, + device.createdAt!.microsecond, + ); + } + + LinearGradient getGradientColor(int monthLeft){ + if(monthLeft <= 3){ + return const LinearGradient( + colors: [Color(0xFFff4b1f), Color(0xFFff9068)], + ); + }else{ + return const LinearGradient( + colors: [Color(0xFF56ab2f), Color(0xFFa8e063)], + ); + } + } + +} diff --git a/lib/feature/settings/sim_data/sim_data_screen.dart b/lib/feature/settings/sim_data/sim_data_screen.dart new file mode 100644 index 0000000..ad334f5 --- /dev/null +++ b/lib/feature/settings/sim_data/sim_data_screen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:sfm_app/feature/settings/sim_data/shared_sim_component.dart'; + +import '../../../product/services/language_services.dart'; +import '../../../product/shared/shared_loading_animation.dart'; +import '../../../bloc/sim_data_bloc.dart'; +import '../../../product/base/bloc/base_bloc.dart'; + +class SimDataScreen extends StatefulWidget { + const SimDataScreen({super.key}); + @override + State createState() => _SimDataScreenState(); +} + +class _SimDataScreenState extends State { + late SimDataBloc simDataBloc; + @override + void initState() { + super.initState(); + simDataBloc = BlocProvider.of(context); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(appLocalization(context).profile_sim_data), + centerTitle: true, + ), + body: StreamBuilder( + stream: simDataBloc.streamDevices, + builder: (context, devicesSnapshot) { + if (devicesSnapshot.data == null) { + simDataBloc.getOwnerDevices(context); + return const SharedLoadingAnimation(); + } else if (devicesSnapshot.data!.isEmpty) { + return Center( + child: Text(appLocalization(context).main_no_data), + ); + } else { + return SafeArea( + child: Column( + children: devicesSnapshot.data!.map( + (device) { + return SharedSimComponent(device: device,); + }, + ).toList(), + ), + ); + } + }, + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 12aa178..f7b717d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; @@ -7,6 +9,7 @@ import 'firebase_options.dart'; import 'product/lang/l10n/app_localizations.dart'; import 'product/services/api_services.dart'; +import 'product/services/notification_services.dart'; import 'product/services/theme_services.dart'; import 'product/services/language_services.dart'; import 'bloc/main_bloc.dart'; @@ -146,7 +149,7 @@ class _MyAppState extends State { // // notificationServices.firebaseInit(context); // // notificationServices.setupInteractMessage(); // notificationServices.getDeviceToken().then((token){ - // print("Firebase Token: $token"); + // log("Firebase Token: $token"); // sendNotificationToken(token); // }); } diff --git a/lib/product/constant/app/app_constants.dart b/lib/product/constant/app/app_constants.dart index 5a8373a..e2784ab 100644 --- a/lib/product/constant/app/app_constants.dart +++ b/lib/product/constant/app/app_constants.dart @@ -21,6 +21,7 @@ class ApplicationConstants { static const DEVICE_LOGS_PATH = "/device-logs"; static const GROUP_PATH = "/groups"; static const DEVICE_NOTIFICATIONS_SETTINGS = "/device-notifications-settings"; + static const SIM_DATA_SETTINGS = "/sim-data-settings"; static const OWNER_GROUP = "owner"; static const PARTICIPANT_GROUP = "participant"; static const NO_DATA = "no_data"; diff --git a/lib/product/constant/enums/app_route_enums.dart b/lib/product/constant/enums/app_route_enums.dart index 0f3712a..5f74c28 100644 --- a/lib/product/constant/enums/app_route_enums.dart +++ b/lib/product/constant/enums/app_route_enums.dart @@ -13,4 +13,5 @@ enum AppRoutes { HISTORY, GROUPS, GROUP_DETAIL, + SIM_DATA_SETTING, } diff --git a/lib/product/constant/navigation/navigation_router.dart b/lib/product/constant/navigation/navigation_router.dart index 0838db6..5aea73f 100644 --- a/lib/product/constant/navigation/navigation_router.dart +++ b/lib/product/constant/navigation/navigation_router.dart @@ -1,4 +1,7 @@ import 'package:go_router/go_router.dart'; + +import '../../../bloc/sim_data_bloc.dart'; +import '../../../feature/settings/sim_data/sim_data_screen.dart'; import '../../../bloc/device_detail_bloc.dart'; import '../../../feature/devices/device_detail/device_detail_screen.dart'; import '../../../bloc/device_notification_settings_bloc.dart'; @@ -151,6 +154,16 @@ GoRouter goRouter() { ), transitionsBuilder: transitionsRightToLeft), ), + GoRoute( + path: ApplicationConstants.SIM_DATA_SETTINGS, + name: AppRoutes.SIM_DATA_SETTING.name, + pageBuilder: (context, state) => CustomTransitionPage( + child: BlocProvider( + child: const SimDataScreen(), + blocBuilder: () => SimDataBloc(), + ), + transitionsBuilder: transitionsRightToLeft), + ), ], ); } diff --git a/lib/product/lang/l10n/app_en.arb b/lib/product/lang/l10n/app_en.arb index c49a784..0824128 100644 --- a/lib/product/lang/l10n/app_en.arb +++ b/lib/product/lang/l10n/app_en.arb @@ -111,7 +111,10 @@ "profile_page_title": "Settings Page", "profile_change_info": "Change information", "profile_change_pass": "Change password", + "profile_sim_data": "Device SIM information", "profile_setting": "Notification Setting", + "sim_data_month_left_message": "months left", + "time_title": "Time", "change_profile_title": "Personal information", "change_profile_username": "Username: ", "change_profile_username_hint": "Enter username ", diff --git a/lib/product/lang/l10n/app_localizations.dart b/lib/product/lang/l10n/app_localizations.dart index f53b39d..b93f20c 100644 --- a/lib/product/lang/l10n/app_localizations.dart +++ b/lib/product/lang/l10n/app_localizations.dart @@ -770,12 +770,30 @@ abstract class AppLocalizations { /// **'Change password'** String get profile_change_pass; + /// No description provided for @profile_sim_data. + /// + /// In en, this message translates to: + /// **'Device SIM information'** + String get profile_sim_data; + /// No description provided for @profile_setting. /// /// In en, this message translates to: /// **'Notification Setting'** String get profile_setting; + /// No description provided for @sim_data_month_left_message. + /// + /// In en, this message translates to: + /// **'months left'** + String get sim_data_month_left_message; + + /// No description provided for @time_title. + /// + /// In en, this message translates to: + /// **'Time'** + String get time_title; + /// No description provided for @change_profile_title. /// /// In en, this message translates to: diff --git a/lib/product/lang/l10n/app_localizations_en.dart b/lib/product/lang/l10n/app_localizations_en.dart index 3b9eccf..9eedc94 100644 --- a/lib/product/lang/l10n/app_localizations_en.dart +++ b/lib/product/lang/l10n/app_localizations_en.dart @@ -360,9 +360,18 @@ class AppLocalizationsEn extends AppLocalizations { @override String get profile_change_pass => 'Change password'; + @override + String get profile_sim_data => 'Device SIM information'; + @override String get profile_setting => 'Notification Setting'; + @override + String get sim_data_month_left_message => 'months left'; + + @override + String get time_title => 'Time'; + @override String get change_profile_title => 'Personal information'; diff --git a/lib/product/lang/l10n/app_localizations_vi.dart b/lib/product/lang/l10n/app_localizations_vi.dart index aad4525..7deaf16 100644 --- a/lib/product/lang/l10n/app_localizations_vi.dart +++ b/lib/product/lang/l10n/app_localizations_vi.dart @@ -356,9 +356,18 @@ class AppLocalizationsVi extends AppLocalizations { @override String get profile_change_pass => 'Đổi mật khẩu'; + @override + String get profile_sim_data => 'Thông tin sim thiết bị'; + @override String get profile_setting => 'Cài đặt thông báo'; + @override + String get sim_data_month_left_message => 'tháng còn lại'; + + @override + String get time_title => 'Thời gian'; + @override String get change_profile_title => 'Thông tin người dùng'; diff --git a/lib/product/lang/l10n/app_vi.arb b/lib/product/lang/l10n/app_vi.arb index 98b0336..52abcad 100644 --- a/lib/product/lang/l10n/app_vi.arb +++ b/lib/product/lang/l10n/app_vi.arb @@ -112,6 +112,9 @@ "profile_change_info": "Đổi thông tin cá nhân", "profile_change_pass": "Đổi mật khẩu", "profile_setting": "Cài đặt thông báo", + "profile_sim_data": "Thông tin sim thiết bị", + "sim_data_month_left_message": "tháng còn lại", + "time_title": "Thời gian", "change_profile_title": "Thông tin người dùng", "change_profile_username": "Tên người dùng: ", "change_profile_username_hint": "Nhập tên ", diff --git a/lib/product/services/notification_services.dart b/lib/product/services/notification_services.dart index 919cd72..23d28d5 100644 --- a/lib/product/services/notification_services.dart +++ b/lib/product/services/notification_services.dart @@ -36,7 +36,7 @@ class NotificationServices { Future getDeviceToken() async { print("GET FB TOKEN"); - String? token = await messaging.getAPNSToken(); + String? token = await messaging.getToken(); print("GET FB: ${token}"); return token!; }