Complete refactoring SFM App Source Code

This commit is contained in:
anhtunz
2024-12-15 00:59:02 +07:00
parent caa73ca43c
commit 2e27d59278
247 changed files with 18390 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
typedef BlocBuilder<T> = T Function();
typedef BlocDisposer<T> = Function(T);
abstract class BlocBase {
void dispose();
}
class _BlocProviderInherited<T> extends InheritedWidget {
const _BlocProviderInherited({
Key? key,
required Widget child,
required this.bloc,
}) : super(key: key, child: child);
final T bloc;
@override
bool updateShouldNotify(_BlocProviderInherited oldWidget) => false;
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
const BlocProvider({
Key? key,
required this.child,
required this.blocBuilder,
this.blocDispose,
}) : super(key: key);
final Widget child;
final BlocBuilder<T> blocBuilder;
final BlocDisposer<T>? blocDispose;
@override
BlocProviderState<T> createState() => BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context) {
_BlocProviderInherited<T> provider = context
.getElementForInheritedWidgetOfExactType<_BlocProviderInherited<T>>()
?.widget as _BlocProviderInherited<T>;
return provider.bloc;
}
}
class BlocProviderState<T extends BlocBase> extends State<BlocProvider<T>> {
late T bloc;
@override
void initState() {
super.initState();
bloc = widget.blocBuilder();
}
@override
void dispose() {
if (widget.blocDispose != null) {
widget.blocDispose!(bloc);
} else {
bloc.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return _BlocProviderInherited<T>(
bloc: bloc,
child: widget.child,
);
}
}

View File

@@ -0,0 +1,142 @@
import 'dart:async';
import 'package:flutter/material.dart';
abstract class BasePageScreen extends StatefulWidget {
const BasePageScreen({Key? key}) : super(key: key);
}
abstract class BasePageScreenState<T extends BasePageScreen> extends State<T> {
bool _isBack = true;
bool _isHome = true;
bool _isShowTitle = true;
final _loadingController = StreamController<bool>();
String appBarTitle();
String appBarSubTitle();
void onClickBackButton();
void onClickHome();
void isBackButton(bool isBack) {
_isBack = isBack;
}
void isHomeButton(bool isHome) {
_isHome = isHome;
}
void isShowTitle(bool isShowTitle) {
_isShowTitle = isShowTitle;
}
void showLoading() {
_loadingController.add(true);
}
void hideLoading() {
_loadingController.add(false);
}
}
mixin BaseScreen<T extends BasePageScreen> on BasePageScreenState<T> {
Widget body();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// flexibleSpace: Container(
// decoration: const BoxDecoration(
// image: DecorationImage(
// image: AssetImage('assets/images/bg_header.jpeg'),
// fit: BoxFit.cover)),
// ),
title: Column(
children: [
Visibility(
visible: _isShowTitle,
child: Text(
appBarTitle(),
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
Text(
appBarSubTitle(),
maxLines: _isShowTitle ? 1 : 2,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)
],
),
leading: _isBack
? IconButton(
icon: const Icon(
Icons.arrow_back_ios,
color: Colors.white,
),
onPressed: () {
// onClickBackButton();
Navigator.of(context).pushNamedAndRemoveUntil(
"/main", (Route<dynamic> route) => false);
},
)
: Container(),
actions: [
_isHome
? IconButton(
icon: const Icon(
Icons.home,
color: Colors.white,
),
onPressed: () {
onClickHome();
},
)
: Container()
],
),
body: Stack(
children: [
Container(
height: double.infinity,
color: Colors.white,
child: body(),
),
StreamBuilder(
stream: _loadingController.stream,
builder: (_, snapshot) {
return snapshot.data == true
? Positioned.fill(
child: _buildLoader(),
)
: const SizedBox();
},
),
],
));
}
Widget _buildLoader() {
return Container(
color: Colors.black54,
child: const Center(
child: CircularProgressIndicator(),
),
);
}
@override
void dispose() {
_loadingController.close();
super.dispose();
}
}

View File

@@ -0,0 +1,61 @@
import 'package:app_settings/app_settings.dart';
import 'package:flutter/material.dart';
import '../../../extention/context_extention.dart';
import '../../../services/language_services.dart';
class RequestPermissionDialog {
showRequestPermissionDialog(BuildContext context, IconData icon,
String dialogContent, AppSettingsType type) {
showDialog(
useRootNavigator: false,
context: context,
builder: (dialogContext) {
return AlertDialog(
title: Center(
child: Icon(icon),
),
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: context.paddingNormalVertical,
child: Text(
dialogContent,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
),
),
Divider(height: context.lowValue),
GestureDetector(
onTap: () {
AppSettings.openAppSettings(type: type);
},
child: Padding(
padding:
context.paddingNormalVertical, // Cách giữa các phần tử
child: Text(appLocalization(context).allow_message,
style: const TextStyle(fontWeight: FontWeight.bold)),
),
),
Divider(height: context.lowValue),
GestureDetector(
onTap: () {
Navigator.of(dialogContext).pop(); // Đóng dialog
},
child: Padding(
padding:
context.paddingNormalVertical, // Cách giữa các phần tử
child: Text(appLocalization(context).decline_message,
style: const TextStyle(fontWeight: FontWeight.bold)),
),
),
],
),
);
},
);
}
}

42
lib/product/cache/local_manager.dart vendored Normal file
View File

@@ -0,0 +1,42 @@
import 'package:shared_preferences/shared_preferences.dart';
import '../constant/enums/local_keys_enums.dart';
class LocaleManager {
LocaleManager._init() {
SharedPreferences.getInstance().then((value) {
_preferences = value;
});
}
static final LocaleManager _instance = LocaleManager._init();
SharedPreferences? _preferences;
static LocaleManager get instance => _instance;
static Future prefrencesInit() async {
instance._preferences ??= await SharedPreferences.getInstance();
}
void setString(PreferencesKeys key, String value) async {
await _preferences?.setString(key.toString(), value);
}
void setInt(PreferencesKeys key, int value) async {
await _preferences?.setInt(key.toString(), value);
}
Future<void> setStringValue(PreferencesKeys key, String value) async {
await _preferences!.setString(key.toString(), value);
}
String getStringValue(PreferencesKeys key) =>
_preferences?.getString(key.toString()) ?? '';
int getIntValue(PreferencesKeys key) =>
_preferences?.getInt(key.toString()) ?? 0;
Future<void> deleteStringValue(PreferencesKeys key) async {
await _preferences!.remove(key.toString());
}
}

View File

@@ -0,0 +1,24 @@
// ignore_for_file: constant_identifier_names
class APIPathConstants {
static const LOGIN_PATH = "/api/login";
static const LOGOUT_PATH = "/api/logout";
static const BELL_NOTIFICATIONS_PATH = "/api/notifications/bell-list";
static const BELL_UPDATE_READ_NOTIFICATIONS_PATH =
"/api/notifications/bell-read";
static const DEVICE_NOTIFICATION_SETTINGS = "/api/users/my-device-settings";
static const DASHBOARD_DEVICES = "/api/dashboard/devices";
static const USER_PATH = "/api/users";
static const USER_PROFILE_PATH = "/api/users/profile";
static const PROVINCES_PATH = "/api/vn/provinces";
static const DISTRICTS_PATH = "/api/vn/districts";
static const WARDS_PATH = "/api/vn/wards";
static const DEVICE_PATH = "/api/devices";
static const DEVICE_REGISTER_PATH = "/api/devices/register";
static const DEVICE_UNREGISTER_PATH = "/api/devices/unregister";
static const ALL_GROUPS_PATH = "/api/groups/user";
static const GROUPS_PATH = "/api/groups";
static const JOIN_GROUP_PATH = "/api/groups/join";
static const APPROVE_GROUP_PATH = "/api/groups/approve";
static const DEVICE_LOGS_PATH = "/api/device-logs";
}

View File

@@ -0,0 +1,21 @@
// ignore_for_file: constant_identifier_names
class ApplicationConstants {
static const APP_NAME = "Smatec SFM";
static const DOMAIN = "sfm.smatec.com.vn";
static const MAP_KEY = "AIzaSyDI8b-PUgKUgj5rHdtgEHCwWjUXYJrqYhE";
static const LOGIN_PATH = "/login";
static const LOGOUT_PATH = "/logout";
static const HOME_PATH = "/";
static const SETTINGS_PATH = "/settings";
static const BELL_PATH = "/bell";
static const DEVICES_MANAGER_PATH = "/devices-manager";
static const DEVICES_UPDATE_PATH = "/device-update";
static const DEVICES_DETAIL_PATH = "/device";
static const MAP_PATH = "/map";
static const DEVICE_LOGS_PATH = "/device-logs";
static const GROUP_PATH = "/groups";
static const DEVICE_NOTIFICATIONS_SETTINGS = "/device-notifications-settings";
static const OWNER_GROUP = "owner";
static const PARTICIPANT_GROUP = "participant";
}

View File

@@ -0,0 +1,16 @@
// ignore_for_file: constant_identifier_names
enum AppRoutes {
LOGIN,
HOME,
SETTINGS,
DEVICE_NOTIFICATION_SETTINGS,
BELL,
DEVICES,
DEVICE_UPDATE,
DEVICE_DETAIL,
MAP,
HISTORY,
GROUPS,
GROUP_DETAIL,
}

View File

@@ -0,0 +1,3 @@
// ignore_for_file: constant_identifier_names
enum AppThemes { LIGHT, DARK, SYSTEM }

View File

@@ -0,0 +1,10 @@
// ignore_for_file: constant_identifier_names
enum PreferencesKeys{
TOKEN,
UID,
EXP,
ROLE,
LANGUAGE_CODE,
THEME
}

View File

@@ -0,0 +1,7 @@
// ignore_for_file: constant_identifier_names
enum RoleEnums{
USER,
ADMIN,
MOD
}

View File

@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class IconConstants {
IconConstants._init();
static IconConstants? _instance;
static IconConstants get instance => _instance ??= IconConstants._init();
String get logo => getIcon("");
String getIcon(String name) => "assets/icons/$name.png";
Icon getMaterialIcon(IconData icon) => Icon(icon);
}

View File

@@ -0,0 +1,9 @@
class ImageConstants {
ImageConstants._init();
static ImageConstants? _instance;
static ImageConstants get instance => _instance ??= ImageConstants._init();
String get logo => getImage("");
String getImage(String name) => "assets/images/$name.png";
}

View File

@@ -0,0 +1,6 @@
// ignore_for_file: constant_identifier_names
class LanguageConstants {
static const ENGLISH = "en";
static const VIETNAM = "vi";
}

View File

@@ -0,0 +1,156 @@
import 'package:go_router/go_router.dart';
import 'package:sfm_app/feature/devices/device_detail/device_detail_bloc.dart';
import 'package:sfm_app/feature/devices/device_detail/device_detail_screen.dart';
import 'package:sfm_app/feature/settings/device_notification_settings/device_notification_settings_bloc.dart';
import 'package:sfm_app/feature/settings/device_notification_settings/device_notification_settings_screen.dart';
import '../app/app_constants.dart';
import '../../../feature/auth/login/bloc/login_bloc.dart';
import '../../../feature/auth/login/screen/login_screen.dart';
import '../../../feature/bell/bell_bloc.dart';
import '../../../feature/bell/bell_screen.dart';
import '../../../feature/devices/device_update/device_update_bloc.dart';
import '../../../feature/devices/device_update/device_update_screen.dart';
import '../../../feature/devices/devices_manager_bloc.dart';
import '../../../feature/devices/devices_manager_screen.dart';
import '../../../feature/error/not_found_screen.dart';
import '../../../feature/inter_family/group_detail/group_detail_bloc.dart';
import '../../../feature/inter_family/group_detail/group_detail_screen.dart';
import '../../../feature/inter_family/inter_family_bloc.dart';
import '../../../feature/inter_family/inter_family_screen.dart';
import '../../../feature/log/device_logs_bloc.dart';
import '../../../feature/log/device_logs_screen.dart';
import '../../../feature/main/main_bloc.dart';
import '../../../feature/main/main_screen.dart';
import '../../../feature/map/map_bloc.dart';
import '../../../feature/map/map_screen.dart';
import '../../../feature/settings/settings_bloc.dart';
import '../../../feature/settings/settings_screen.dart';
import '../../../product/base/bloc/base_bloc.dart';
import '../enums/app_route_enums.dart';
import '../../../product/shared/shared_transition.dart';
GoRouter goRouter() {
return GoRouter(
debugLogDiagnostics: true,
errorBuilder: (context, state) => const NotFoundScreen(),
initialLocation: ApplicationConstants.LOGIN_PATH,
routes: <RouteBase>[
GoRoute(
path: ApplicationConstants.LOGIN_PATH,
name: AppRoutes.LOGIN.name,
builder: (context, state) => BlocProvider(
child: const LoginScreen(),
blocBuilder: () => LoginBloc(),
),
),
GoRoute(
path: ApplicationConstants.HOME_PATH,
name: AppRoutes.HOME.name,
builder: (context, state) => BlocProvider(
child: const MainScreen(),
blocBuilder: () => MainBloc(),
),
),
GoRoute(
path: ApplicationConstants.SETTINGS_PATH,
name: AppRoutes.SETTINGS.name,
pageBuilder: (context, state) => CustomTransitionPage(
child: BlocProvider(
child: const SettingsScreen(),
blocBuilder: () => SettingsBloc(),
),
transitionsBuilder: transitionsBottomToTop,
),
),
GoRoute(
path: ApplicationConstants.BELL_PATH,
name: AppRoutes.BELL.name,
pageBuilder: (context, state) => CustomTransitionPage(
child: BlocProvider(
child: const BellScreen(),
blocBuilder: () => BellBloc(),
),
transitionsBuilder: transitionsCustom1),
),
GoRoute(
path: ApplicationConstants.DEVICES_MANAGER_PATH,
name: AppRoutes.DEVICES.name,
builder: (context, state) => BlocProvider(
child: const DevicesManagerScreen(),
blocBuilder: () => DevicesManagerBloc(),
),
),
GoRoute(
path: '${ApplicationConstants.DEVICES_UPDATE_PATH}/:thingID',
name: AppRoutes.DEVICE_UPDATE.name,
pageBuilder: (context, state) => CustomTransitionPage(
child: BlocProvider(
child: DeviceUpdateScreen(
thingID: state.pathParameters['thingID']!,
),
blocBuilder: () => DeviceUpdateBloc(),
),
transitionsBuilder: transitionsBottomToTop),
),
GoRoute(
path: '${ApplicationConstants.DEVICES_DETAIL_PATH}/:thingID',
name: AppRoutes.DEVICE_DETAIL.name,
pageBuilder: (context, state) => CustomTransitionPage(
child: BlocProvider(
child: DetailDeviceScreen(
thingID: state.pathParameters['thingID']!,
),
blocBuilder: () => DetailDeviceBloc(),
),
transitionsBuilder: transitionsRightToLeft),
),
GoRoute(
path: ApplicationConstants.MAP_PATH,
name: AppRoutes.MAP.name,
builder: (context, state) => BlocProvider(
child: const MapScreen(),
blocBuilder: () => MapBloc(),
),
),
GoRoute(
path: ApplicationConstants.DEVICE_LOGS_PATH,
name: AppRoutes.HISTORY.name,
builder: (context, state) => BlocProvider(
child: const DeviceLogsScreen(),
blocBuilder: () => DeviceLogsBloc(),
),
),
GoRoute(
path: ApplicationConstants.GROUP_PATH,
name: AppRoutes.GROUPS.name,
builder: (context, state) => BlocProvider(
child: const InterFamilyScreen(),
blocBuilder: () => InterFamilyBloc(),
),
),
GoRoute(
path: '${ApplicationConstants.GROUP_PATH}/:groupId',
name: AppRoutes.GROUP_DETAIL.name,
pageBuilder: (context, state) {
final groupId = state.pathParameters['groupId']!;
final role = state.extra! as String;
return CustomTransitionPage(
child: BlocProvider(
child: DetailGroupScreen(group: groupId, role: role),
blocBuilder: () => DetailGroupBloc(),
),
transitionsBuilder: transitionsRightToLeft);
}),
GoRoute(
path: ApplicationConstants.DEVICE_NOTIFICATIONS_SETTINGS,
name: AppRoutes.DEVICE_NOTIFICATION_SETTINGS.name,
pageBuilder: (context, state) => CustomTransitionPage(
child: BlocProvider(
child: const DeviceNotificationSettingsScreen(),
blocBuilder: () => DeviceNotificationSettingsBloc(),
),
transitionsBuilder: transitionsRightToLeft),
),
],
);
}

