Add SimDataScreen

This commit is contained in:
anhtunz
2025-07-08 16:56:30 +07:00
parent e6c536f380
commit cdbd5b7484
14 changed files with 330 additions and 3 deletions

View File

@@ -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<List<Device>>.broadcast();
StreamSink<List<Device>> get sinkDevices => devices.sink;
Stream<List<Device>> get streamDevices => devices.stream;
@override
void dispose() {}
void getOwnerDevices(BuildContext context) async {
await apiServices.execute(context, () async {
List<Device> devices = [];
devices = await apiServices.getOwnerDevices();
List<Device> publicDevices = [];
for (var device in devices) {
if (device.visibility == "PUBLIC") {
publicDevices.add(device);
}
}
sinkDevices.add(publicDevices);
});
}
}

View File

@@ -92,6 +92,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
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<SettingsScreen> {
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);
}
},

View File

@@ -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)],
);
}
}
}

View File

@@ -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<SimDataScreen> createState() => _SimDataScreenState();
}
class _SimDataScreenState extends State<SimDataScreen> {
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(),
),
);
}
},
),
);
}
}

View File

@@ -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<MyApp> {
// // notificationServices.firebaseInit(context);
// // notificationServices.setupInteractMessage();
// notificationServices.getDeviceToken().then((token){
// print("Firebase Token: $token");
// log("Firebase Token: $token");
// sendNotificationToken(token);
// });
}

View File

@@ -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";

View File

@@ -13,4 +13,5 @@ enum AppRoutes {
HISTORY,
GROUPS,
GROUP_DETAIL,
SIM_DATA_SETTING,
}

View File

@@ -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),
),
],
);
}

View File

@@ -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 ",

View File

@@ -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:

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 ",

View File

@@ -36,7 +36,7 @@ class NotificationServices {
Future<String> getDeviceToken() async {
print("GET FB TOKEN");
String? token = await messaging.getAPNSToken();
String? token = await messaging.getToken();
print("GET FB: ${token}");
return token!;
}