chore(group): fix delay show group when switch and convert to relative import

This commit is contained in:
Tran Anh Tuan
2025-10-23 11:08:03 +07:00
parent cfebf74515
commit 9bff11a0b1
44 changed files with 357 additions and 380 deletions

View File

@@ -7,6 +7,10 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
use_build_context_synchronously: ignore
avoid_print: ignore
include: package:flutter_lints/flutter.yaml
linter:

View File

@@ -1,5 +1,4 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
@@ -9,7 +8,6 @@ import '../product/services/api_services.dart';
import '../product/utils/date_time_utils.dart';
import '../feature/device_log/device_logs_model.dart';
import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/utils/device_utils.dart';

View File

@@ -6,7 +6,6 @@ import '../product/base/bloc/base_bloc.dart';
import '../product/constant/app/app_constants.dart';
import '../product/services/api_services.dart';
import '../product/utils/date_time_utils.dart';
import '../product/utils/device_utils.dart';
import '../feature/device_log/device_logs_model.dart';

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../feature/settings/device_notification_settings/device_notification_settings_model.dart';

View File

@@ -2,7 +2,6 @@
import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intl/intl.dart';

View File

@@ -1,8 +1,8 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/widgets.dart';
import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/services/api_services.dart';

View File

@@ -1,8 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/extension/context_extension.dart';
import '../product/extension/context_extension.dart';
import '../product/services/api_services.dart';
import '../feature/home/device_alias_model.dart';
import '../product/base/bloc/base_bloc.dart';
@@ -65,7 +64,6 @@ class HomeBloc extends BlocBase {
void getOwnerDeviceState(BuildContext context,List<DeviceWithAlias> allDevices) async {
// int notificationCount = 0;
Map<String, List<DeviceWithAlias>> ownerDevicesStatus = {};
List<String> ownerDevicesState = [];
if (!context.mounted) return;
sinkOwnerDevicesStatus.add(ownerDevicesStatus);

View File

@@ -1,7 +1,6 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import '../product/constant/app/app_constants.dart';

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../product/services/language_services.dart';

View File

@@ -1,6 +1,7 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../product/shared/shared_component_loading_animation.dart';
import '../../product/shared/shared_loading_animation.dart';
@@ -14,7 +15,6 @@ import '../../product/services/language_services.dart';
import '../../product/shared/shared_snack_bar.dart';
import '../../product/utils/date_time_utils.dart';
import '../../product/utils/device_utils.dart';
import '../../product/base/bloc/base_bloc.dart';
import 'device_logs_model.dart';

View File

@@ -1,6 +1,7 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import '../../bloc/devices_manager_bloc.dart';
import '../../product/constant/enums/role_enums.dart';
import '../../product/services/api_services.dart';

View File

@@ -1,10 +1,9 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:sfm_app/product/shared/shared_component_loading_animation.dart';
import 'package:simple_ripple_animation/simple_ripple_animation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../product/shared/shared_component_loading_animation.dart';
import '../../../product/constant/image/image_constants.dart';
import '../../../product/shared/shared_line_chart.dart';
import '../../../product/shared/shared_curve.dart';
@@ -15,7 +14,6 @@ import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../bloc/device_detail_bloc.dart';

View File

@@ -9,7 +9,6 @@ import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/api_services.dart';
import '../../../product/services/language_services.dart';
import '../../../product/shared/model/district_model.dart';
import '../../../product/shared/model/province_model.dart';
import '../../../product/shared/model/ward_model.dart';

View File

@@ -2,15 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' ;
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../bloc/device_update_bloc.dart';
import '../../../product/constant/app/app_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../product/shared/find_location_maps/shared_map_search_location.dart';
import '../../../product/shared/find_location_maps/model/prediction_model.dart';
import '../../../product/shared/shared_transition.dart';
import 'geocode_model.dart';
@@ -64,7 +63,8 @@ showMapDialog(
String latitude = mapDialogLatitudeController.text;
String longitude = mapDialogLongitudeController.text;
log("Finish -- Latitude: $latitude, longitude: $longitude --");
getDataFromApi(context,latitude, longitude, deviceUpdateBloc);
getDataFromApi(
context, latitude, longitude, deviceUpdateBloc);
latitudeController.text =
mapDialogLatitudeController.text;
longitudeController.text =
@@ -184,7 +184,7 @@ addMarker(
updateCameraPosition(position, 14, mapController);
}
void getDataFromApi(BuildContext context,String latitude, String longitude,
void getDataFromApi(BuildContext context, String latitude, String longitude,
DeviceUpdateBloc deviceUpdateBloc) async {
String path =
"maps/api/geocode/json?latlng=$latitude,$longitude&language=vi&result_type=political&key=${ApplicationConstants.MAP_KEY}";
@@ -215,7 +215,7 @@ void getDataFromApi(BuildContext context,String latitude, String longitude,
log("$key: $value");
});
await _processLocations(context,locations, deviceUpdateBloc);
await _processLocations(context, locations, deviceUpdateBloc);
}
Map<String, String> _extractLocationComponents(
@@ -248,24 +248,24 @@ Future<void> _processLocations(BuildContext context,
String wardNameFromAPI = locations['wardkey'] ?? "";
final province =
await deviceUpdateBloc.getProvinceByName(context,provinceNameFromAPI);
await deviceUpdateBloc.getProvinceByName(context, provinceNameFromAPI);
if (province.name != "null") {
log("Province: ${province.fullName}, ProvinceCode: ${province.code}");
deviceUpdateBloc.sinkProvinceData
.add({"code": province.code!, "name": province.fullName!});
deviceUpdateBloc.getAllProvinces(context);
final district = await deviceUpdateBloc.getDistrictByName(context,
districtNameFromAPI, province.code!);
final district = await deviceUpdateBloc.getDistrictByName(
context, districtNameFromAPI, province.code!);
log("Districtname: ${district.fullName}, districtCode: ${district.code}");
deviceUpdateBloc.getAllDistricts(context,province.code!);
deviceUpdateBloc.getAllDistricts(context, province.code!);
if (district.name != "null") {
deviceUpdateBloc.sinkDistrictData
.add({"code": district.code!, "name": district.fullName!});
final ward =
await deviceUpdateBloc.getWardByName(context,wardNameFromAPI, district.code!);
final ward = await deviceUpdateBloc.getWardByName(
context, wardNameFromAPI, district.code!);
log("Wardname: ${ward.fullName}, WardCode: ${ward.code}");
deviceUpdateBloc.getAllWards(context,district.code!);
deviceUpdateBloc.getAllWards(context, district.code!);
if (ward.name != "null") {
log("Xac dinh duoc het thong tin tu toa do");
deviceUpdateBloc.sinkWardData

View File

@@ -1,9 +1,9 @@
import 'dart:async';
import 'package:data_table_2/data_table_2.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../product/shared/shared_component_loading_animation.dart';
import '../../product/shared/shared_loading_animation.dart';
import 'add_new_device_widget.dart';

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:sfm_app/product/constant/enums/app_route_enums.dart';
import 'package:sfm_app/product/constant/image/image_constants.dart';
import '../../product/constant/enums/app_route_enums.dart';
import '../../product/constant/image/image_constants.dart';
class NotFoundScreen extends StatelessWidget {
const NotFoundScreen({super.key});

View File

@@ -3,13 +3,13 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../../product/shared/shared_rocket_container.dart';
import '../../../product/constant/enums/app_route_enums.dart';
import '../../../product/constant/image/image_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../device_alias_model.dart';

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'status_card.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../product/extension/context_extension.dart';
class StatusCard extends StatelessWidget {

View File

@@ -2,11 +2,11 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import '../../../product/extension/context_extension.dart';
import '../../../bloc/group_detail_bloc.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/services/language_services.dart';
import '../../devices/device_model.dart';
import 'group_detail_model.dart';

View File

@@ -1,8 +1,8 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../bloc/group_detail_bloc.dart';
import '../../../product/shared/shared_loading_animation.dart';
import 'group_detail_model.dart';
@@ -14,7 +14,6 @@ import '../../../product/services/api_services.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../../product/utils/response_status_utils.dart';
import 'add_device_to_group_dialog.dart';
class DetailGroupScreen extends StatefulWidget {

View File

@@ -1,187 +1,125 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../product/constant/enums/app_route_enums.dart';
import '../../../product/shared/shared_component_loading_animation.dart';
import '../../../product/shared/shared_loading_animation.dart';
import 'groups_model.dart';
import 'groups_widget.dart';
import '../../../bloc/inter_family_bloc.dart';
import '../inter_family_widget.dart';
import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/constant/app/app_constants.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import 'groups_widget.dart';
class GroupsScreen extends StatefulWidget {
const GroupsScreen({super.key, required this.role});
/// Stateless widget that renders a provided list of groups. The parent
/// screen owns fetching/updating the list; this widget only displays it and
/// forwards actions to the provided [InterFamilyBloc].
class GroupsScreen extends StatelessWidget {
const GroupsScreen(
{super.key,
required this.role,
required this.groups,
required this.interFamilyBloc});
final String role;
@override
State<GroupsScreen> createState() => _GroupsScreenState();
}
class _GroupsScreenState extends State<GroupsScreen> {
late InterFamilyBloc interFamilyBloc;
Timer? getAllGroupsTimer;
@override
void initState() {
super.initState();
interFamilyBloc = BlocProvider.of(context);
const duration = Duration(seconds: 5);
getAllGroupsTimer = Timer.periodic(
duration,
(Timer t) => interFamilyBloc.getAllGroup(context, widget.role),
);
}
@override
void dispose() {
getAllGroupsTimer?.cancel();
super.dispose();
}
final List<Group> groups;
final InterFamilyBloc interFamilyBloc;
@override
Widget build(BuildContext context) {
if (widget.role == ApplicationConstants.OWNER_GROUP ||
widget.role == ApplicationConstants.PARTICIPANT_GROUP) {
return StreamBuilder<List<Group>>(
stream: interFamilyBloc.streamCurrentGroups,
builder: (context, groupsSnapshot) {
if (groupsSnapshot.data == null) {
interFamilyBloc.getAllGroup(context,widget.role);
return const SharedLoadingAnimation();
} else if (groupsSnapshot.data!.isEmpty) {
return Center(
child: Text(appLocalization(context).dont_have_group),
);
} else {
return Scaffold(
body: groupsSnapshot.data?.isEmpty ?? true
? const SharedComponentLoadingAnimation()
: ListView.builder(
itemCount: groupsSnapshot.data!.length,
if (role != ApplicationConstants.OWNER_GROUP &&
role != ApplicationConstants.PARTICIPANT_GROUP) {
return const SizedBox.shrink();
}
if (groups.isEmpty) {
return Center(child: Text(appLocalization(context).dont_have_group));
}
return ListView.builder(
itemCount: groups.length,
itemBuilder: (context, index) {
final group = groups[index];
return ListTile(
onTap: () {
context.pushNamed(AppRoutes.GROUP_DETAIL.name,
pathParameters: {
"groupId": groupsSnapshot.data![index].id!
onTap: () => context.pushNamed(AppRoutes.GROUP_DETAIL.name,
pathParameters: {"groupId": group.id!}, extra: role),
leading: IconConstants.instance.getMaterialIcon(Icons.diversity_2),
title: Text(group.name ?? '',
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(group.description ?? ''),
trailing: role == ApplicationConstants.OWNER_GROUP
? _ownerPopupMenu(group, context)
: null,
);
},
extra: widget.role);
},
leading: IconConstants.instance
.getMaterialIcon(Icons.diversity_2),
title: Text(
groupsSnapshot.data![index].name ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
groupsSnapshot.data![index].description ?? ""),
trailing:
widget.role == ApplicationConstants.OWNER_GROUP
? PopupMenuButton(
);
}
Widget _ownerPopupMenu(Group group, BuildContext context) {
return PopupMenuButton<int>(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(8.0),
bottomRight: Radius.circular(8.0),
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0),
),
),
borderRadius: BorderRadius.all(Radius.circular(8.0))),
itemBuilder: (ctx) => [
_buildPopupMenuItem(group, context,
appLocalization(context).share_group_title, Icons.share, 4),
_buildPopupMenuItem(
groupsSnapshot.data![index],
group,
context,
appLocalization(context)
.share_group_title,
Icons.share,
4),
_buildPopupMenuItem(
groupsSnapshot.data![index],
context,
appLocalization(context)
.change_group_infomation_title,
appLocalization(context).change_group_infomation_title,
Icons.settings_backup_restore,
2),
_buildPopupMenuItem(
groupsSnapshot.data![index],
group,
context,
appLocalization(context)
.delete_group_title,
appLocalization(context).delete_group_title,
Icons.delete_forever_rounded,
3),
],
icon: const Icon(Icons.more_horiz),
)
: const SizedBox.shrink(),
);
},
),
);
}
},
);
} else {
return const SizedBox.shrink();
}
}
PopupMenuItem _buildPopupMenuItem(Group group, BuildContext context,
String title, IconData iconData, int position) {
return PopupMenuItem(
PopupMenuItem<int> _buildPopupMenuItem(Group group, BuildContext context,
String title, IconData iconData, int value) {
return PopupMenuItem<int>(
value: value,
child: Row(children: [
Icon(iconData, color: Colors.black),
const SizedBox(width: 10),
Text(title)
]),
onTap: () {
if (title == appLocalization(context).share_group_title) {
Future.delayed(context.lowDuration, () {
if (title == appLocalization(context).share_group_title) {
shareGroup(context, group);
});
} else if (title ==
appLocalization(context).change_group_infomation_title) {
Future.delayed(context.lowDuration, () {
createOrJoinGroupDialog(
context,
interFamilyBloc,
widget.role,
role,
appLocalization(context).change_group_infomation_content,
appLocalization(context).group_name_title,
group.name!,
group.name ?? '',
false,
group.id!,
group.id ?? '',
appLocalization(context).description_group,
group.description ?? "");
});
group.description ?? '',
);
} else if (title == appLocalization(context).delete_group_title) {
Future.delayed(context.lowDuration, () {
showActionDialog(
context,
widget.role,
role,
interFamilyBloc,
appLocalization(context).delete_group_title,
appLocalization(context).delete_group_content,
group);
group,
);
}
});
} else {}
},
value: position,
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
iconData,
color: Colors.black,
),
const SizedBox(width: 10),
Text(title),
],
),
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'groups/groups_screen.dart';
import 'groups/groups_model.dart';
import '../../bloc/inter_family_bloc.dart';
import 'inter_family_widget.dart';
import '../../product/base/bloc/base_bloc.dart';
@@ -25,20 +26,29 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
void initState() {
super.initState();
interFamilyBloc = BlocProvider.of(context);
// fetch initial groups for the default selected tab
WidgetsBinding.instance.addPostFrameCallback((_) {
interFamilyBloc.getAllGroup(
context,
_selectedIndex == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP);
});
}
final _widgetOptions = <Widget>[
BlocProvider(
blocBuilder: () => InterFamilyBloc(),
child: const GroupsScreen(
List<Group> ownerGroups = [];
List<Group> participantGroups = [];
List<Widget> get _widgetOptions => [
GroupsScreen(
role: ApplicationConstants.OWNER_GROUP,
groups: ownerGroups,
interFamilyBloc: interFamilyBloc,
),
),
BlocProvider(
blocBuilder: () => InterFamilyBloc(),
child: const GroupsScreen(
GroupsScreen(
role: ApplicationConstants.PARTICIPANT_GROUP,
),
groups: participantGroups,
interFamilyBloc: interFamilyBloc,
),
];
@@ -49,6 +59,20 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
stream: interFamilyBloc.streamSelectedScreen,
initialData: _selectedIndex,
builder: (context, selectSnapshot) {
// subscribe to groups stream and update local lists so child widgets render instantly
return StreamBuilder<List<Group>>(
stream: interFamilyBloc.streamCurrentGroups,
builder: (context, groupsSnapshot) {
if (groupsSnapshot.hasData) {
final all = groupsSnapshot.data!;
ownerGroups = all
.where((g) => g.isOwner == true && g.visibility == 'PUBLIC')
.toList();
participantGroups = all
.where((g) => g.isOwner == null && g.visibility == 'PUBLIC')
.toList();
}
// build UI below
return Scaffold(
appBar: AppBar(
actions: [
@@ -58,35 +82,32 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
createOrJoinGroupDialog(
context,
interFamilyBloc,
selectSnapshot.data! == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP,
ApplicationConstants.OWNER_GROUP,
appLocalization(context).add_new_group,
appLocalization(context).group_name_title,
"",
false,
"",
"",
"");
"",
);
} else {
createOrJoinGroupDialog(
context,
interFamilyBloc,
selectSnapshot.data! == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP,
ApplicationConstants.PARTICIPANT_GROUP,
appLocalization(context).join_group,
appLocalization(context).group_id_title,
'',
true,
"",
appLocalization(context).group_name_title,
"");
"",
);
}
},
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
),
style:
ElevatedButton.styleFrom(shape: const CircleBorder()),
child: IconConstants.instance.getMaterialIcon(Icons.add),
),
],
@@ -142,6 +163,8 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
);
},
);
},
);
}
void checkTitle(int index) {
@@ -159,5 +182,11 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
interFamilyBloc.sinkSelectedScreen.add(_selectedIndex);
isLoading = false;
interFamilyBloc.sinkIsLoading.add(isLoading);
// fetch groups for the selected tab immediately
interFamilyBloc.getAllGroup(
context,
_selectedIndex == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP);
}
}

View File

@@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:badges/badges.dart' as badges;
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:sfm_app/product/shared/shared_snack_bar.dart';
import '../../product/shared/shared_snack_bar.dart';
import '../../product/services/notification_services.dart';
import '../../product/utils/permission_handler.dart';
import '../../product/permission/notification_permission.dart';

View File

@@ -3,6 +3,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'show_direction_widget.dart';
import 'show_nearest_place.dart';
import '../../../product/constant/icon/icon_constants.dart';
@@ -11,7 +12,6 @@ import '../../../bloc/map_bloc.dart';
import '../../../product/services/api_services.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../devices/device_model.dart';
onTapMarker(

View File

@@ -1,12 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:maps_launcher/maps_launcher.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../bloc/map_bloc.dart';
showDirections(

View File

@@ -1,9 +1,9 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../bloc/map_bloc.dart';
import 'show_direction_widget.dart';
import '../../../product/constant/icon/icon_constants.dart';

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/feature/settings/sim_data/shared_sim_component.dart';
import 'shared_sim_component.dart';
import '../../../product/services/language_services.dart';
import '../../../product/shared/shared_loading_animation.dart';
import '../../../bloc/sim_data_bloc.dart';

View File

@@ -1,4 +1,4 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
class ApplicationConstants {
static const APP_NAME = "Smatec SFM";

View File

@@ -1,9 +1,8 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/utils/app_logger_utils.dart';
import 'package:sfm_app/product/utils/responsive_text_utils.dart';
import '../utils/app_logger_utils.dart';
import '../utils/responsive_text_utils.dart';
import '../theme/app_theme_light.dart';
// MEDIA

View File

@@ -95,7 +95,7 @@ abstract class AppLocalizations {
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('en'),
Locale('vi'),
Locale('vi')
];
/// No description provided for @description_NOTUSE.
@@ -1623,6 +1623,5 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.',
);
'that was used.');
}

View File

@@ -1,5 +1,5 @@
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
import 'package:sfm_app/product/constant/lang/language_constants.dart';
import '../constant/icon/icon_constants.dart';
import '../constant/lang/language_constants.dart';
class Language {
final int id;

View File

@@ -1,10 +1,10 @@
import 'dart:developer';
import 'dart:io';
import 'package:app_settings/app_settings.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:sfm_app/product/base/widget/dialog/request_permission_dialog.dart';
import '../base/widget/dialog/request_permission_dialog.dart';
class LocationPermissionRequest {
LocationPermissionRequest._init();

View File

@@ -59,6 +59,5 @@ class MapServices {
log("Lỗi khi tìm đường");
return [];
}
return [];
}
}

View File

@@ -3,18 +3,20 @@ import 'dart:io';
import 'dart:math' as math;
import 'package:alarm/alarm.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart' hide NotificationSettings;
import 'package:firebase_messaging/firebase_messaging.dart'
hide NotificationSettings;
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:sfm_app/product/utils/app_logger_utils.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../utils/app_logger_utils.dart';
import '../../firebase_options.dart';
import '../../main.dart';
import 'alarm_services.dart';
class NotificationServices {
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
AlarmServices alarmServices = AlarmServices();
@@ -24,10 +26,13 @@ class NotificationServices {
}
Future<void> initializeLocalNotifications() async {
try{
const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings();
const InitializationSettings initializationSettings = InitializationSettings(
try {
const AndroidInitializationSettings androidInitializationSettings =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings iosInitializationSettings =
DarwinInitializationSettings();
const InitializationSettings initializationSettings =
InitializationSettings(
android: androidInitializationSettings,
iOS: iosInitializationSettings,
);
@@ -35,12 +40,13 @@ class NotificationServices {
await _notificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
dev.log("Người dùng click thông báo ở foreground với payload: ${response.payload}");
handleMessage(response.payload,controller);
dev.log(
"Người dùng click thông báo ở foreground với payload: ${response.payload}");
handleMessage(response.payload, controller);
},
);
dev.log("Local notifications initialized");
}catch(e){
} catch (e) {
dev.log("Error initializing local notifications: $e");
}
}
@@ -62,11 +68,13 @@ class NotificationServices {
try {
if (Platform.isAndroid) {
return await _notificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission();
} else if (Platform.isIOS) {
return await _notificationsPlugin
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(alert: true, sound: true, badge: true);
}
return null;
@@ -84,7 +92,8 @@ class NotificationServices {
final type = message.data['type'] as String? ?? 'normal';
if (title == null || body == null) {
dev.log('Skipping notification: missing title or body', name: 'Notification');
dev.log('Skipping notification: missing title or body',
name: 'Notification');
return;
}
@@ -100,15 +109,16 @@ class NotificationServices {
const channelName = 'High Importance Notification';
const channelDescription = 'Channel description';
final androidChannel = AndroidNotificationChannel(
channelId,
channelName,
importance: Importance.max,
description: channelDescription,
);
// final androidChannel = AndroidNotificationChannel(
// channelId,
// channelName,
// importance: Importance.max,
// description: channelDescription,
// );
final androidPlugin = _notificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
final androidPlugin =
_notificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
// Delete existing channel to prevent conflicts
await androidPlugin?.deleteNotificationChannel(channelId);
@@ -180,12 +190,13 @@ class NotificationServices {
Future<void> setupInteractMessage(PersistentTabController controller) async {
// Khi app terminated
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
handleMessage(initialMessage.data['type'],controller);
handleMessage(initialMessage.data['type'], controller);
} catch (e, stack) {
dev.log("Error handling initial message: $e\n$stack");
}
@@ -194,14 +205,15 @@ class NotificationServices {
// Khi app ở background
FirebaseMessaging.onMessageOpenedApp.listen((message) {
try {
handleMessage(message.data['type'],controller);
handleMessage(message.data['type'], controller);
} catch (e, stack) {
dev.log("Error in onMessageOpenedApp: $e\n$stack");
}
});
}
Future<void> showBackgroundOrTerminateNotification(RemoteMessage message) async {
Future<void> showBackgroundOrTerminateNotification(
RemoteMessage message) async {
try {
// Early validation of notification data
final title = message.data['title'] as String?;
@@ -209,7 +221,8 @@ class NotificationServices {
final type = message.data['type'] as String? ?? 'normal';
if (title == null || body == null) {
dev.log('Skipping notification: missing title or body', name: 'Notification');
dev.log('Skipping notification: missing title or body',
name: 'Notification');
return;
}
@@ -218,7 +231,6 @@ class NotificationServices {
const channelName = 'High Importance Notification';
const channelDescription = 'Channel description';
// Configure notification details
final androidDetails = AndroidNotificationDetails(
channelId,
@@ -274,9 +286,9 @@ Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
name: "sfm-notification"
);
AppLoggerUtils.warning("Firebase đã được khởi tạo trong background handler");
name: "sfm-notification");
AppLoggerUtils.warning(
"Firebase đã được khởi tạo trong background handler");
} else {
AppLoggerUtils.warning("Firebase đã được khởi tạo trước đó");
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/constant/image/image_constants.dart';
import '../constant/image/image_constants.dart';
class SharedBackground extends StatelessWidget {
final Widget child;

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
import '../constant/icon/icon_constants.dart';
const int _kDuration = 300;
const double _kWidth = 60;

View File

@@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/extension/context_extension.dart';
import 'package:top_snackbar_flutter/custom_snack_bar.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart';
import '../extension/context_extension.dart';
void showNoIconTopSnackBar(BuildContext context, String message,
Color backgroundColor, Color textColor) {
if (!context.mounted) return;

View File

@@ -1,7 +1,7 @@
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/theme/app_theme.dart';
import 'app_theme.dart';
class AppThemeDark extends AppTheme {
static AppThemeDark? _instance;
static AppThemeDark get instance {

View File

@@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
import 'package:sfm_app/product/services/api_services.dart';
import 'package:sfm_app/product/services/language_services.dart';
import 'package:sfm_app/product/shared/model/district_model.dart';
import 'package:sfm_app/product/shared/model/province_model.dart';
import '../../feature/device_log/device_logs_model.dart';
import '../services/api_services.dart';
import '../services/language_services.dart';
import '../shared/model/district_model.dart';
import '../shared/model/province_model.dart';
import '../../feature/devices/device_model.dart';
import '../constant/icon/icon_constants.dart';
import '../shared/model/ward_model.dart';

View File

@@ -23,7 +23,8 @@ Future<LocationPermission> checkAndRequestPermission() async {
}
if (permission == LocationPermission.deniedForever) {
print('Quyền truy cập vị trí bị từ chối vĩnh viễn. Vui lòng cấp quyền trong cài đặt.');
print(
'Quyền truy cập vị trí bị từ chối vĩnh viễn. Vui lòng cấp quyền trong cài đặt.');
return permission;
}
@@ -56,7 +57,8 @@ Future<void> requestLocationPermission() async {
LocationPermission permission = await checkAndRequestPermission();
// Bước 3: Nếu quyền được cấp, lấy vị trí
if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
if (permission == LocationPermission.whileInUse ||
permission == LocationPermission.always) {
await getCurrentPosition();
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_barcode_scanner_plus/flutter_barcode_scanner_plus.dart';
import '../services/language_services.dart';
class QRScanUtils {

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../constant/status_code/status_code_constants.dart';
import '../shared/shared_snack_bar.dart';

View File

@@ -1,4 +1,5 @@
import 'package:flutter/cupertino.dart';
import '../extension/context_extension.dart';
class ResponsiveText{