View File

@@ -0,0 +1,7 @@
// ignore_for_file: constant_identifier_names
class StatusCodeConstants {
static const CREATED = 201;
static const OK = 200;
static const BAD_REQUEST = 400;
}

View File

@@ -0,0 +1,103 @@
import 'dart:math';
import 'package:flutter/material.dart';
import '../theme/app_theme_light.dart';
// MEDIA
extension ContextExtension on BuildContext {
MediaQueryData get mediaQuery => MediaQuery.of(this);
}
// VALUES
extension MediaQueryExtension on BuildContext {
double get height => mediaQuery.size.height;
double get width => mediaQuery.size.width;
double get lowValue => height * 0.01;
double get normalValue => height * 0.02;
double get mediumValue => height * 0.04;
double get highValue => height * 0.1;
double dynamicWidth(double val) => width * val;
double dynamicHeight(double val) => height * val;
}
// THEME
extension ThemeExtension on BuildContext {
ThemeData get theme => Theme.of(this);
TextTheme get textTheme => theme.textTheme;
ColorScheme get colors => AppThemeLight.instance.theme.colorScheme;
}
// PADDING ALLL
extension PaddingExtensionAll on BuildContext {
EdgeInsets get paddingLow => EdgeInsets.all(lowValue);
EdgeInsets get paddingNormal => EdgeInsets.all(normalValue);
EdgeInsets get paddingMedium => EdgeInsets.all(mediumValue);
EdgeInsets get paddingHigh => EdgeInsets.all(highValue);
EdgeInsets dynamicPadding(double val) => EdgeInsets.all(val);
// double dynamicPadding(double val) => height * val;
}
// PADDING SYMETRIC
extension PaddingExtensionSymetric on BuildContext {
// VERTICAL PADDİNG
EdgeInsets get paddingLowVertical => EdgeInsets.symmetric(vertical: lowValue);
EdgeInsets get paddingNormalVertical =>
EdgeInsets.symmetric(vertical: normalValue);
EdgeInsets get paddingMediumVertical =>
EdgeInsets.symmetric(vertical: mediumValue);
EdgeInsets get paddingHighVertical =>
EdgeInsets.symmetric(vertical: highValue);
// HORIZONTAL PADDİNG
EdgeInsets get paddingLowHorizontal =>
EdgeInsets.symmetric(horizontal: lowValue);
EdgeInsets get paddingNormalHorizontal =>
EdgeInsets.symmetric(horizontal: normalValue);
EdgeInsets get paddingMediumHorizontal =>
EdgeInsets.symmetric(horizontal: mediumValue);
EdgeInsets get paddingHighHorizontal =>
EdgeInsets.symmetric(horizontal: highValue);
}
// RANDOM COLOR
extension PageExtension on BuildContext {
Color get randomColor => Colors.primaries[Random().nextInt(17)];
}
// DURATION
extension DurationExtension on BuildContext {
Duration get lowDuration => const Duration(milliseconds: 150);
Duration get normalDuration => const Duration(milliseconds: 500);
Duration dynamicSecondDuration(int seconds) => Duration(seconds: seconds);
Duration dynamicMinutesDuration(int minutes) => Duration(minutes: minutes);
}
// RADIUS
extension RadiusExtension on BuildContext {
Radius get lowRadius => Radius.circular(width * 0.02);
Radius get normalRadius => Radius.circular(width * 0.05);
Radius get highRadius => Radius.circular(width * 0.1);
}
extension TextStyleExtention on BuildContext {
TextStyle get labelSmallTextStyle => Theme.of(this).textTheme.labelSmall!;
TextStyle get labelMediumTextStyle => Theme.of(this).textTheme.labelMedium!;
TextStyle get labelLargeTextStyle => Theme.of(this).textTheme.labelLarge!;
TextStyle get bodySmallTextStyle => Theme.of(this).textTheme.bodySmall!;
TextStyle get bodyMediumTextStyle => Theme.of(this).textTheme.bodyMedium!;
TextStyle get bodyLargeTextStyle => Theme.of(this).textTheme.bodyLarge!;
TextStyle get titleSmallTextStyle => Theme.of(this).textTheme.titleSmall!;
TextStyle get titleMediumTextStyle => Theme.of(this).textTheme.titleMedium!;
TextStyle get titleLargeTextStyle => Theme.of(this).textTheme.titleLarge!;
TextStyle get headlineSmallTextStyle =>
Theme.of(this).textTheme.headlineSmall!;
TextStyle get headlineMediumTextStyle =>
Theme.of(this).textTheme.headlineMedium!;
TextStyle get headlineLargeTextStyle =>
Theme.of(this).textTheme.headlineLarge!;
}

View File

@@ -0,0 +1,242 @@
{
"description_NOTUSE": "This is english language in HomePage",
"home_page_name": "Home Page",
"vietnam_language": "Vietnamese",
"english_language": "English",
"notification": "Notifications:",
"profile_icon_title": "Settings",
"log_out": "Log out",
"log_out_content": "Are you sure you want to log out?",
"notification_description": "All devices are operating normally",
"button_fake_fire_message": "False fire alarm",
"in_progress_message": "In progress",
"smoke_detecting_message": "Smoke detecting!",
"smoke_detecting_message_lowercase": "smoke detecting!",
"disconnect_message_uppercase": "Disconnected",
"disconnect_message_lowercase": "disconnected",
"location_message": "Address: ",
"confirm_fake_fire_message": "Are you sure the fire is a false alarm?",
"confirm_fake_fire_body": "Please check carefully to ensure that this is just a normal incident. The fire department will confirm that this is a false alarm!",
"confirm_fake_fire_sure_message": "I''m sure",
"let_PCCC_handle_message": "Let the Fire Prevention and Fighting Team handle it!",
"overview_message": "Overview",
"total_nof_devices_message": "Total number of devices",
"active_devices_message": "Active",
"inactive_devices_message": "Inactive",
"warning_devices_message": "Warning",
"unused_devices_message": "Unused",
"description_NOTUSE1": "This is english language in DeviceManagerPage",
"device_manager_page_name": "Devices Manager",
"add_device_title": "Add new device",
"input_extID_device_input": "Devcice ID",
"input_extID_device_hintText": "Enter the device ID",
"input_name_device_device": "Device Name",
"input_name_device_hintText": "Enter the device Name",
"paginated_data_table_title": "List of devices",
"paginated_data_table_column_action": "Action",
"paginated_data_table_column_deviceName": "Device name",
"paginated_data_table_column_deviceStatus": "Status",
"paginated_data_table_column_deviceBaterry": "Battery",
"paginated_data_table_column_deviceSignal": "Signal",
"paginated_data_table_column_deviceTemperature": "Temperature",
"paginated_data_table_column_deviceHump": "Humidity",
"paginated_data_table_column_devicePower": "Power",
"delete_device_dialog_title": "Remove device",
"delete_device_dialog_content": "Are you sure you want to delete this device?",
"update_device_dialog_title": "Update device",
"update_device_dialog_location_title": "Device location",
"update_device_dialog_location_longitude": "Longitude",
"update_device_dialog_location_latitude": "Latitude",
"update_device_dialog_location_longitude_hintText": "Enter longitude",
"update_device_dialog_location_latitude_hintText": "Enter latitude",
"update_device_dialog_location_province_hintText": "Select Province/City",
"update_device_dialog_location_province_searchHint": "Find Province/City",
"update_device_dialog_location_district_hintText": "Select District",
"update_device_dialog_location_district_searchHint": "Find district",
"update_device_dialog_location_ward_hintText": "Select Ward/Commune",
"update_device_dialog_location_ward_searchHint": "Find Ward/Commune",
"update_device_dialog_maps_dialog_title": "Update location",
"update_device_dialog_search_location_hint": "Search Location",
"description_NOTUSE8": "This is english language in MapPositionPage",
"map_your_location": "Your Location",
"map_show_direction": "Give directions",
"map_nearby_hospital": "Nearby hospital",
"map_nearest_hospital": "Nearest hospital",
"map_nearby_firestation": "Nearby fire station",
"map_nearest_firestation": "Nearest fire station",
"map_result": "Result",
"map_always_opened": "Always open",
"map_openning": "Openning",
"map_closed": "Closed",
"map_no_results": "No results found",
"map_start": "Start",
"map_destination": "Destination",
"map_stream": "Stream",
"description_NOTUSE2": "This is english language in DeviceLogPage",
"device_log_page_name": "Devices Log",
"choose_device_dropdownButton": "Select device",
"choose_date_start_datePicker": "Start from",
"choose_date_end_datePicker": "End",
"main_no_data": "No data yet.",
"description_NOTUSE3": "This is english language in InterFamily",
"interfamily_page_name": "InterFamily",
"my_group_title": "My group",
"invite_group": "Joined group",
"add_new_group": "Add new group",
"join_group": "Join group",
"group_name_title": "Group Name",
"group_id_title": "Group ID",
"add_new_user_title": "Add user",
"share_group_title": "Share group",
"change_group_infomation_title": "Change Infomation",
"change_group_infomation_content": "Change group infomation",
"delete_group_title": "Delete group",
"delete_group_content": "Are you sure you want to delete this group?",
"leave_group_content": "Are you sure you want to leave this group?",
"dont_have_group": "No group yet",
"dont_join_group": "You haven''t joined any groups yet.",
"description_group": "Description",
"add_new_device_title": "Add new device",
"approve_user": "Approve members",
"devices_title": "Devices",
"device_title": "Device",
"member_title": "Members",
"leave_group_title": "Leave group",
"dont_have_device": "No device yet",
"description_NOTUSE4": "This is english language in ProfilePage",
"profile_page_title": "Settings Page",
"profile_change_info": "Change information",
"profile_change_pass": "Change password",
"profile_setting": "Notification Setting",
"change_profile_title": "Personal information",
"change_profile_username": "Username: ",
"change_profile_username_hint": "Enter username ",
"change_profile_email": "Email: ",
"change_profile_email_hint": "Enter email ",
"change_profile_email_not_empty": "Email cannot be empty",
"change_profile_tel": "Phone number: ",
"change_profile_tel_hint": "Enter phone number",
"change_profile_tel_not_empty": "Phone number cannot be empty",
"change_profile_address": "Address: ",
"change_profile_address_hint": "Enter address",
"change_profile_old_pass": "Password: ",
"change_profile_old_pass_hint": "Enter password",
"change_profile_old_pass_not_empty": "Old password cannot be empty",
"change_profile_new_pass": "New password: ",
"change_profile_new_pass_hint": "Enter new password",
"change_profile_new_pass_not_empty": "New password cannot be empty",
"change_profile_device_notification_select_all": "Select all",
"change_profile_device_notification_deselect_all": "Deselect all",
"description_NOTUSE5": "This is english language in BellPage",
"bell_page_title": "Notifications",
"bell_page_no_items_body": "No notifications yet",
"bell_user_uppercase": "User",
"bell_battery_device": "Device Battery",
"bell_user_joined_group": "joined group",
"bell_leave_group": "left group",
"bell_user_added_group": "added to the group",
"bell_user_kick_group": "removed from the group",
"bell_operate_normal": "operating normally",
"bell_invalid_code": "Invalid event code",
"bell_days_ago": "days ago",
"bell_hours_ago": "hours ago",
"bell_minutes_ago": "minutes ago",
"bell_just_now": "just now",
"bell_read_all": "You have read all the notifications",
"description_NOTUSE6": "This is english language in GlobalFunction",
"gf_newly_create_message": "Newly created",
"gf_disconnect_message": "Disconnected",
"gf_smoke_detected_message": "Smoke detected",
"gf_no_signal_message": "No Signal",
"gf_weak_signal_message": "Weak Signal",
"gf_moderate_signal_message": "Moderate signal",
"gf_good_signal_message": "Good signal",
"gf_volt_detect_message": "Voltage detected",
"gf_temp_detect_message": "Temperature detected",
"gf_hum_detect_message": "Humidity detected",
"gf_battery_detect_message": "Battery detected",
"gf_offline_message": "Offline",
"gf_in_firefighting_message": "In firefighting",
"gf_device_error_message": "Device error",
"gf_not_move_message": "Not moved",
"gf_moving_message": "Moved",
"gf_remove_from_base_message": "Removed from the base",
"gf_connected_lowercase": "connected",
"description_NOTUSE7": "This is english language in LoginPage",
"login_account_not_empty": "Account cannot be empty",
"login_account_hint": "Account",
"login_password_not_empty": "Password cannot be empty",
"login_password_hint": "Password",
"login_success_message": "Login successful",
"login_incorrect_usernameOrPass": "Incorrect account or password",
"login_button_content": "Login",
"description_NOTUSE9": "This is english language in DeviceUpdatePage",
"device_update_title": "Update Device",
"device_update_location": "Device Location",
"device_update_province": "Province/City",
"device_update_district": "District",
"device_update_ward": "Ward/Commune",
"description_NOTUSE10": "This is english language in DetailDevicePage",
"detail_device_dont_has_location_message": "No location information available yet",
"no_data_message": "No data yet",
"normal_message": "Normal",
"warning_status_message": "Warning",
"undefine_message": "Undefined",
"low_message_uppercase": "Low",
"moderate_message_uppercase": "Moderate",
"good_message_uppercase": "Good",
"low_message_lowercase": "low",
"moderate_message_lowercase": "moderate",
"good_message_lowercase": "good",
"error_message_uppercase": "Error",
"error_message_lowercase": "error",
"warning_message": "Warning: ",
"loading_message": "Loading...",
"detail_message": "Detail",
"decline_message": "DECLINE",
"allow_message": "ALLOW",
"add_button_content": "Add",
"update_button_content": "Update",
"change_button_content": "Change",
"confirm_button_content": "Confirm",
"delete_button_content": "Delete",
"cancel_button_content": "Cancel",
"find_button_content": "Find",
"home_page_destination": "Home",
"manager_page_destination": "Manager",
"map_page_destination": "Map",
"history_page_destination": "History",
"history_page_destination_tooltip": "Device history",
"group_page_destination": "Group",
"group_page_destination_tooltip": "Exchange device notifications",
"notification_enter_all_inf": "Please enter all the required information",
"notification_update_device_success": "Device update successfully",
"notification_update_device_failed": "Device update failed",
"notification_update_device_error": "Device update Error",
"notification_cannot_find_address_from_location": "Can''t find the location",
"notification_add_device_success": "Device added successfully",
"notification_add_device_failed": "Failed to add device",
"notification_create_device_success": "Device created successfully",
"notification_create_device_failed": "Failed to create device",
"notification_delete_device_success": "Device deleted successfully",
"notification_delete_device_failed": "Failed to delete device",
"notification_device_not_exist": "The device does not exist",
"notification_add_group_success": "Group created successfully",
"notification_add_group_failed": "Failed to create group",
"notification_update_group_success": "Group updated successfully",
"notification_update_group_failed": "Failed to updated group",
"notification_delete_group_success": "Group deleted successfully",
"notification_delete_group_failed": "Failed to delete group",
"notification_leave_group_success": "Leave group successfully",
"notification_leave_group_failed": "Failed to leave group",
"notification_join_request_group_success": "Group join request successful!",
"notification_join_request_group_failed": "Group join request failed!",
"notification_update_profile_success": "Update profile successfully",
"notification_update_profile_failed": "Failed to update profile",
"notification_update_password_success": "Change password successfully",
"notification_update_password_failed": "The old password does not match",
"notification_update_device_settings_success": "Device notification updated successfully",
"notification_update_device_settings_failed": "Failed to update device notification",
"notification_confirm_fake_fire_success": "Information has been updated to the Fire Station",
"notification_confirm_fake_fire_failed": "Failed to update confirm fake fire"
}

