Complete refactoring SFM App Source Code
This commit is contained in:
72
lib/product/base/bloc/base_bloc.dart
Normal file
72
lib/product/base/bloc/base_bloc.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
142
lib/product/base/screen/base_screen.dart
Normal file
142
lib/product/base/screen/base_screen.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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
42
lib/product/cache/local_manager.dart
vendored
Normal 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());
|
||||
}
|
||||
}
|
||||
24
lib/product/constant/app/api_path_constant.dart
Normal file
24
lib/product/constant/app/api_path_constant.dart
Normal 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";
|
||||
}
|
||||
21
lib/product/constant/app/app_constants.dart
Normal file
21
lib/product/constant/app/app_constants.dart
Normal 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";
|
||||
}
|
||||
16
lib/product/constant/enums/app_route_enums.dart
Normal file
16
lib/product/constant/enums/app_route_enums.dart
Normal 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,
|
||||
}
|
||||
3
lib/product/constant/enums/app_theme_enums.dart
Normal file
3
lib/product/constant/enums/app_theme_enums.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
enum AppThemes { LIGHT, DARK, SYSTEM }
|
||||
10
lib/product/constant/enums/local_keys_enums.dart
Normal file
10
lib/product/constant/enums/local_keys_enums.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
enum PreferencesKeys{
|
||||
TOKEN,
|
||||
UID,
|
||||
EXP,
|
||||
ROLE,
|
||||
LANGUAGE_CODE,
|
||||
THEME
|
||||
}
|
||||
7
lib/product/constant/enums/role_enums.dart
Normal file
7
lib/product/constant/enums/role_enums.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
enum RoleEnums{
|
||||
USER,
|
||||
ADMIN,
|
||||
MOD
|
||||
}
|
||||
13
lib/product/constant/icon/icon_constants.dart
Normal file
13
lib/product/constant/icon/icon_constants.dart
Normal 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);
|
||||
}
|
||||
9
lib/product/constant/image/image_constants.dart
Normal file
9
lib/product/constant/image/image_constants.dart
Normal 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";
|
||||
}
|
||||
6
lib/product/constant/lang/language_constants.dart
Normal file
6
lib/product/constant/lang/language_constants.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
class LanguageConstants {
|
||||
static const ENGLISH = "en";
|
||||
static const VIETNAM = "vi";
|
||||
}
|
||||
156
lib/product/constant/navigation/navigation_router.dart
Normal file
156
lib/product/constant/navigation/navigation_router.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
103
lib/product/extention/context_extention.dart
Normal file
103
lib/product/extention/context_extention.dart
Normal 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!;
|
||||
}
|
||||
|
||||
242
lib/product/lang/l10n/app_en.arb
Normal file
242
lib/product/lang/l10n/app_en.arb
Normal 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"
|
||||
}
|
||||
242
lib/product/lang/l10n/app_vi.arb
Normal file
242
lib/product/lang/l10n/app_vi.arb
Normal 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"
|
||||
}
|
||||
18
lib/product/lang/language_model.dart
Normal file
18
lib/product/lang/language_model.dart
Normal 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),
|
||||
];
|
||||
}
|
||||
}
|
||||
114
lib/product/network/network_manager.dart
Normal file
114
lib/product/network/network_manager.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
31
lib/product/permission/notification_permission.dart
Normal file
31
lib/product/permission/notification_permission.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
386
lib/product/services/api_services.dart
Normal file
386
lib/product/services/api_services.dart
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
36
lib/product/services/language_services.dart
Normal file
36
lib/product/services/language_services.dart
Normal 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)!;
|
||||
}
|
||||
63
lib/product/services/map_services.dart
Normal file
63
lib/product/services/map_services.dart
Normal 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 [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
103
lib/product/services/notification_services.dart
Normal file
103
lib/product/services/notification_services.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
43
lib/product/shared/model/district_model.dart
Normal file
43
lib/product/shared/model/district_model.dart
Normal 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!;
|
||||
}
|
||||
172
lib/product/shared/model/near_by_search_model.dart
Normal file
172
lib/product/shared/model/near_by_search_model.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
42
lib/product/shared/model/province_model.dart
Normal file
42
lib/product/shared/model/province_model.dart
Normal 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!;
|
||||
}
|
||||
43
lib/product/shared/model/ward_model.dart
Normal file
43
lib/product/shared/model/ward_model.dart
Normal 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!;
|
||||
}
|
||||
44
lib/product/shared/shared_background.dart
Normal file
44
lib/product/shared/shared_background.dart
Normal 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)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/product/shared/shared_input_decoration.dart
Normal file
18
lib/product/shared/shared_input_decoration.dart
Normal 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),
|
||||
));
|
||||
56
lib/product/shared/shared_snack_bar.dart
Normal file
56
lib/product/shared/shared_snack_bar.dart
Normal 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);
|
||||
}
|
||||
137
lib/product/shared/shared_transition.dart
Normal file
137
lib/product/shared/shared_transition.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
5
lib/product/theme/app_theme.dart
Normal file
5
lib/product/theme/app_theme.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AppTheme {
|
||||
ThemeData? theme;
|
||||
}
|
||||
53
lib/product/theme/app_theme_dark.dart
Normal file
53
lib/product/theme/app_theme_dark.dart
Normal 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,
|
||||
// );
|
||||
}
|
||||
54
lib/product/theme/app_theme_light.dart
Normal file
54
lib/product/theme/app_theme_light.dart
Normal 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,
|
||||
// );
|
||||
}
|
||||
23
lib/product/theme/provider/app_provider.dart
Normal file
23
lib/product/theme/provider/app_provider.dart
Normal 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 = [];
|
||||
}
|
||||
104
lib/product/theme/theme_notifier.dart
Normal file
104
lib/product/theme/theme_notifier.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
16
lib/product/utils/date_time_utils.dart
Normal file
16
lib/product/utils/date_time_utils.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
269
lib/product/utils/device_utils.dart
Normal file
269
lib/product/utils/device_utils.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
lib/product/utils/qr_utils.dart
Normal file
34
lib/product/utils/qr_utils.dart
Normal 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] : '',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
23
lib/product/utils/response_status_utils.dart
Normal file
23
lib/product/utils/response_status_utils.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
32
lib/product/utils/string_utils.dart
Normal file
32
lib/product/utils/string_utils.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user