View File

@@ -0,0 +1,242 @@
{
"description_NOTUSE": "This is VietNam language in HomePage",
"home_page_name": "Trang chủ",
"vietnam_language": "Tiếng Việt",
"english_language": "Tiếng Anh",
"notification": "Thông báo:",
"profile_icon_title": "Cài đặt",
"log_out": "Đăng xuất",
"log_out_content": "Bạn chắc chắn muốn đăng xuất?",
"notification_description": "Tất cả thiết bị hoạt động bình thường",
"button_fake_fire_message": "Cháy giả?",
"in_progress_message": "Đang xử lý",
"smoke_detecting_message": "Phát hiện khói!",
"smoke_detecting_message_lowercase": "Phát hiện khói!",
"disconnect_message_uppercase": "Mất kết nối",
"disconnect_message_lowercase": "mất kết nối",
"location_message": "Địa chỉ: ",
"confirm_fake_fire_message": "Bạn chắc chắn đám cháy là cháy giả?",
"confirm_fake_fire_body": "Bạn hãy kiểm tra thật kỹ để chắc chắn rằng đây chỉ là sự cố bình thường. Đội PCCC sẽ xác nhận đây là đám cháy giả!",
"confirm_fake_fire_sure_message": "Tôi chắc chắn",
"let_PCCC_handle_message": "Hãy để Đội PCCC xử lý!",
"overview_message": "Tổng quan",
"total_nof_devices_message": "Tổng số",
"active_devices_message": "Đang hoạt động",
"inactive_devices_message": "Đang tắt",
"warning_devices_message": "Cảnh báo",
"unused_devices_message": "Không sử dụng",
"description_NOTUSE1": "This is vietnamese language in DeviceManagerPage",
"device_manager_page_name": "Quản lý thiết bị",
"add_device_title": "Thêm thiết bị",
"input_extID_device_input": "Mã thiết bị",
"input_extID_device_hintText": "Nhập mã thiết bị",
"input_name_device_device": "Tên thiết bị",
"input_name_device_hintText": "Nhập tên thiết bị",
"paginated_data_table_title": "Danh sách thiết bị",
"paginated_data_table_column_action": "Thao tác",
"paginated_data_table_column_deviceName": "Tên thiết bị",
"paginated_data_table_column_deviceStatus": "Tình trạng",
"paginated_data_table_column_deviceBaterry": "Mức pin",
"paginated_data_table_column_deviceSignal": "Mức sóng",
"paginated_data_table_column_deviceTemperature": "Nhiệt độ",
"paginated_data_table_column_deviceHump": "Độ ẩm",
"paginated_data_table_column_devicePower": "Nguồn",
"delete_device_dialog_title": "Xóa thiết bị",
"delete_device_dialog_content": "Bạn có chắc chắn muốn xóa thiết bị này?",
"update_device_dialog_title": "Sửa thiết bị",
"update_device_dialog_location_title": "Ví trí thiết bị",
"update_device_dialog_location_longitude": "Kinh độ",
"update_device_dialog_location_latitude": "Vĩ độ",
"update_device_dialog_location_longitude_hintText": "Nhập kinh độ",
"update_device_dialog_location_latitude_hintText": "Nhập vĩ độ",
"update_device_dialog_location_province_hintText": "Chọn Tỉnh/Thành phố",
"update_device_dialog_location_province_searchHint": "Tìm Tỉnh/Thành phố",
"update_device_dialog_location_district_hintText": "Chọn Quận/Huyện",
"update_device_dialog_location_district_searchHint": "Tìm Quận/Huyện",
"update_device_dialog_location_ward_hintText": "Chọn Phường/Xã",
"update_device_dialog_location_ward_searchHint": "Tìm Phường/Xã",
"update_device_dialog_maps_dialog_title": "Cập nhật vị trí",
"update_device_dialog_search_location_hint": "Tìm kiếm địa chỉ",
"description_NOTUSE8": "This is vietnamese language in MapPositionPage",
"map_your_location": "Vị trí của bạn",
"map_show_direction": "Chỉ đường",
"map_nearby_hospital": "Bệnh viện gần đó",
"map_nearest_hospital": "Bệnh viện gần nhất",
"map_nearby_firestation": "Trạm cứu hỏa gần đó",
"map_nearest_firestation": "Trạm cứu hỏa gần nhất",
"map_result": "Kết quả",
"map_always_opened": "Luôn mở cửa",
"map_openning": "Đang mở cửa",
"map_closed": "Đóng cửa",
"map_no_results": "Không tìm thấy kết quả",
"map_start": "Xuất phát",
"map_destination": "Đích đến",
"map_stream": "Trực tiếp",
"description_NOTUSE2": "This is vietnamese language in DeviceLogPage",
"device_log_page_name": "Lịch sử thiết bị",
"choose_device_dropdownButton": "Chọn thiết bị",
"choose_date_start_datePicker": "Bắt đầu từ",
"choose_date_end_datePicker": "Kết thúc",
"main_no_data": "Chưa có dữ liệu.",
"description_NOTUSE3": "This is vietnamese language in InterFamily",
"interfamily_page_name": "Liên gia",
"my_group_title": "Group của tôi",
"invite_group": "Group tham gia",
"add_new_group": "Thêm nhóm mới",
"join_group": "Tham gia nhóm",
"group_name_title": "Tên nhóm",
"group_id_title": "ID nhóm",
"add_new_user_title": "Thêm người dùng",
"share_group_title": "Chia sẻ nhóm",
"change_group_infomation_title": "Đổi thông tin",
"change_group_infomation_content": "Chỉnh sửa thông tin nhóm",
"delete_group_title": "Xóa nhóm",
"delete_group_content": "Bạn chắc chắn muốn xóa nhóm này?",
"leave_group_content": "Bạn chắc chắn muốn rời nhóm?",
"dont_have_group": "Chưa có nhóm",
"dont_join_group": "Bạn chưa tham gia nhóm nào",
"description_group": "Mô tả",
"add_new_device_title": "Thêm thiết bị mới",
"approve_user": "Duyệt thành viên",
"devices_title": "Thiết bị",
"device_title": "Thiết bị",
"member_title": "Thành viên",
"leave_group_title": "Rời nhóm",
"dont_have_device": "Chưa có thiết bị",
"description_NOTUSE4": "This is vietnamese language in ProfilePage",
"profile_page_title": "Cài đặt",
"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",
"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 ",
"change_profile_email": "Email: ",
"change_profile_email_hint": "Nhập email ",
"change_profile_email_not_empty": "Email không được để trống",
"change_profile_tel": "Số điện thoại: ",
"change_profile_tel_hint": "Nhập số điện thoại",
"change_profile_tel_not_empty": "Số điện thoại không được để trống",
"change_profile_address": "Địa chỉ: ",
"change_profile_address_hint": "Nhập địa chỉ",
"change_profile_old_pass": "Mật khẩu cũ: ",
"change_profile_old_pass_hint": "Nhập mật khẩu cũ",
"change_profile_old_pass_not_empty": "Mật khẩu không được để trống",
"change_profile_new_pass": "Mật khẩu mới: ",
"change_profile_new_pass_hint": "Nhập mật khẩu mới",
"change_profile_new_pass_not_empty": "Mật khẩu không được để trống",
"change_profile_device_notification_select_all": "Chọn tất cả",
"change_profile_device_notification_deselect_all": "Bỏ chọn tất cả",
"description_NOTUSE5": "This is vietnamese language in BellPage",
"bell_page_title": "Thông báo",
"bell_page_no_items_body": "Chưa có thông báo",
"bell_user_uppercase": "Người dùng",
"bell_battery_device": "Pin thiết bị",
"bell_user_joined_group": "đã tham gia nhóm",
"bell_leave_group": "đã rời nhóm",
"bell_user_added_group": "đã được thêm vào nhóm",
"bell_user_kick_group": "đã bị xóa khỏi nhóm",
"bell_operate_normal": "hoạt động bình thường",
"bell_invalid_code": "Mã sự kiện không hợp lệ",
"bell_days_ago": "ngày trước",
"bell_hours_ago": "giờ trước",
"bell_minutes_ago": "phút trước",
"bell_just_now": "Vừa xong",
"bell_read_all": "Bạn đã xem hết thông báo",
"description_NOTUSE6": "This is vietnamese language in GlobalFunction",
"gf_newly_create_message": "Mới tạo",
"gf_disconnect_message": "Mất kết nối",
"gf_smoke_detected_message": "Đang hoạt động",
"gf_no_signal_message": "Không có sóng",
"gf_weak_signal_message": "Mức sóng yếu",
"gf_moderate_signal_message": "Mức sóng khá",
"gf_good_signal_message": "Mức sóng tốt",
"gf_volt_detect_message": "Có điện thế",
"gf_temp_detect_message": "Có nhiệt độ",
"gf_hum_detect_message": "Có độ ẩm",
"gf_battery_detect_message": "Có mức pin",
"gf_offline_message": "Không hoạt động",
"gf_in_firefighting_message": "Đang chữa cháy",
"gf_device_error_message": "Thiết bị lỗi",
"gf_not_move_message": "Chưa di chuyển",
"gf_moving_message": "Đã di chuyển",
"gf_remove_from_base_message": "Bị tháo khỏi đế",
"gf_connected_lowercase": "đã kết nối",
"description_NOTUSE7": "This is vietnamese language in LoginPage",
"login_account_not_empty": "Tài khoản không được để trống",
"login_account_hint": "Tài khoản",
"login_password_not_empty": "Mật khẩu không được để trống",
"login_password_hint": "Mật khẩu",
"login_success_message": "Đăng nhập thành công",
"login_incorrect_usernameOrPass": "Tài khoản hoặc mật khẩu không đúng",
"login_button_content": "Đăng nhập",
"description_NOTUSE9": "This is vietnamese language in DeviceUpdatePage",
"device_update_title": "Chỉnh sửa chi tiết thiết bị",
"device_update_location": "Vị trí thiết bị",
"device_update_province": "Tỉnh/Thành phố",
"device_update_district": "Quận/Huyện",
"device_update_ward": "Phường/Xã",
"description_NOTUSE10": "This is vietnamese language in DetailDevicePage",
"detail_device_dont_has_location_message": "Chưa có thông tin về vị trí",
"no_data_message": "Chưa có",
"normal_message": "Bình thường",
"warning_status_message": "Cảnh báo",
"undefine_message": "Không xác định",
"low_message_uppercase": "Yếu",
"moderate_message_uppercase": "Khá",
"good_message_uppercase": "Tốt",
"low_message_lowercase": "yếu",
"moderate_message_lowercase": "khá",
"good_message_lowercase": "tốt",
"error_message_uppercase": "Lỗi",
"error_message_lowercase": "lỗi",
"warning_message": "Cảnh báo:",
"loading_message": "Đang tải...",
"detail_message": "Chi tiết",
"decline_message": "TỪ CHỐI",
"allow_message": "CHO PHÉP",
"add_button_content": "Thêm",
"update_button_content": "Cập nhật",
"change_button_content": "Chỉnh sửa",
"confirm_button_content": "Xác nhận",
"delete_button_content": "Xóa",
"cancel_button_content": "Hủy",
"find_button_content": "Tìm",
"home_page_destination": "Trang chủ",
"manager_page_destination": "Quản lý",
"map_page_destination": "Bản đồ",
"history_page_destination": "Lịch sử",
"history_page_destination_tooltip": "Lịch sử thiết bị",
"group_page_destination": "Nhóm",
"group_page_destination_tooltip": "Trao đổi thông báo thiết bị",
"notification_enter_all_inf": "Vui lòng điền đầy đủ thông tin",
"notification_update_device_success": "Cập nhật thiết bị thành công",
"notification_update_device_failed": "Cập nhật thiết bị thất bại",
"notification_update_device_error": "Cập nhật lỗi",
"notification_cannot_find_address_from_location": "Không tìm được vị trí",
"notification_add_device_success": "Thêm thiết bị thành công",
"notification_add_device_failed": "Thêm thiết bị thất bại",
"notification_create_device_success": "Tạo thiết bị thành công",
"notification_create_device_failed": "Tạo thiết bị thất bại",
"notification_delete_device_success": "Xóa thiết bị thành công",
"notification_delete_device_failed": "Xóa thiết bị thất bại",
"notification_device_not_exist": "Thiết bị không tồn tại",
"notification_add_group_success": "Tạo nhóm thành công",
"notification_add_group_failed": "Tạo nhóm thất bại",
"notification_update_group_success": "Sửa nhóm thành công",
"notification_update_group_failed": "Sửa nhóm thất bại",
"notification_delete_group_success": "Xóa nhóm thành công",
"notification_delete_group_failed": "Xóa nhóm thất bại",
"notification_leave_group_success": "Rời nhóm thành công",
"notification_leave_group_failed": "Rời nhóm thất bại",
"notification_join_request_group_success": "Yêu cầu tham gia nhóm thành công!",
"notification_join_request_group_failed": "Yêu cầu tham gia nhóm thất bại!",
"notification_update_profile_success": "Sửa thông tin thành công",
"notification_update_profile_failed": "Sửa thông tin thất bại",
"notification_update_password_success": "Đổi mật khẩu thành công",
"notification_update_password_failed": "Mật khẩu cũ không khớp",
"notification_update_device_settings_success": "Cập nhật thông báo cho thiết bị thành công",
"notification_update_device_settings_failed": "Cập nhật thông báo cho thiết bị thất bại",
"notification_confirm_fake_fire_success": "Đã cập nhật thông tin đến đội PCCC",
"notification_confirm_fake_fire_failed": "Cập nhật cháy giả thất bại"
}

View File

@@ -0,0 +1,18 @@
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
import 'package:sfm_app/product/constant/lang/language_constants.dart';
class Language {
final int id;
final String imgageIcon;
final String name;
final String languageCode;
Language(this.id, this.imgageIcon, this.name, this.languageCode);
static List<Language> languages() {
return <Language>[
Language(1, IconConstants.instance.getIcon("vi_icon"), "vn", LanguageConstants.VIETNAM),
Language(2, IconConstants.instance.getIcon("en_icon"), "en", LanguageConstants.VIETNAM),
];
}
}

View File

@@ -0,0 +1,114 @@
import 'dart:convert';
import 'dart:developer';
import 'package:sfm_app/product/constant/status_code/status_code_constants.dart';
import '../cache/local_manager.dart';
import '../constant/app/app_constants.dart';
import '../constant/enums/local_keys_enums.dart';
import 'package:http/http.dart' as http;
class NetworkManager {
NetworkManager._init();
static NetworkManager? _instance;
static NetworkManager? get instance => _instance ??= NetworkManager._init();
Future<Map<String, String>> getHeaders() async {
String? token =
LocaleManager.instance.getStringValue(PreferencesKeys.TOKEN);
Map<String, String> headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
"Access-Control-Allow-Credentials": "false",
"Access-Control-Allow-Headers":
"Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale",
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
'Authorization': token,
};
return headers;
}
/// Retrieves data from the server using a GET request.
///
/// [path] is the endpoint for the request. Returns the response body as a
/// [String] if the request is successful (status code 200), or an empty
/// string if the request fails
Future<String> getDataFromServer(String path) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("GET url: $url");
final headers = await getHeaders();
final response = await http.get(url, headers: headers);
if (response.statusCode == StatusCodeConstants.OK ||
response.statusCode == StatusCodeConstants.CREATED) {
return response.body;
} else {
return "";
}
}
/// Sends a GET request to the server with the specified parameters.
///
/// This function constructs a URL from the provided [path] and [params],
/// then sends an HTTP GET request to the server. If the response has a
/// status code of 200, the function returns the response body.
/// Otherwise, it returns an empty string.
///
/// [path] is the endpoint on the server.
/// [params] is a map containing query parameters for the request.
///
/// Returns a [Future<String>] containing the server response body.
Future<String> getDataFromServerWithParams(
String path, Map<String, dynamic> params) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path, params);
log("GET Params url: $url");
final headers = await getHeaders();
final response = await http.get(url, headers: headers);
if (response.statusCode == StatusCodeConstants.CREATED ||
response.statusCode == StatusCodeConstants.OK) {
return response.body;
} else {
return "";
}
}
/// Creates new data on the server using a POST request.
///
/// [path] is the endpoint for the request, and [body] contains the data
/// to be sent. Returns the HTTP status code of the response.
Future<int> createDataInServer(String path, Map<String, dynamic> body) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("POST url: $url");
final headers = await getHeaders();
final response =
await http.post(url, headers: headers, body: jsonEncode(body));
return response.statusCode;
}
/// Updates existing data on the server using a PUT request.
///
/// [path] is the endpoint for the request, and [body] contains the data
/// to be updated. Returns the HTTP status code of the response.
Future<int> updateDataInServer(String path, Map<String, dynamic> body) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("PUT url: $url");
final headers = await getHeaders();
final response =
await http.put(url, headers: headers, body: jsonEncode(body));
return response.statusCode;
}
/// Deletes data from the server using a DELETE request.
///
/// [path] is the endpoint for the request. Returns the HTTP status code
/// of the response, indicating the result of the deletion operation.
/// A status code of 200 indicates success, while other codes indicate
/// failure or an error.
Future<int> deleteDataInServer(String path) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("DELETE url: $url");
final headers = await getHeaders();
final response = await http.delete(url, headers: headers);
return response.statusCode;
}
}

View File

@@ -0,0 +1,31 @@
import 'dart:developer';
import 'package:app_settings/app_settings.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import '../base/widget/dialog/request_permission_dialog.dart';
class NotificationPermission {
FirebaseMessaging messaging = FirebaseMessaging.instance;
void requestNotificationsPermission(BuildContext context) async {
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: true,
criticalAlert: true,
provisional: true,
sound: true);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
log("NotificationsPermission: User granted permission");
} else if (settings.authorizationStatus ==
AuthorizationStatus.provisional) {
log("NotificationsPermission: User granted provisional permission");
} else {
log("NotificationsPermission: User denied permission");
// ignore: use_build_context_synchronously
RequestPermissionDialog().showRequestPermissionDialog(context,
Icons.location_on_outlined, "ABCDE", AppSettingsType.notification);
}
}
}

View File

@@ -0,0 +1,386 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import '../constant/app/api_path_constant.dart';
import '../shared/shared_snack_bar.dart';
import '../constant/enums/app_route_enums.dart';
import 'language_services.dart';
import '../../feature/bell/bell_model.dart';
import '../cache/local_manager.dart';
import '../constant/app/app_constants.dart';
import '../constant/enums/local_keys_enums.dart';
import '../network/network_manager.dart';
class APIServices {
Map<String, String> headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
"Access-Control-Allow-Credentials": "false",
"Access-Control-Allow-Headers":
"Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale",
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
};
Future<Map<String, String>> getHeaders() async {
String? token =
LocaleManager.instance.getStringValue(PreferencesKeys.TOKEN);
Map<String, String> headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
"Access-Control-Allow-Credentials": "false",
"Access-Control-Allow-Headers":
"Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale",
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
'Authorization': token,
};
return headers;
}
Future<String> login(String path, Map<String, dynamic> loginRequest) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
final headers = await getHeaders();
final response =
await http.post(url, headers: headers, body: jsonEncode(loginRequest));
return response.body;
}
Future<void> logOut(BuildContext context) async {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(appLocalization(context).log_out_content,
textAlign: TextAlign.center),
actions: [
TextButton(
onPressed: () async {
var url = Uri.http(ApplicationConstants.DOMAIN,
APIPathConstants.LOGOUT_PATH);
final headers = await NetworkManager.instance!.getHeaders();
final response = await http.post(url, headers: headers);
if (response.statusCode == 200) {
LocaleManager.instance
.deleteStringValue(PreferencesKeys.UID);
LocaleManager.instance
.deleteStringValue(PreferencesKeys.TOKEN);
LocaleManager.instance
.deleteStringValue(PreferencesKeys.EXP);
LocaleManager.instance
.deleteStringValue(PreferencesKeys.ROLE);
context.goNamed(AppRoutes.LOGIN.name);
} else {
showErrorTopSnackBarCustom(
context, "Error: ${response.statusCode}");
}
},
child: Text(appLocalization(context).log_out,
style: const TextStyle(color: Colors.red)),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(appLocalization(context).cancel_button_content),
),
],
));
}
Future<String> getUID() async {
String uid = LocaleManager.instance.getStringValue(PreferencesKeys.UID);
return uid;
}
Future<String> getUserRole() async {
String role = LocaleManager.instance.getStringValue(PreferencesKeys.ROLE);
return role;
}
Future<String> checkTheme() async {
String theme = LocaleManager.instance.getStringValue(PreferencesKeys.THEME);
return theme;
}
Future<String> checkLanguage() async {
String language =
LocaleManager.instance.getStringValue(PreferencesKeys.LANGUAGE_CODE);
return language;
}
Future<Bell> getBellNotifications(String offset, String pagesize) async {
Bell bell = Bell();
final params = {"offset": offset, "page_size": pagesize};
final data = await NetworkManager.instance!.getDataFromServerWithParams(
APIPathConstants.BELL_NOTIFICATIONS_PATH, params);
if (data != "") {
bell = Bell.fromJson(jsonDecode(data));
return bell;
} else {
return bell;
}
}
Future<int> updateStatusOfNotification(List<String> notificationID) async {
Map<String, dynamic> body = {
"event_ids": notificationID,
};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.BELL_UPDATE_READ_NOTIFICATIONS_PATH, body);
return statusCode;
}
Future<String> getUserDetail() async {
String uid = await getUID();
String? response = await NetworkManager.instance!
.getDataFromServer('${APIPathConstants.USER_PATH}/$uid');
return response;
}
Future<int> updateUserProfile(Map<String, dynamic> body) async {
String uid = await getUID();
int statusCode = await NetworkManager.instance!
.updateDataInServer("${APIPathConstants.USER_PROFILE_PATH}/$uid", body);
return statusCode;
}
Future<int> updateUserPassword(Map<String, dynamic> body) async {
String uid = await getUID();
int statusCode = await NetworkManager.instance!.updateDataInServer(
"${APIPathConstants.USER_PATH}/$uid/password", body);
return statusCode;
}
Future<String> getAllSettingsNotificationOfDevices() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.DEVICE_NOTIFICATION_SETTINGS);
return data;
}
Future<int> updateDeviceNotificationSettings(
String thingID, Map<String, int> data) async {
Map<String, dynamic> body = {"thing_id": thingID, "notifi_settings": data};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body);
return statusCode;
}
Future<String> getDashBoardDevices() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.DASHBOARD_DEVICES);
return data;
}
Future<int> setupDeviceNotification(String thingID, String deviceName) async {
Map<String, dynamic> body = {
"thing_id": thingID,
"alias": deviceName,
"notifi_settings": {
"001": 1,
"002": 1,
"003": 1,
"004": 1,
"005": 1,
"006": 1,
"101": 1,
"102": 1,
"103": 1,
"104": 1,
}
};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body);
return statusCode;
}
Future<String> getAllProvinces() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.PROVINCES_PATH);
return data;
}
Future<String> getProvincesByName(String name) async {
final params = {'name': name};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.PROVINCES_PATH, params);
return data;
}
Future<String> getProvinceByID(String provinceID) async {
String data = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.PROVINCES_PATH}/$provinceID");
return data;
}
Future<String> getAllDistricts(String provinceID) async {
final params = {"parent": provinceID};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params);
return data;
}
Future<String> getDistrictsByName(String districtName) async {
final params = {"name": districtName};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params);
return data;
}
Future<String> getDistrictByID(String districtID) async {
String? data = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.DISTRICTS_PATH}/$districtID");
return data;
}
Future<String> getAllWards(String districtID) async {
final params = {'parent': districtID};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params);
return data;
}
Future<String> getWarsdByName(String wardName) async {
final params = {"name": wardName};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params);
return data;
}
Future<String> getWardByID(String wardID) async {
String? data = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.WARDS_PATH}/$wardID");
return data;
}
Future<int> confirmFakeFireByUser(String thingID) async {
Map<String, dynamic> body = {
"state": 3,
"note": "Người dùng xác nhận cháy giả!"
};
int statusCode = await NetworkManager.instance!
.updateDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID", body);
return statusCode;
}
Future<String> getOwnerDevices() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.DEVICE_PATH);
return data;
}
Future<int> createDeviceByAdmin(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_PATH, body);
return statusCode;
}
Future<int> registerDevice(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body);
return statusCode;
}
Future<int> deleteDeviceByAdmin(String thingID) async {
int statusCode = await NetworkManager.instance!
.deleteDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID");
return statusCode;
}
Future<int> unregisterDevice(Map<String, dynamic> body) async {
int statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_UNREGISTER_PATH, body);
return statusCode;
}
Future<String> getDeviceInfomation(String thingID) async {
String? response = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID");
return response;
}
Future<String> getAllGroups() async {
String? body = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.ALL_GROUPS_PATH);
return body;
}
Future<int> createGroup(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.GROUPS_PATH, body);
return statusCode;
}
Future<int> updateGroup(Map<String, dynamic> body, String groupID) async {
int? statusCode = await NetworkManager.instance!
.updateDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID", body);
return statusCode;
}
Future<int> joinGroup(String groupID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body);
return statusCode;
}
Future<int> deleteGroup(String groupID) async {
int? statusCode = await NetworkManager.instance!
.deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID");
return statusCode;
}
Future<String> getGroupDetail(String groupID) async {
String? body = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.GROUPS_PATH}/$groupID");
return body;
}
Future<int> approveGroup(Map<String, dynamic> body) async {
int statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.APPROVE_GROUP_PATH, body);
return statusCode;
}
Future<int> deleteUserInGroup(String groupID, String userID) async {
int? statusCode = await NetworkManager.instance!.deleteDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID");
return statusCode;
}
Future<int> deleteDeviceInGroup(String groupID, String thingID) async {
int? statusCode = await NetworkManager.instance!.deleteDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID");
return statusCode;
}
Future<int> updateDeviceAlias(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body);
return statusCode;
}
Future<int> addDeviceToGroup(
String groupID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!.createDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/things", body);
return statusCode;
}
Future<int> updateOwnerDevice(
String thingID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.updateDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID", body);
return statusCode;
}
Future<String> getLogsOfDevice(
String thingID, Map<String, dynamic> params) async {
String? body = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DEVICE_LOGS_PATH, params);
return body;
}
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import '../cache/local_manager.dart';
import '../constant/enums/local_keys_enums.dart';
import '../constant/lang/language_constants.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LanguageServices {
Future<Locale> setLocale(String languageCode) async {
await LocaleManager.prefrencesInit();
LocaleManager.instance
.setStringValue(PreferencesKeys.LANGUAGE_CODE, languageCode);
return _locale(languageCode);
}
Future<Locale> getLocale() async {
await LocaleManager.prefrencesInit();
String languageCode =
LocaleManager.instance.getStringValue(PreferencesKeys.LANGUAGE_CODE);
return _locale(languageCode);
}
Locale _locale(String languageCode) {
switch (languageCode) {
case LanguageConstants.ENGLISH:
return const Locale(LanguageConstants.ENGLISH, '');
case LanguageConstants.VIETNAM:
return const Locale(LanguageConstants.VIETNAM, '');
default:
return const Locale(LanguageConstants.VIETNAM, '');
}
}
}
AppLocalizations appLocalization(BuildContext context) {
return AppLocalizations.of(context)!;
}

View File

@@ -0,0 +1,63 @@
import 'dart:convert';
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
import '../constant/app/app_constants.dart';
import '../shared/find_location_maps/model/prediction_model.dart';
import '../shared/model/near_by_search_model.dart';
class MapServices {
Future<List<PlaceDetails>> getNearbyPlaces(double latitude, double longitude,
String searchKey, int radius, String type) async {
List<PlaceDetails> result = [];
var url = Uri.parse(
'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$searchKey&language=vi&location=$latitude%2C$longitude&radius=$radius&strictbounds=true&type=$type&key=${ApplicationConstants.MAP_KEY}');
log("URL LIST: $url");
var response = await http.post(url);
final placesAutocompleteResponse =
PlacesAutocompleteResponse.fromJson(jsonDecode(response.body));
if (placesAutocompleteResponse.predictions != null) {
for (int i = 0; i < placesAutocompleteResponse.predictions!.length; i++) {
var url =
"https://maps.googleapis.com/maps/api/place/details/json?placeid=${placesAutocompleteResponse.predictions![i].placeId}&language=vi&key=${ApplicationConstants.MAP_KEY}";
log(url.toString());
Response response = await Dio().get(
url,
);
PlaceDetails placeDetails = PlaceDetails.fromJson(response.data);
result.add(placeDetails);
// displayLocation(placesAutocompleteResponse.predictions![i], i);
}
return result;
} else {
log("null");
return [];
}
}
Future<List<LatLng>> findTheWay(LatLng origin, LatLng destination) async {
List<LatLng> polylineCoordinates = [];
PolylinePoints polylinePoints = PolylinePoints();
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
ApplicationConstants.MAP_KEY,
PointLatLng(origin.latitude, origin.longitude),
PointLatLng(destination.latitude, destination.longitude),
travelMode: TravelMode.driving,
optimizeWaypoints: true);
if (result.points.isNotEmpty) {
for (var point in result.points) {
polylineCoordinates.add(LatLng(point.latitude, point.longitude));
}
return polylineCoordinates;
} else {
log("Lỗi khi tìm đường");
return [];
}
}
}

View File

@@ -0,0 +1,103 @@
import 'dart:developer' as dev;
import 'dart:math' as math;
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationServices {
FirebaseMessaging messaging = FirebaseMessaging.instance;
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
void firebaseInit(BuildContext context) async {
FirebaseMessaging.onMessage.listen((message) {
initNotifications(context, message);
showNotification(message);
dev.log(
"Title: ${message.notification!.title}, Body: ${message.notification!.body} from ${message.sentTime}");
});
}
Future<String> getDeviceToken() async {
String? token = await messaging.getToken();
return token!;
}
void isTokenRefresh() async {
messaging.onTokenRefresh.listen((newToken) {
dev.log("Refresh Firebase Messaging Token: $newToken");
});
}
void initNotifications(BuildContext context, RemoteMessage message) async {
var androidInitializationSettings =
const AndroidInitializationSettings('app_icon');
var iosInitializationSettings = const DarwinInitializationSettings();
var initializationSettings = InitializationSettings(
android: androidInitializationSettings, iOS: iosInitializationSettings);
await _flutterLocalNotificationsPlugin.initialize(initializationSettings,
onDidReceiveNotificationResponse: (payload) {
handleMessage(context, message);
});
}
Future<void> showNotification(RemoteMessage message) async {
AndroidNotificationChannel androidNotificationChannel =
AndroidNotificationChannel(
math.Random.secure().nextInt(1000000).toString(),
'high Important Notification',
importance: Importance.max);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
androidNotificationChannel.id.toString(),
androidNotificationChannel.name.toString(),
sound: RawResourceAndroidNotificationSound(message.data['sound']),
channelDescription: "Channel description",
importance: androidNotificationChannel.importance,
priority: Priority.high,
ticker: 'ticker',
);
const DarwinNotificationDetails darwinNotificationDetails =
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentBanner: true,
presentSound: true,
);
NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails, iOS: darwinNotificationDetails);
Future.delayed(Duration.zero, () {
_flutterLocalNotificationsPlugin.show(0, message.notification!.title!,
message.notification!.body, notificationDetails);
});
}
void handleMessage(BuildContext context, RemoteMessage message) async {
if (message.data['type'] == "msj") {
// Navigator.push(context,
// MaterialPageRoute(builder: (context) => const MessageScreen()));
} else if (message.data['type'] == "warn") {
// Navigator.push(
// context, MaterialPageRoute(builder: (context) => const MapScreen()));
} else {
dev.log("Not found data");
}
}
Future<void> setupInteractMessage(BuildContext context) async {
// When app terminate
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
// showNotification(initialMessage);
// ignore: use_build_context_synchronously
handleMessage(context, initialMessage);
}
// When app is inBackGround
FirebaseMessaging.onMessageOpenedApp.listen((message) {
handleMessage(context, message);
});
}
}

View File

@@ -0,0 +1,137 @@
import 'package:dio/dio.dart';
import 'error_response_model.dart';
class DioErrorHandler {
ErrorResponse errorResponse = ErrorResponse();
String errorDescription = "";
ErrorResponse handleDioError(DioException dioError) {
switch (dioError.type) {
case DioExceptionType.cancel:
errorResponse.message = "Request to API server was cancelled";
break;
case DioExceptionType.connectionTimeout:
errorResponse.message = "Connection timeout with API server";
break;
case DioExceptionType.unknown:
if ((dioError.message!.contains("RedirectException"))) {
errorResponse.message = dioError.message;
} else {
errorResponse.message = "Please check the internet connection";
}
break;
case DioExceptionType.receiveTimeout:
errorResponse.message = "Receive timeout in connection with API server";
break;
case DioExceptionType.badResponse:
try {
if (dioError.response?.data['message'] != null) {
errorResponse.message = dioError.response?.data['message'];
} else {
if ((dioError.response?.statusMessage ?? "").isNotEmpty) {
errorResponse.message = dioError.response?.statusMessage;
} else {
return _handleError(
dioError.response!.statusCode, dioError.response!.data);
}
}
} catch (e) {
if ((dioError.response?.statusMessage ?? "").isNotEmpty) {
errorResponse.message = dioError.response?.statusMessage;
} else {
return _handleError(
dioError.response!.statusCode, dioError.response!.data);
}
}
break;
case DioExceptionType.sendTimeout:
errorResponse.message = "Send timeout in connection with API server";
break;
default:
errorResponse.message = "Something went wrong";
break;
}
return errorResponse;
}
ErrorResponse _handleError(int? statusCode, dynamic error) {
switch (statusCode) {
case 400:
return getMas(error);
// case 401:
// return checkTokenExpire(error);
case 404:
return getMas(error);
case 403:
return getMas(error);
case 500:
errorResponse.message = 'Internal server error';
return errorResponse;
default:
return getUnKnownMes(error);
}
}
// checkTokenExpire(error) {
// // print("my error ${error}");
// if (error['msg'].toString().toLowerCase() ==
// "Token has expired".toLowerCase()) {
// UIData.tokenExpire(error['msg']);
// return;
// }
// errorResponse.message = error['msg'].toString();
// return errorResponse;
// }
getMas(dynamic error) {
print("myError ${error.runtimeType}");
if (error.runtimeType != String) {
errorResponse.message =
error['message'].toString(); //?? S.of(Get.context).something_wrong;
} else {
if (error['msg'] != null) {
errorResponse.message = error['msg'].toString();
} else {
errorResponse.message = "Something Wrong";
} //S.of(Get.context).something_wrong;
}
return errorResponse;
}
getUnKnownMes(dynamic error) {
if (error['msg'] != null) {
errorResponse.message = error['msg'].toString();
} else if (error['message'] != null) {
errorResponse.message = error['message'].toString();
} else {
errorResponse.message = "Something went wrong";
}
return errorResponse;
}
}
class ErrorHandler {
static final ErrorHandler _inst = ErrorHandler.internal();
ErrorHandler.internal();
factory ErrorHandler() {
return _inst;
}
ErrorResponse errorResponse = ErrorResponse();
ErrorResponse handleError(var error) {
if (error.runtimeType.toString().toLowerCase() ==
"_TypeError".toLowerCase()) {
// return error.toString();
errorResponse.message = "The Provided API key is invalid";
return errorResponse;
} else if (error is DioError) {
return DioErrorHandler().handleDioError(error);
}
errorResponse.message = "The Provided API key is invalid";
return errorResponse;
}
}

View File

@@ -0,0 +1,19 @@
class ErrorResponse {
String? message;
int? status;
ErrorResponse({this.message, this.status});
ErrorResponse.fromJson(Map<String, dynamic> json) {
message = json['message'];
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['message'] = message;
data['status'] = status;
return data;
}
}

View File

@@ -0,0 +1,234 @@
class PlaceDetails {
Result? result;
String? status;
PlaceDetails({this.result, this.status});
PlaceDetails.fromJson(Map<String, dynamic> json) {
result =
json['result'] != null ? Result.fromJson(json['result']) : null;
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (result != null) {
data['result'] = result!.toJson();
}
data['status'] = status;
return data;
}
}
class Result {
List<AddressComponents>? addressComponents;
String? adrAddress;
String? formattedAddress;
Geometry? geometry;
String? icon;
String? name;
List<Photos>? photos;
String? placeId;
String? reference;
String? scope;
List<String>? types;
String? url;
int? utcOffset;
String? vicinity;
String? website;
Result(
{this.addressComponents,
this.adrAddress,
this.formattedAddress,
this.geometry,
this.icon,
this.name,
this.photos,
this.placeId,
this.reference,
this.scope,
this.types,
this.url,
this.utcOffset,
this.vicinity,
this.website});
Result.fromJson(Map<String, dynamic> json) {
if (json['address_components'] != null) {
addressComponents = [];
json['address_components'].forEach((v) {
addressComponents!.add(AddressComponents.fromJson(v));
});
}
adrAddress = json['adr_address'];
formattedAddress = json['formatted_address'];
geometry = json['geometry'] != null
? Geometry.fromJson(json['geometry'])
: null;
icon = json['icon'];
name = json['name'];
if (json['photos'] != null) {
photos = [];
json['photos'].forEach((v) {
photos!.add(Photos.fromJson(v));
});
}
placeId = json['place_id'];
reference = json['reference'];
scope = json['scope'];
types = json['types'].cast<String>();
url = json['url'];
utcOffset = json['utc_offset'];
vicinity = json['vicinity'];
website = json['website'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (addressComponents != null) {
data['address_components'] =
addressComponents!.map((v) => v.toJson()).toList();
}
data['adr_address'] = adrAddress;
data['formatted_address'] = formattedAddress;
if (geometry != null) {
data['geometry'] = geometry!.toJson();
}
data['icon'] = icon;
data['name'] = name;
if (photos != null) {
data['photos'] = photos!.map((v) => v.toJson()).toList();
}
data['place_id'] = placeId;
data['reference'] = reference;
data['scope'] = scope;
data['types'] = types;
data['url'] = url;
data['utc_offset'] = utcOffset;
data['vicinity'] = vicinity;
data['website'] = website;
return data;
}
}
class AddressComponents {
String? longName;
String? shortName;
List<String>? types;
AddressComponents({this.longName, this.shortName, this.types});
AddressComponents.fromJson(Map<String, dynamic> json) {
longName = json['long_name'];
shortName = json['short_name'];
types = json['types'].cast<String>();
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['long_name'] = longName;
data['short_name'] = shortName;
data['types'] = types;
return data;
}
}
class Geometry {
Location? location;
Viewport? viewport;
Geometry({this.location, this.viewport});
Geometry.fromJson(Map<String, dynamic> json) {
location = json['location'] != null
? Location.fromJson(json['location'])
: null;
viewport = json['viewport'] != null
? Viewport.fromJson(json['viewport'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (location != null) {
data['location'] = location!.toJson();
}
if (viewport != null) {
data['viewport'] = viewport!.toJson();
}
return data;
}
}
class Location {
double? lat;
double? lng;
Location({this.lat, this.lng});
Location.fromJson(Map<String, dynamic> json) {
lat = json['lat'];
lng = json['lng'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['lat'] = lat;
data['lng'] = lng;
return data;
}
}
class Viewport {
Location? northeast;
Location? southwest;
Viewport({this.northeast, this.southwest});
Viewport.fromJson(Map<String, dynamic> json) {
northeast = json['northeast'] != null
? Location.fromJson(json['northeast'])
: null;
southwest = json['southwest'] != null
? Location.fromJson(json['southwest'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (northeast != null) {
data['northeast'] = northeast!.toJson();
}
if (southwest != null) {
data['southwest'] = southwest!.toJson();
}
return data;
}
}
class Photos {
int? height;
List<String>? htmlAttributions;
String? photoReference;
int? width;
Photos({this.height, this.htmlAttributions, this.photoReference, this.width});
Photos.fromJson(Map<String, dynamic> json) {
height = json['height'];
htmlAttributions = json['html_attributions'].cast<String>();
photoReference = json['photo_reference'];
width = json['width'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['height'] = height;
data['html_attributions'] = htmlAttributions;
data['photo_reference'] = photoReference;
data['width'] = width;
return data;
}
}

View File

@@ -0,0 +1,157 @@
class PlacesAutocompleteResponse {
List<Prediction>? predictions;
String? status;
PlacesAutocompleteResponse({this.predictions, this.status});
PlacesAutocompleteResponse.fromJson(Map<String, dynamic> json) {
if (json['predictions'] != null) {
predictions = [];
json['predictions'].forEach((v) {
predictions!.add(Prediction.fromJson(v));
});
}
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (predictions != null) {
data['predictions'] = predictions!.map((v) => v.toJson()).toList();
}
data['status'] = status;
return data;
}
}
class Prediction {
String? description;
String? id;
List<MatchedSubstrings>? matchedSubstrings;
String? placeId;
String? reference;
StructuredFormatting? structuredFormatting;
List<Terms>? terms;
List<String>? types;
String? lat;
String? lng;
Prediction(
{this.description,
this.id,
this.matchedSubstrings,
this.placeId,
this.reference,
this.structuredFormatting,
this.terms,
this.types,
this.lat,
this.lng});
Prediction.fromJson(Map<String, dynamic> json) {
description = json['description'];
id = json['id'];
if (json['matched_substrings'] != null) {
matchedSubstrings = [];
json['matched_substrings'].forEach((v) {
matchedSubstrings!.add(MatchedSubstrings.fromJson(v));
});
}
placeId = json['place_id'];
reference = json['reference'];
structuredFormatting = json['structured_formatting'] != null
? StructuredFormatting.fromJson(json['structured_formatting'])
: null;
if (json['terms'] != null) {
terms = [];
json['terms'].forEach((v) {
terms!.add(Terms.fromJson(v));
});
}
types = json['types'].cast<String>();
lat = json['lat'];
lng = json['lng'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['description'] = description;
data['id'] = id;
if (matchedSubstrings != null) {
data['matched_substrings'] =
matchedSubstrings!.map((v) => v.toJson()).toList();
}
data['place_id'] = placeId;
data['reference'] = reference;
if (structuredFormatting != null) {
data['structured_formatting'] = structuredFormatting!.toJson();
}
if (terms != null) {
data['terms'] = terms!.map((v) => v.toJson()).toList();
}
data['types'] = types;
data['lat'] = lat;
data['lng'] = lng;
return data;
}
}
class MatchedSubstrings {
int? length;
int? offset;
MatchedSubstrings({this.length, this.offset});
MatchedSubstrings.fromJson(Map<String, dynamic> json) {
length = json['length'];
offset = json['offset'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['length'] = length;
data['offset'] = offset;
return data;
}
}
class StructuredFormatting {
String? mainText;
String? secondaryText;
StructuredFormatting({this.mainText, this.secondaryText});
StructuredFormatting.fromJson(Map<String, dynamic> json) {
mainText = json['main_text'];
secondaryText = json['secondary_text'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['main_text'] = mainText;
data['secondary_text'] = secondaryText;
return data;
}
}
class Terms {
int? offset;
String? value;
Terms({this.offset, this.value});
Terms.fromJson(Map<String, dynamic> json) {
offset = json['offset'];
value = json['value'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['offset'] = offset;
data['value'] = value;
return data;
}
}

View File

@@ -0,0 +1,313 @@
// ignore_for_file: unnecessary_this, use_build_context_synchronously, prefer_typing_uninitialized_variables, library_private_types_in_public_api
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:rxdart/rxdart.dart';
import 'error/dio_handle_error.dart';
import 'model/place_detail_model.dart';
import 'model/prediction_model.dart';
// ignore: must_be_immutable
class NearBySearchSFM extends StatefulWidget {
InputDecoration inputDecoration;
ItemClick? itemClick;
GetPlaceDetailswWithLatLng? getPlaceDetailWithLatLng;
bool isLatLngRequired = true;
double locationLatitude;
double locationLongitude;
int radius;
TextStyle textStyle;
String googleAPIKey;
int debounceTime = 600;
List<String>? countries = [];
TextEditingController textEditingController = TextEditingController();
ListItemBuilder? itemBuilder;
TextInputAction? textInputAction;
Widget? seperatedBuilder;
void clearData;
BoxDecoration? boxDecoration;
bool isCrossBtnShown;
bool showError;
NearBySearchSFM(
{super.key,
required this.textEditingController,
required this.googleAPIKey,
required this.locationLatitude,
required this.locationLongitude,
required this.radius,
this.debounceTime = 600,
this.inputDecoration = const InputDecoration(),
this.itemClick,
this.isLatLngRequired = true,
this.textStyle = const TextStyle(),
this.countries,
this.getPlaceDetailWithLatLng,
this.itemBuilder,
this.boxDecoration,
this.isCrossBtnShown = true,
this.seperatedBuilder,
this.showError = true,
this.textInputAction});
@override
_NearBySearchSFMState createState() =>
_NearBySearchSFMState();
}
class _NearBySearchSFMState
extends State<NearBySearchSFM> {
final subject = PublishSubject<String>();
OverlayEntry? _overlayEntry;
List<Prediction> alPredictions = [];
TextEditingController controller = TextEditingController();
final LayerLink _layerLink = LayerLink();
bool isSearched = false;
bool isCrossBtn = true;
late var _dio;
CancelToken? _cancelToken = CancelToken();
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
alignment: Alignment.centerLeft,
decoration: widget.boxDecoration ??
BoxDecoration(
shape: BoxShape.rectangle,
border: Border.all(color: Colors.grey, width: 0.6),
borderRadius: const BorderRadius.all(Radius.circular(10))),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: TextFormField(
decoration: widget.inputDecoration,
style: widget.textStyle,
controller: widget.textEditingController,
onChanged: (string) {
subject.add(string);
if (widget.isCrossBtnShown) {
isCrossBtn = string.isNotEmpty ? true : false;
setState(() {});
}
},
),
),
(!widget.isCrossBtnShown)
? const SizedBox()
: isCrossBtn && _showCrossIconWidget()
? IconButton(onPressed: clearData, icon: Icon(Icons.close))
: const SizedBox()
],
),
),
);
}
getLocation(
String text, double latitude, double longitude, int radius) async {
// String url =
// "https://maps.googleapis.com/maps/api/place/nearbysearch/json?keyword=$text&location=$latitude%2C$longitude&radius=$radius&type=hospital|pharmacy|doctor&key=${widget.googleAPIKey}";
String url =
"https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$text&location=$latitude%2C$longitude&radius=$radius&strictbounds=true&key=${widget.googleAPIKey}";
log(url);
if (widget.countries != null) {
for (int i = 0; i < widget.countries!.length; i++) {
String country = widget.countries![i];
if (i == 0) {
url = url + "&components=country:$country";
} else {
url = url + "|" + "country:" + country;
}
}
}
if (_cancelToken?.isCancelled == false) {
_cancelToken?.cancel();
_cancelToken = CancelToken();
}
try {
Response response = await _dio.get(url);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
Map map = response.data;
if (map.containsKey("error_message")) {
throw response.data;
}
PlacesAutocompleteResponse subscriptionResponse =
PlacesAutocompleteResponse.fromJson(response.data);
if (text.length == 0) {
alPredictions.clear();
this._overlayEntry!.remove();
return;
}
isSearched = false;
alPredictions.clear();
if (subscriptionResponse.predictions!.isNotEmpty &&
(widget.textEditingController.text.toString().trim()).isNotEmpty) {
alPredictions.addAll(subscriptionResponse.predictions!);
}
this._overlayEntry = null;
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry!);
} catch (e) {
var errorHandler = ErrorHandler.internal().handleError(e);
_showSnackBar("${errorHandler.message}");
}
}
@override
void initState() {
super.initState();
_dio = Dio();
subject.stream
.distinct()
.debounceTime(Duration(milliseconds: widget.debounceTime))
.listen(textChanged);
}
textChanged(String text) async {
getLocation(text, widget.locationLatitude, widget.locationLongitude,
widget.radius);
}
OverlayEntry? _createOverlayEntry() {
if (context.findRenderObject() != null) {
RenderBox renderBox = context.findRenderObject() as RenderBox;
var size = renderBox.size;
var offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(
builder: (context) => Positioned(
left: offset.dx,
top: size.height + offset.dy,
width: size.width,
child: CompositedTransformFollower(
showWhenUnlinked: false,
link: this._layerLink,
offset: Offset(0.0, size.height + 5.0),
child: Material(
child: ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: alPredictions.length,
separatorBuilder: (context, pos) =>
widget.seperatedBuilder ?? SizedBox(),
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
var selectedData = alPredictions[index];
if (index < alPredictions.length) {
widget.itemClick!(selectedData);
if (widget.isLatLngRequired) {
getPlaceDetailsFromPlaceId(selectedData);
}
removeOverlay();
}
},
child: widget.itemBuilder != null
? widget.itemBuilder!(
context, index, alPredictions[index])
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10)),
padding: const EdgeInsets.all(5),
child: Text(alPredictions[index].description!)),
);
},
)),
),
));
}
return null;
}
removeOverlay() {
alPredictions.clear();
this._overlayEntry = this._createOverlayEntry();
Overlay.of(context).insert(this._overlayEntry!);
this._overlayEntry!.markNeedsBuild();
}
Future<Response?> getPlaceDetailsFromPlaceId(Prediction prediction) async {
//String key = GlobalConfiguration().getString('google_maps_key');
var url =
"https://maps.googleapis.com/maps/api/place/details/json?placeid=${prediction.placeId}&key=${widget.googleAPIKey}";
Response response = await Dio().get(
url,
);
PlaceDetails placeDetails = PlaceDetails.fromJson(response.data);
prediction.lat = placeDetails.result!.geometry!.location!.lat.toString();
prediction.lng = placeDetails.result!.geometry!.location!.lng.toString();
widget.getPlaceDetailWithLatLng!(prediction);
return null;
}
void clearData() {
widget.textEditingController.clear();
if (_cancelToken?.isCancelled == false) {
_cancelToken?.cancel();
}
setState(() {
alPredictions.clear();
isCrossBtn = false;
});
if (this._overlayEntry != null) {
try {
this._overlayEntry?.remove();
} catch (e) {}
}
}
_showCrossIconWidget() {
return (widget.textEditingController.text.isNotEmpty);
}
_showSnackBar(String errorData) {
if (widget.showError) {
final snackBar = SnackBar(
content: Text("$errorData"),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
}
PlacesAutocompleteResponse parseResponse(Map responseBody) {
return PlacesAutocompleteResponse.fromJson(
responseBody as Map<String, dynamic>);
}
PlaceDetails parsePlaceDetailMap(Map responseBody) {
return PlaceDetails.fromJson(responseBody as Map<String, dynamic>);
}
typedef ItemClick = void Function(Prediction postalCodeResponse);
typedef GetPlaceDetailswWithLatLng = void Function(
Prediction postalCodeResponse);
typedef ListItemBuilder = Widget Function(
BuildContext context, int index, Prediction prediction);

View File

@@ -0,0 +1,43 @@
class District {
String? code;
String? name;
String? nameEn;
String? fullName;
String? fullNameEn;
String? codeName;
String? provinceCode;
String? type;
District({
this.code,
this.name,
this.nameEn,
this.fullName,
this.fullNameEn,
this.codeName,
this.provinceCode,
this.type,
});
District.fromJson(Map<String, dynamic> json) {
code = json['code'];
name = json['name'];
nameEn = json['name_en'];
fullName = json['full_name'];
fullNameEn = json['full_name_en'];
codeName = json['code_name'];
provinceCode = json['parent'];
type = json['type'];
}
static List<District> fromJsonList(List list) {
return list.map((e) => District.fromJson(e)).toList();
}
static List<District> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => District.fromJson(e)).toList();
}
// @override
// String toString() => fullName!;
}

View File

@@ -0,0 +1,172 @@
class NearbySearch {
List<Result>? results;
String? status;
NearbySearch({
this.results,
this.status,
});
NearbySearch.fromJson(Map<String, dynamic> json) {
if (json['results'] != null) {
results = [];
json['results'].forEach((v) {
results!.add(Result.fromJson(v));
});
}
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (results != null) {
data['result'] = results!.map((v) => v.toJson()).toList();
}
data['status'] = status;
return data;
}
}
class PlaceDetails {
Result? result;
String? status;
PlaceDetails({this.result, this.status});
PlaceDetails.fromJson(Map<String, dynamic> json) {
result = json['result'] != null ? Result.fromJson(json['result']) : null;
status = json['status'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (result != null) {
data['result'] = result!.toJson();
}
data['status'] = status;
return data;
}
static List<PlaceDetails> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => PlaceDetails.fromJson(e)).toList();
}
}
class Result {
Geometry? geometry;
String? icon;
String? name;
String? placeId;
String? vicinity;
String? adress;
String? formattedAddress;
String? formattedPhoneNumber;
String? phoneNumber;
OpeningHours? openingHours;
Result({
this.geometry,
this.icon,
this.name,
this.placeId,
this.vicinity,
this.formattedPhoneNumber,
this.phoneNumber,
this.openingHours,
this.adress,
this.formattedAddress,
});
Result.fromJson(Map<String, dynamic> json) {
geometry =
json['geometry'] != null ? Geometry.fromJson(json['geometry']) : null;
icon = json['icon'];
name = json['name'];
placeId = json['place_id'];
vicinity = json['vicinity'];
adress = json['adr_address'];
formattedPhoneNumber = json['formatted_phone_number'] ?? "";
phoneNumber = json['international_phone_number'] ?? "";
formattedAddress = json['formatted_address'];
openingHours = json['opening_hours'] != null
? OpeningHours.fromJson(json['opening_hours'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (geometry != null) {
data['geometry'] = geometry!.toJson();
}
data['icon'] = icon;
data['name'] = name;
data['vicinity'] = vicinity;
data['adr_address'] = adress;
data['formatted_address'] = formattedAddress;
data['international_phone_number'] = phoneNumber;
data['formatted_phone_number'] = formattedPhoneNumber;
if (openingHours != null) {
data['opening_hours'] = openingHours!.toJson();
}
return data;
}
}
class Geometry {
Location? location;
Geometry({
this.location,
});
Geometry.fromJson(Map<String, dynamic> json) {
location =
json['location'] != null ? Location.fromJson(json['location']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (location != null) {
data['location'] = location!.toJson();
}
return data;
}
}
class Location {
double? lat;
double? lng;
Location({
this.lat,
this.lng,
});
Location.fromJson(Map<String, dynamic> json) {
lat = json['lat'];
lng = json['lng'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['lat'] = lat;
data['lng'] = lng;
return data;
}
}
class OpeningHours {
bool? openNow;
OpeningHours({
this.openNow,
});
OpeningHours.fromJson(Map<String, dynamic> json) {
openNow = json['open_now'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['open_now'] = openNow;
return data;
}
}

View File

@@ -0,0 +1,42 @@
class Province {
String? code;
String? name;
String? nameEn;
String? fullName;
String? fullNameEn;
String? codeName;
String? parent;
String? type;
Province(
{this.code,
this.name,
this.nameEn,
this.fullName,
this.fullNameEn,
this.codeName,
this.parent,
this.type});
Province.fromJson(Map<String, dynamic> json) {
code = json['code'];
name = json['name'];
nameEn = json['name_en'];
fullName = json['full_name'];
fullNameEn = json['full_name_en'];
codeName = json['code_name'];
parent = json['parent'];
type = json['type'];
}
static List<Province> fromJsonList(List list) {
return list.map((e) => Province.fromJson(e)).toList();
}
static List<Province> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => Province.fromJson(e)).toList();
}
@override
String toString() => fullName!;
}

View File

@@ -0,0 +1,43 @@
class Ward {
String? code;
String? name;
String? nameEn;
String? fullName;
String? fullNameEn;
String? codeName;
String? districtCode;
String? type;
Ward({
this.code,
this.name,
this.nameEn,
this.fullName,
this.fullNameEn,
this.codeName,
this.districtCode,
this.type,
});
Ward.fromJson(Map<String, dynamic> json) {
code = json['code'];
name = json['name'];
nameEn = json['name_en'];
fullName = json['full_name'];
fullNameEn = json['full_name_en'];
codeName = json['code_name'];
districtCode = json['parent'];
type = json['type'];
}
static List<Ward> fromJsonList(List list) {
return list.map((e) => Ward.fromJson(e)).toList();
}
static List<Ward> fromJsonDynamicList(List<dynamic> list) {
return list.map((e) => Ward.fromJson(e)).toList();
}
@override
String toString() => fullName!;
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/constant/image/image_constants.dart';
class SharedBackground extends StatelessWidget {
final Widget child;
const SharedBackground({super.key, required this.child});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Directionality(
textDirection: TextDirection.ltr,
child: Scaffold(
resizeToAvoidBottomInset: false,
body: SizedBox(
height: size.height,
width: double.infinity,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: Image.asset(
ImageConstants.instance.getImage("background_top"),
width: size.width * 0.3,
),
),
Positioned(
bottom: 0,
right: 0,
child: Image.asset(
ImageConstants.instance.getImage("background_bottom"),
width: size.width * 0.4,
),
),
SafeArea(child: child)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import '../extention/context_extention.dart';
InputDecoration borderRadiusTopLeftAndBottomRight(
BuildContext context, String hintText) =>
InputDecoration(
hintText: hintText,
border: OutlineInputBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12), bottomRight: Radius.circular(12)),
));
InputDecoration borderRadiusAll(BuildContext context, String hintText) =>
InputDecoration(
hintText: hintText,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(context.normalRadius),
));

View File

@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/extention/context_extention.dart';
import 'package:top_snackbar_flutter/custom_snack_bar.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart';
void showNoIconTopSnackBar(BuildContext context, String message,
Color backgroundColor, Color textColor) {
if (!context.mounted) return;
showTopSnackBar(
Overlay.of(context),
Card(
color: backgroundColor,
child: Container(
margin: const EdgeInsets.only(left: 20),
height: 50,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
message,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: textColor,
),
),
),
),
),
displayDuration: context.lowDuration);
}
void showSuccessTopSnackBarCustom(BuildContext context, String message) {
if (!context.mounted) return;
showTopSnackBar(
Overlay.of(context),
SizedBox(
height: 60,
child: CustomSnackBar.success(
message: message,
icon: const Icon(Icons.sentiment_very_satisfied_outlined,
color: Color(0x15000000), size: 80))),
displayDuration: context.lowDuration);
}
void showErrorTopSnackBarCustom(BuildContext context, String message) {
if (!context.mounted) return;
showTopSnackBar(
Overlay.of(context),
SizedBox(
height: 60,
child: CustomSnackBar.error(
message: message,
icon: const Icon(Icons.sentiment_dissatisfied_outlined,
color: Color(0x15000000), size: 80))),
displayDuration: context.lowDuration);
}

View File

@@ -0,0 +1,137 @@
import 'package:flutter/material.dart';
/// Hiệu ứng di chuyển từ phải qua trái
Widget transitionsRightToLeft(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ trái sang phải
Widget transitionsLeftToRight(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(-1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ dưới lên trên
Widget transitionsBottomToTop(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ trên xuống dưới
Widget transitionsTopToBottom(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = Offset(0.0, -1.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
}
/// Hiệu ứng mở dần vào và ra
Widget transitionsFaded(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = 0.0;
const end = 1.0;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var fadeAnimation = animation.drive(tween);
return FadeTransition(
opacity: fadeAnimation,
child: child,
);
}
/// Hiệu ứng di chuyển từ kích thước nhỏ đến đầy đủ
Widget transitionsScale(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = 0.0; // Bắt đầu từ kích thước 0
const end = 1.0; // Kết thúc ở kích thước 1
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var scaleAnimation = animation.drive(tween);
return ScaleTransition(
scale: scaleAnimation,
child: child,
);
}
Widget transitionsTotation(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const begin = 0.0; // Bắt đầu từ góc 0 độ
const end = 1.0; // Kết thúc ở góc 1 vòng (360 độ)
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var rotationAnimation = animation.drive(tween);
return RotationTransition(
turns: rotationAnimation,
child: child,
);
}
/// Hiệu ứng kết hợp (Di chuyển và mờ dần)
Widget transitionsCustom1(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
const beginOffset = Offset(1.0, 0.0); // Di chuyển từ phải vào
const endOffset = Offset.zero;
const beginOpacity = 0.0; // Bắt đầu từ độ mờ 0
const endOpacity = 1.0; // Kết thúc ở độ mờ 1
var offsetTween = Tween(begin: beginOffset, end: endOffset);
var opacityTween = Tween(begin: beginOpacity, end: endOpacity);
var offsetAnimation =
animation.drive(offsetTween.chain(CurveTween(curve: Curves.easeInOut)));
var opacityAnimation =
animation.drive(opacityTween.chain(CurveTween(curve: Curves.easeInOut)));
return FadeTransition(
opacity: opacityAnimation,
child: SlideTransition(
position: offsetAnimation,
child: child,
),
);
}

View File

@@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
abstract class AppTheme {
ThemeData? theme;
}

View File

@@ -0,0 +1,53 @@
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/theme/app_theme.dart';
class AppThemeDark extends AppTheme {
static AppThemeDark? _instance;
static AppThemeDark get instance {
_instance ??= AppThemeDark._init();
return _instance!;
}
AppThemeDark._init();
@override
ThemeData get theme => FlexThemeData.dark(
useMaterial3: true,
scheme: FlexScheme.flutterDash,
subThemesData: const FlexSubThemesData(
inputDecoratorRadius: 30,
interactionEffects: true,
tintedDisabledControls: true,
blendOnColors: true,
useM2StyleDividerInM3: true,
inputDecoratorBorderType: FlexInputBorderType.outline,
tooltipRadius: 20,
tooltipWaitDuration: Duration(milliseconds: 500),
tooltipShowDuration: Duration(milliseconds: 500),
navigationRailUseIndicator: true,
navigationRailLabelType: NavigationRailLabelType.all,
),
visualDensity: FlexColorScheme.comfortablePlatformDensity,
);
// ThemeData.dark().copyWith(
// useMaterial3: true,
// colorScheme: _buildColorScheme,
// );
// ColorScheme get _buildColorScheme => FlexColorScheme.dark().toScheme;
// ColorScheme(
// brightness: Brightness.dark,
// primary: Colors.blue.shade900,
// onPrimary: Colors.blue,
// secondary: Colors.white,
// onSecondary: Colors.white70,
// error: Colors.red,
// onError: Colors.orange,
// background: Colors.black,
// onBackground: Colors.white70,
// surface: Colors.grey,
// onSurface: Colors.white,
// );
}

View File

@@ -0,0 +1,54 @@
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/theme/app_theme.dart';
class AppThemeLight extends AppTheme {
static AppThemeLight? _instance;
static AppThemeLight get instance {
_instance ??= AppThemeLight._init();
return _instance!;
}
AppThemeLight._init();
@override
ThemeData get theme => FlexThemeData.light(
useMaterial3: true,
scheme: FlexScheme.flutterDash,
bottomAppBarElevation: 20.0,
subThemesData: const FlexSubThemesData(
inputDecoratorRadius: 30,
interactionEffects: true,
tintedDisabledControls: true,
useM2StyleDividerInM3: true,
inputDecoratorBorderType: FlexInputBorderType.outline,
tooltipRadius: 20,
tooltipWaitDuration: Duration(milliseconds: 1600),
tooltipShowDuration: Duration(milliseconds: 1500),
navigationRailUseIndicator: true,
navigationRailLabelType: NavigationRailLabelType.all,
),
visualDensity: FlexColorScheme.comfortablePlatformDensity,
);
// ThemeData.light().copyWith(
// useMaterial3: true,
// colorScheme: _buildColorScheme,
// );
// ColorScheme get _buildColorScheme => FlexColorScheme.light(
// ).toScheme;
// const ColorScheme(
// brightness: Brightness.light,
// primary: Colors.black,
// onPrimary: Colors.white,
// secondary: Colors.black,
// onSecondary: Colors.black45,
// error: Colors.red,
// onError: Colors.orange,
// background: Colors.white,
// onBackground: Colors.red,
// surface: Colors.white,
// onSurface: Colors.black,
// );
}

View File

@@ -0,0 +1,23 @@
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import '../theme_notifier.dart';
class ApplicationProvider {
static ApplicationProvider? _instance;
static ApplicationProvider get instance {
_instance ??= ApplicationProvider._init();
return _instance!;
}
ApplicationProvider._init();
List<SingleChildWidget> singleItems = [];
List<SingleChildWidget> dependItems = [
ChangeNotifierProvider(
create: (context) => ThemeNotifier(),
),
// Provider.value(value: NavigationService.instance)
];
List<SingleChildWidget> uiChangesItems = [];
}

View File

@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'app_theme_dark.dart';
import 'app_theme_light.dart';
import '../constant/enums/app_theme_enums.dart';
class ThemeNotifier extends ChangeNotifier {
// ThemeData _currentTheme = AppThemeLight.instance.theme;
// // ThemeData get currentTheme => LocaleManager.instance.getStringValue(PreferencesKeys.THEME) ==
// // AppThemes.LIGHT.name
// // ? AppThemeLight.instance.theme
// // : AppThemeDark.instance.theme;
// ThemeData get currentTheme {
// log("ThemeKey: ${LocaleManager.instance.getStringValue(PreferencesKeys.THEME)}");
// if (LocaleManager.instance.getStringValue(PreferencesKeys.THEME) ==
// AppThemes.LIGHT.name) {
// log("light");
// } else {
// log("dark");
// }
// return LocaleManager.instance.getStringValue(PreferencesKeys.THEME) ==
// AppThemes.LIGHT.name
// ? AppThemeLight.instance.theme
// : AppThemeDark.instance.theme;
// }
// ThemeData _currentTheme = AppThemeLight.instance.theme; // Mặc định là light
// ThemeNotifier() {
// loadThemeFromPreferences();
// }
// Future<void> loadThemeFromPreferences() async {
// String themeKey =
// LocaleManager.instance.getStringValue(PreferencesKeys.THEME);
// log("ThemeNotifierKey:$themeKey ");
// if (themeKey == AppThemes.LIGHT.name) {
// _currentTheme = AppThemeLight.instance.theme;
// } else {
// _currentTheme = AppThemeDark.instance.theme;
// }
// notifyListeners(); // Thông báo cho các widget lắng nghe
// }
// ThemeData get currentTheme => _currentTheme;
// AppThemes _currenThemeEnum = AppThemes.LIGHT;
// AppThemes get currenThemeEnum =>
// LocaleManager.instance.getStringValue(PreferencesKeys.THEME) ==
// AppThemes.LIGHT.name
// ? AppThemes.LIGHT
// : AppThemes.DARK;
// // AppThemes get currenThemeEnum => _currenThemeEnum;
// void changeValue(AppThemes theme) {
// if (theme == AppThemes.LIGHT) {
// _currentTheme = ThemeData.dark();
// } else {
// _currentTheme = ThemeData.light();
// }
// notifyListeners();
// }
// void changeTheme() {
// if (_currenThemeEnum == AppThemes.LIGHT) {
// _currentTheme = AppThemeDark.instance.theme;
// _currenThemeEnum = AppThemes.DARK;
// LocaleManager.instance
// .setString(PreferencesKeys.THEME, AppThemes.DARK.name);
// } else {
// _currentTheme = AppThemeLight.instance.theme;
// _currenThemeEnum = AppThemes.LIGHT;
// LocaleManager.instance
// .setString(PreferencesKeys.THEME, AppThemes.LIGHT.name);
// }
// notifyListeners();
// }
ThemeData _currentTheme = AppThemeLight.instance.theme;
ThemeData get currentTheme => _currentTheme;
AppThemes _currenThemeEnum = AppThemes.LIGHT;
AppThemes get currenThemeEnum => _currenThemeEnum;
void changeValue(AppThemes theme) {
if (theme == AppThemes.LIGHT) {
_currentTheme = AppThemeLight.instance.theme;
} else {
_currentTheme = AppThemeDark.instance.theme;
}
notifyListeners();
}
void changeTheme() {
if (_currenThemeEnum == AppThemes.LIGHT) {
_currentTheme = AppThemeDark.instance.theme;
_currenThemeEnum = AppThemes.DARK;
} else {
_currentTheme = AppThemeLight.instance.theme;
_currenThemeEnum = AppThemes.LIGHT;
}
notifyListeners();
}
}

View File

@@ -0,0 +1,16 @@
import 'package:intl/intl.dart';
class DateTimeUtils {
DateTimeUtils._init();
static DateTimeUtils? _instance;
static DateTimeUtils get instance => _instance ??= DateTimeUtils._init();
String formatDateTimeToString(DateTime dateTime) {
return DateFormat('yyyy-MM-dd\'T\'00:00:00\'Z\'').format(dateTime);
}
String convertCurrentMillisToDateTimeString(int time) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch((time) * 1000);
return DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
}
}

View File

@@ -0,0 +1,269 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:sfm_app/feature/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/devices/device_model.dart';
import '../shared/model/ward_model.dart';
class DeviceUtils {
DeviceUtils._init();
static DeviceUtils? _instance;
static DeviceUtils get instance => _instance ??= DeviceUtils._init();
APIServices apiServices = APIServices();
Map<String, dynamic> getDeviceSensors(
BuildContext context, List<Sensor> sensors) {
Map<String, dynamic> map = {};
if (sensors.isEmpty) {
map['sensorState'] = appLocalization(context).no_data_message;
map['sensorBattery'] = appLocalization(context).no_data_message;
map['sensorCsq'] = appLocalization(context).no_data_message;
map['sensorTemp'] = appLocalization(context).no_data_message;
map['sensorHum'] = appLocalization(context).no_data_message;
map['sensorVolt'] = appLocalization(context).no_data_message;
} else {
for (var sensor in sensors) {
if (sensor.name == "1") {
if (sensor.value == 0) {
map['sensorIn'] = "sensor 0";
} else {
map['sensorIn'] = "sensor 1";
}
}
if (sensor.name == "2") {
if (sensor.value == 0) {
map['sensorMove'] = appLocalization(context).gf_not_move_message;
} else {
map['sensorMove'] = appLocalization(context).gf_moving_message;
}
}
if (sensor.name == "3") {
if (sensor.value == 0) {
map['sensorAlarm'] = appLocalization(context).normal_message;
} else {
map['sensorAlarm'] =
appLocalization(context).warning_status_message;
}
}
if (sensor.name == "6") {
if (sensor.value! > 0 && sensor.value! < 12) {
map['sensorCsq'] = appLocalization(context).low_message_uppercase;
} else if (sensor.value! >= 12 && sensor.value! < 20) {
map['sensorCsq'] =
appLocalization(context).moderate_message_uppercase;
} else if (sensor.value! >= 20) {
map['sensorCsq'] = appLocalization(context).good_message_uppercase;
} else {
map['sensorCsq'] = appLocalization(context).gf_no_signal_message;
}
}
if (sensor.name == "7") {
map['sensorVolt'] = "${(sensor.value!) / 1000} V";
}
if (sensor.name == "8") {
map['sensorTemp'] = "${sensor.value}°C";
}
if (sensor.name == "9") {
map['sensorHum'] = "${sensor.value} %";
}
if (sensor.name == "10") {
map['sensorBattery'] = "${sensor.value}";
}
if (sensor.name == "11") {
if (sensor.value == 0) {
map['sensorState'] = appLocalization(context).normal_message;
} else if (sensor.value == 1) {
map['sensorState'] =
appLocalization(context).smoke_detecting_message;
} else if (sensor.value == 3) {
map['sensorState'] =
appLocalization(context).gf_remove_from_base_message;
} else {
map['sensorState'] = appLocalization(context).undefine_message;
}
}
}
}
return map;
}
Future<String> getFullDeviceLocation(
BuildContext context, String areaPath) async {
if (areaPath != "") {
List<String> parts = areaPath.split('_');
String provinceID = parts[0];
String districtID = parts[1];
String wardID = parts[2];
String provinceBody = await apiServices.getProvinceByID(provinceID);
final provinceItem = jsonDecode(provinceBody);
Province province = Province.fromJson(provinceItem['data']);
String districtBody = await apiServices.getDistrictByID(districtID);
final districtItem = jsonDecode(districtBody);
District district = District.fromJson(districtItem['data']);
String wardBody = await apiServices.getWardByID(wardID);
final wardItem = jsonDecode(wardBody);
Ward ward = Ward.fromJson(wardItem['data']);
return "${ward.fullName}, ${district.fullName}, ${province.fullName}";
}
return appLocalization(context).no_data_message;
}
String checkStateDevice(BuildContext context, int state) {
String message = appLocalization(context).no_data_message;
if (state == 1) {
message = appLocalization(context).smoke_detecting_message;
} else if (state == 0) {
message = appLocalization(context).normal_message;
} else if (state == -1) {
message = appLocalization(context).disconnect_message_uppercase;
} else if (state == 2) {
message = appLocalization(context).in_progress_message;
} else if (state == 3) {
message = appLocalization(context).in_progress_message;
}
return message;
}
List<Device> sortDeviceByState(List<Device> devices) {
List<Device> sortedDevices = List.from(devices);
sortedDevices.sort((a, b) {
int stateOrder = [2, 1, 3, 0, -1, 3].indexOf(a.state!) -
[2, 1, 3, 0, -1, 3].indexOf(b.state!);
return stateOrder;
});
return sortedDevices;
}
Color getTableRowColor(int state) {
if (state == 1) {
return Colors.red;
} else if (state == 0) {
return Colors.green;
} else {
return Colors.grey;
}
}
String getDeviceSensorsLog(BuildContext context, SensorLogs sensor) {
String message = "";
if (sensor.detail!.username != null || sensor.detail!.note != null) {
message = "${appLocalization(context).bell_user_uppercase} ";
if (sensor.name! == "0") {
if (sensor.value! == 3) {
String state = "đã thông báo rằng đây là cháy giả";
message = message + state;
}
}
} else {
message = "${appLocalization(context).device_title} ";
if (sensor.name == "11") {
String state = checkStateDevice(context, sensor.value!);
message = message + state;
} else if (sensor.name == "2") {
String state = getDeviceMove(context, sensor.value!);
message = message + state;
} else if (sensor.name == "6") {
String state = getDeviceCSQ(context, sensor.value!);
message = message + state;
} else if (sensor.name == "7") {
String state = getDeviceVolt(context, sensor.value!);
message = message + state;
} else if (sensor.name == "8") {
String state = getDeviceTemp(context, sensor.value!);
message = message + state;
} else if (sensor.name == "9") {
String state = getDeviceHum(context, sensor.value!);
message = message + state;
} else if (sensor.name == "10") {
String state = getDeviceBattery(context, sensor.value!);
message = message + state;
} else {
String state = appLocalization(context).undefine_message;
message = message + state;
}
}
return message;
}
String getDeviceCSQ(BuildContext context, int number) {
if (number >= 0 && number < 12) {
return appLocalization(context).gf_weak_signal_message;
} else if (number >= 12 && number < 20) {
return appLocalization(context).gf_moderate_signal_message;
} else if (number >= 20) {
return appLocalization(context).gf_good_signal_message;
}
return appLocalization(context).gf_no_signal_message;
}
String getDeviceVolt(BuildContext context, int number) {
return "${appLocalization(context).gf_volt_detect_message} ${number / 1000} V";
}
String getDeviceTemp(BuildContext context, int number) {
return "${appLocalization(context).gf_temp_detect_message} $number °C";
}
String getDeviceHum(BuildContext context, int number) {
return "${appLocalization(context).gf_hum_detect_message} $number%";
}
String getDeviceBattery(BuildContext context, int number) {
return "${appLocalization(context).gf_battery_detect_message} $number%";
}
String getDeviceMove(BuildContext context, int number) {
String message = "";
if (number == 0) {
message = appLocalization(context).gf_not_move_message;
} else {
message = appLocalization(context).gf_moving_message;
}
return message;
}
IconData getBatteryIcon(int battery) {
if (battery <= 10) {
return Icons.battery_alert;
} else if (battery <= 40) {
return Icons.battery_2_bar;
} else if (battery <= 70) {
return Icons.battery_4_bar;
} else if (battery <= 90) {
return Icons.battery_6_bar;
} else {
return Icons.battery_full_rounded;
}
}
IconData getSignalIcon(BuildContext context, String signal) {
if (signal == appLocalization(context).gf_weak_signal_message) {
return Icons.signal_cellular_alt_1_bar;
} else if (signal == appLocalization(context).gf_moderate_signal_message) {
return Icons.signal_cellular_alt_2_bar;
} else {
return Icons.signal_cellular_alt;
}
}
Color getColorRiple(int state) {
if (state == 1) {
return Colors.red;
} else if (state == 3) {
return Colors.orange;
} else if (state == -1) {
return Colors.grey;
} else {
return Colors.green;
}
}
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import '../services/language_services.dart';
class QRScanUtils {
QRScanUtils._init();
static QRScanUtils? _instance;
static QRScanUtils get instance => _instance ??= QRScanUtils._init();
Future<String> scanQR(BuildContext context) async {
String barcodeScanRes;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
barcodeScanRes = await FlutterBarcodeScanner.scanBarcode(
'#ffffff', appLocalization(context).cancel_button_content, true, ScanMode.QR);
} on PlatformException {
barcodeScanRes = 'Failed to get platform version.';
}
return barcodeScanRes;
}
Map<String, dynamic> getQRData(String data) {
if (data.isEmpty) {
return {};
} else {
final parts = data.split('.');
return {
'group_id': parts.length == 2 ? parts[0] : '',
'group_name': parts.length == 2 ? parts[1] : '',
};
}
}
}

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import '../constant/status_code/status_code_constants.dart';
import '../shared/shared_snack_bar.dart';
void showSnackBarResponseByStatusCode(BuildContext context, int statusCode,
String successMessage, String failedMessage) async {
if (statusCode == StatusCodeConstants.OK ||
statusCode == StatusCodeConstants.CREATED) {
showSuccessTopSnackBarCustom(context, successMessage);
} else {
showErrorTopSnackBarCustom(context, failedMessage);
}
}
void showSnackBarResponseByStatusCodeNoIcon(BuildContext context, int statusCode,
String successMessage, String failedMessage) async {
if (statusCode == StatusCodeConstants.OK ||
statusCode == StatusCodeConstants.CREATED) {
showNoIconTopSnackBar(context, successMessage,Colors.green, Colors.white);
} else {
showNoIconTopSnackBar(context, failedMessage, Colors.red, Colors.white);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
class StringUtils {
StringUtils._init();
static StringUtils? _instance;
static StringUtils get instance => _instance ??= StringUtils._init();
List<InlineSpan> parseAddressFromMapAPI(String addressString) {
RegExp regex = RegExp(r'<span class="([^"]+)">([^<]+)</span>');
List<InlineSpan> textSpans = [];
Iterable<Match> matches = regex.allMatches(addressString);
for (Match match in matches) {
String cssClass = match.group(1)!;
String text = match.group(2)!;
if (cssClass == 'country-name') {
continue;
}
textSpans.add(
TextSpan(
text: text,
),
);
textSpans.add(
const TextSpan(text: ', '),
);
}
textSpans.removeLast();
return textSpans;
}
}