Compare commits

..

21 Commits

Author SHA1 Message Date
Tran Anh Tuan
2afc71e24c chore(notifications): fix conflig when show notification in android 2025-10-23 16:57:10 +07:00
Tran Anh Tuan
9bff11a0b1 chore(group): fix delay show group when switch and convert to relative import 2025-10-23 11:08:03 +07:00
Tran Anh Tuan
cfebf74515 update to relative import 2025-10-22 17:52:28 +07:00
anhtunz
fe622d8af9 fix(ui): fix error text in warning card and update information for device's inter-family owner 2025-07-21 10:09:19 +07:00
anhtunz
4ff2ad4396 Update alarm notification when app in foreground, background and terminate 2025-07-11 12:04:20 +07:00
anhtunz
cdbd5b7484 Add SimDataScreen 2025-07-08 16:56:30 +07:00
anhtunz
e6c536f380 update(MainScreen): Fix Timer cannot cancel when switching tabs 2025-06-23 16:10:36 +07:00
anhtunz
bd73ba1b45 Update Gradle Plugin 2025-06-18 13:15:27 +07:00
anhtunz
d7d565a36a Update intl package 2025-06-18 10:02:40 +07:00
anhtunz
2d53f2cdd3 Update
Logging data
Try-catch function
2025-06-17 16:43:45 +07:00
anhtunz
22fef0e0a8 fix map 2025-06-09 15:43:50 +07:00
anhtunz
3a8fa3633c update
feat(api_service): Update try-catch funtion and handle exception
update(loading_animation): Update loading animation using Lottie
2025-06-09 14:29:43 +07:00
477646ab9d v1.0.8 2025-05-26 15:21:58 +07:00
anhtunz
677a7c4d4a chore(HomeScreen): fix context unmounted 2025-05-26 14:08:30 +07:00
anhtunz
01ae020374 Fix(bugs):
Can't logout
ui display when user doesn't has device
2025-05-26 11:58:19 +07:00
anhtunz
f80e234b1d fix(bugs):
fix(DeviceManagerScreen): show all devices when visibility=DELETED
fix(MapScreen): cannot delete polylines when closing SnackBar on iOS
2025-04-28 09:44:26 +07:00
anhtunz
b75635a801 fix(ui): fix layout in DetailGroupScreen 2025-04-15 13:34:42 +07:00
anhtunz
c19cdec3cf fix(bugs): Fix dispose error in group_detail_screen 2025-04-15 12:02:47 +07:00
anhtunz
07af3c344f chore(ui): Set width device_name's column to 1/3 size_width in device_manager_screen
update text color on button show_direction_widget
2025-04-15 11:59:18 +07:00
anhtunz
8310451f6b fix(ui): Change TextStyle to ResponsiveText 2025-04-15 11:31:29 +07:00
anhtunz
da8eb3ae2d fix(bugs): Fix FindAWay Function with new package 2025-04-15 10:13:17 +07:00
112 changed files with 7836 additions and 3156 deletions

View File

@@ -7,6 +7,10 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
analyzer:
errors:
use_build_context_synchronously: ignore
avoid_print: ignore
include: package:flutter_lints/flutter.yaml
linter:
@@ -22,8 +26,8 @@ linter:
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
- prefer_relative_imports: true
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -1,3 +1,10 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
id 'com.google.gms.google-services'
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
@@ -6,11 +13,6 @@ if (localPropertiesFile.exists()) {
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
// throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
@@ -21,21 +23,12 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
// START: FlutterFire Configuration
apply plugin: 'com.google.gms.google-services'
// END: FlutterFire Configuration
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
namespace "vn.smatec.sfm"
compileSdkVersion 35
@@ -44,12 +37,12 @@ android {
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
}
sourceSets {
@@ -102,9 +95,9 @@ flutter {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.firebase:firebase-messaging-directboot:20.2.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation platform('com.google.firebase:firebase-bom:33.15.0')
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
implementation 'androidx.window:window:1.0.0'
implementation 'androidx.window:window-java:1.0.0'
}

View File

@@ -1,30 +1,36 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.camera"
android:required="false"
tools:targetApi="5" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />
<!-- <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> -->
<!-- NOTE: the example app requests USE_EXACT_ALARM to make it easier to run the app.
Developers will need to check if their own app needs to use SCHEDULE_EXACT_ALARM instead -->
<!-- <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- NOTE: Special use was selected as it's the closest match for this example app.
apps should specify the appropriate permission for their use cases. -->
<!-- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> -->
<!-- START Permissions Package -->
<!-- Permissions options for the `camera` group -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Permissions options for the `location` group -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Permissions options for the `alarm` group -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"
tools:ignore="ExactAlarm" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<!-- END Permissions Package -->
<application
android:label="SFM"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyA9C7Pmxw6Gw3H2mM4WA_XGngRIIr2VS7k"/>
android:value="AIzaSyDI8b-PUgKUgj5rHdtgEHCwWjUXYJrqYhE"/>
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
@@ -34,6 +40,7 @@
android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true"
android:enableOnBackInvokedCallback="true"
android:exported="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@@ -60,7 +67,25 @@
<service
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
android:exported="false"
android:stopWithTask="false"/>
android:stopWithTask="false"
android:foregroundServiceType="specialUse" />
<service android:name="com.gdelataillade.alarm.services.NotificationOnKillService" />
<service
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

View File

@@ -1,21 +1,9 @@
buildscript {
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
plugins {
id 'com.google.gms.google-services' version '4.3.15' apply false
}
dependencies {
// START: FlutterFire Configuration
classpath 'com.google.gms:google-services:4.3.15'
// END: FlutterFire Configuration
classpath 'com.android.tools.build:gradle:8.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
@@ -43,3 +31,7 @@ subprojects {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip

View File

@@ -18,8 +18,11 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.2.1" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
id "com.android.application" version "8.3.2" apply false
// START: FlutterFire Configuration
id "com.google.gms.google-services" version "4.3.15" apply false
// END: FlutterFire Configuration
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}
include ":app"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

3
devtools_options.yaml Normal file
View File

@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '14.0'
platform :ios, '15.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -1,62 +1,197 @@
PODS:
- alarm (0.0.1):
- Flutter
- app_settings (5.1.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- Firebase/CoreOnly (11.10.0):
- FirebaseCore (~> 11.10.0)
- Firebase/Messaging (11.10.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.10.0)
- firebase_core (3.13.0):
- Firebase/CoreOnly (= 11.10.0)
- Flutter
- firebase_messaging (15.2.5):
- Firebase/Messaging (= 11.10.0)
- firebase_core
- Flutter
- FirebaseCore (11.10.0):
- FirebaseCoreInternal (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.10.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.10.0):
- FirebaseCore (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.10.0):
- FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- Flutter (1.0.0)
- flutter_barcode_scanner_plus (3.0.7):
- Flutter
- flutter_fgbg (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- geolocator_apple (1.2.0):
- Flutter
- FlutterMacOS
- Google-Maps-iOS-Utils (5.0.0):
- GoogleMaps (~> 8.0)
- google_maps_flutter_ios (0.0.1):
- Flutter
- Google-Maps-iOS-Utils (< 7.0, >= 5.0)
- GoogleMaps (< 10.0, >= 8.4)
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleMaps (8.4.0):
- GoogleMaps/Maps (= 8.4.0)
- GoogleMaps/Base (8.4.0)
- GoogleMaps/Maps (8.4.0):
- GoogleMaps/Base
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.0)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- maps_launcher (0.0.1):
- Flutter
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- permission_handler_apple (9.3.0):
- Flutter
- PromisesObjC (2.4.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- alarm (from `.symlinks/plugins/alarm/ios`)
- app_settings (from `.symlinks/plugins/app_settings/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
- flutter_barcode_scanner_plus (from `.symlinks/plugins/flutter_barcode_scanner_plus/ios`)
- flutter_fgbg (from `.symlinks/plugins/flutter_fgbg/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`)
- maps_launcher (from `.symlinks/plugins/maps_launcher/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS:
trunk:
- Firebase
- FirebaseCore
- FirebaseCoreInternal
- FirebaseInstallations
- FirebaseMessaging
- Google-Maps-iOS-Utils
- GoogleDataTransport
- GoogleMaps
- GoogleUtilities
- nanopb
- PromisesObjC
EXTERNAL SOURCES:
alarm:
:path: ".symlinks/plugins/alarm/ios"
app_settings:
:path: ".symlinks/plugins/app_settings/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_messaging:
:path: ".symlinks/plugins/firebase_messaging/ios"
Flutter:
:path: Flutter
flutter_barcode_scanner_plus:
:path: ".symlinks/plugins/flutter_barcode_scanner_plus/ios"
flutter_fgbg:
:path: ".symlinks/plugins/flutter_fgbg/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/darwin"
google_maps_flutter_ios:
:path: ".symlinks/plugins/google_maps_flutter_ios/ios"
maps_launcher:
:path: ".symlinks/plugins/maps_launcher/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
alarm: 9ff6d2dae9bd69c4022622f7e0a1e9c1dd70064e
app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
firebase_core: 2d4534e7b489907dcede540c835b48981d890943
firebase_messaging: 75bc93a4df25faccad67f6662ae872ac9ae69b64
FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3
FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_barcode_scanner_plus: 5777819a85622aed4284fb14d1fa33b72a64003d
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_barcode_scanner_plus: e5ef7f41cdbf3086e1b9348dce986dde3aa08696
flutter_fgbg: d3da78df78454b1808f0829a5da9cd17dfe16444
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321
google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3
google_maps_flutter_ios: 0291eb2aa252298a769b04d075e4a9d747ff7264
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
maps_launcher: edf829809ba9e894d70e569bab11c16352dedb45
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
PODFILE CHECKSUM: 9de95a6a40372eef38c524a1823cf12342740f38
PODFILE CHECKSUM: 72b62bbc58143b910914489c5ddbf2bfa99b3dd4
COCOAPODS: 1.16.2

View File

@@ -17,6 +17,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
A241E2BF2EAA2F1C00664284 /* warning_alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = A241E2BE2EAA2F1C00664284 /* warning_alarm.caf */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -64,6 +65,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A241E2BE2EAA2F1C00664284 /* warning_alarm.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = warning_alarm.caf; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -109,6 +111,7 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
A241E2BE2EAA2F1C00664284 /* warning_alarm.caf */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
@@ -256,6 +259,7 @@
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
5EFD34A62DA7D89800351DB2 /* GoogleService-Info.plist in Resources */,
A241E2BF2EAA2F1C00664284 /* warning_alarm.caf in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
@@ -465,7 +469,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SmartFM;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -648,7 +652,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SmartFM;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -678,7 +682,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SmartFM;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View File

@@ -1,132 +0,0 @@
{
"originHash" : "55c94c702657474632f237e12a19340acc7419ac493c42deecaaa9d0cd699fd8",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "d1f7c7e8eaa74d7e44467184dc5f592268247d33",
"version" : "11.11.0"
}
},
{
"identity" : "flutterfire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/flutterfire",
"state" : {
"revision" : "a80a123386fd4904cad6938673020a7bcf31b2f2",
"version" : "3.13.0-firebase-core-swift"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "dd89fc79a77183830742a16866d87e4e54785734",
"version" : "11.11.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
"version" : "8.0.2"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
"version" : "1.69.0"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "4d70340d55d7d07cc2fdf8e8125c4c126c1d5f35",
"version" : "4.4.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
"version" : "1.29.0"
}
}
],
"version" : 3
}

View File

@@ -44,6 +44,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
@@ -72,11 +73,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -1,132 +0,0 @@
{
"originHash" : "55c94c702657474632f237e12a19340acc7419ac493c42deecaaa9d0cd699fd8",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "d1f7c7e8eaa74d7e44467184dc5f592268247d33",
"version" : "11.11.0"
}
},
{
"identity" : "flutterfire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/flutterfire",
"state" : {
"revision" : "a80a123386fd4904cad6938673020a7bcf31b2f2",
"version" : "3.13.0-firebase-core-swift"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "dd89fc79a77183830742a16866d87e4e54785734",
"version" : "11.11.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
"version" : "8.0.2"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
"version" : "1.69.0"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "4d70340d55d7d07cc2fdf8e8125c4c126c1d5f35",
"version" : "4.4.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
"version" : "1.29.0"
}
}
],
"version" : 3
}

View File

@@ -4,6 +4,8 @@ import flutter_local_notifications
import GoogleMaps
import FirebaseCore
import FirebaseMessaging
import UserNotifications
import alarm
@main
@objc class AppDelegate: FlutterAppDelegate {
@@ -14,6 +16,7 @@ import FirebaseMessaging
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
SwiftAlarmPlugin.registerBackgroundTasks()
FirebaseApp.configure()
GMSServices.provideAPIKey("AIzaSyA9C7Pmxw6Gw3H2mM4WA_XGngRIIr2VS7k")
GeneratedPluginRegistrant.register(with: self)

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
@@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="121" y="-34"/>
</scene>
</scenes>
</document>

View File

@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Sfm App</string>
<string>SmartFM</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -39,6 +39,7 @@
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>audio</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
@@ -60,5 +61,9 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.gdelataillade.fetch</string>
</array>
</dict>
</plist>

Binary file not shown.

BIN
ios/warning_alarm.caf Normal file

Binary file not shown.

View File

@@ -4,7 +4,6 @@ import '../product/base/bloc/base_bloc.dart';
import '../feature/bell/bell_model.dart';
class BellBloc extends BlocBase {
final bellItems = StreamController<List<BellItems>>.broadcast();
StreamSink<List<BellItems>> get sinkBellItems => bellItems.sink;
Stream<List<BellItems>> get streamBellItems => bellItems.stream;

View File

@@ -1,19 +1,15 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:sfm_app/product/services/api_services.dart';
import 'package:sfm_app/product/utils/device_utils.dart';
import '../product/services/api_services.dart';
import '../product/utils/date_time_utils.dart';
import '../feature/device_log/device_logs_model.dart';
import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/utils/device_utils.dart';
class DetailDeviceBloc extends BlocBase {
APIServices apiServices = APIServices();
@@ -42,14 +38,12 @@ class DetailDeviceBloc extends BlocBase {
String thingID,
Completer<GoogleMapController> controller,
) async {
String body = await apiServices.getDeviceInfomation(thingID);
if (body != "") {
final data = jsonDecode(body);
Device device = Device.fromJson(data);
await apiServices.execute(context, () async {
Device device = await apiServices.getDeviceInformation(thingID);
sinkDeviceInfo.add(device);
if (device.areaPath != null) {
String fullLocation = await DeviceUtils.instance
.getFullDeviceLocation(context, device.areaPath!);
.getFullDeviceLocation(context, device.areaPath!, "");
log("Location: $fullLocation");
sinkDeviceLocation.add(fullLocation);
}
@@ -74,20 +68,56 @@ class DetailDeviceBloc extends BlocBase {
mapController
.animateCamera(CameraUpdate.newCameraPosition(cameraPosition));
}
}
});
// try {
// Device device = await apiServices.getDeviceInformation(thingID);
// sinkDeviceInfo.add(device);
// if (device.areaPath != null) {
// String fullLocation = await DeviceUtils.instance
// .getFullDeviceLocation(context, device.areaPath!, "");
// log("Location: $fullLocation");
// sinkDeviceLocation.add(fullLocation);
// }
// Map<String, dynamic> sensorMap = {};
// if (device.status!.sensors != null) {
// sensorMap = DeviceUtils.instance
// .getDeviceSensors(context, device.status!.sensors!);
// } else {
// sensorMap = DeviceUtils.instance.getDeviceSensors(context, []);
// }
// sinkDeviceSensor.add(sensorMap);
// if (device.settings!.latitude! != "" &&
// device.settings!.longitude! != "") {
// final CameraPosition cameraPosition = CameraPosition(
// target: LatLng(
// double.parse(device.settings!.latitude!),
// double.parse(device.settings!.longitude!),
// ),
// zoom: 13,
// );
// final GoogleMapController mapController = await controller.future;
// mapController
// .animateCamera(CameraUpdate.newCameraPosition(cameraPosition));
// }
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
void findLocation(BuildContext context, String areaPath) async {
String fullLocation =
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath);
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath, "");
sinkDeviceLocation.add(fullLocation);
}
void getNearerSensorValue(String thingID) async {
void getNearerSensorValue(BuildContext context, String thingID) async {
apiServices.execute(context, () async {
List<SensorLogs> sensorTemps = [];
DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2));
String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo);
String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
String now =
DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
Map<String, dynamic> params = {
'thing_id': thingID,
'from': from,
@@ -95,10 +125,8 @@ class DetailDeviceBloc extends BlocBase {
'limit': '100',
'n': '7',
};
final body = await apiServices.getLogsOfDevice(thingID, params);
if (body != "") {
final data = jsonDecode(body);
DeviceLog devicesListLog = DeviceLog.fromJson(data);
DeviceLog devicesListLog =
await apiServices.getLogsOfDevice(thingID, params);
if (devicesListLog.sensors!.isNotEmpty) {
for (var sensor in devicesListLog.sensors!) {
sensorTemps.add(sensor);
@@ -108,6 +136,35 @@ class DetailDeviceBloc extends BlocBase {
} else {
sinkSensorTemps.add([]);
}
}
});
// try {
// List<SensorLogs> sensorTemps = [];
// DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2));
// String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo);
// String now =
// DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
// Map<String, dynamic> params = {
// 'thing_id': thingID,
// 'from': from,
// 'to': now,
// 'limit': '100',
// 'n': '7',
// };
// DeviceLog devicesListLog =
// await apiServices.getLogsOfDevice(thingID, params);
// if (devicesListLog.sensors!.isNotEmpty) {
// for (var sensor in devicesListLog.sensors!) {
// sensorTemps.add(sensor);
// }
// sensorTemps = sensorTemps.reversed.toList();
// sinkSensorTemps.add(sensorTemps);
// } else {
// sinkSensorTemps.add([]);
// }
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// sinkSensorTemps.add([]);
// }
}
}

View File

@@ -1,12 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/constant/app/app_constants.dart';
import '../product/services/api_services.dart';
import '../product/utils/date_time_utils.dart';
import '../product/utils/device_utils.dart';
import '../feature/device_log/device_logs_model.dart';
@@ -35,28 +34,28 @@ class DeviceLogsBloc extends BlocBase {
@override
void dispose() {}
void getAllDevices() async {
String body = await apiServices.getOwnerDevices();
if (body != "") {
final data = jsonDecode(body);
List<dynamic> items = data['items'];
List<Device> originalDevices = Device.fromJsonDynamicList(items);
void getAllDevices(BuildContext context) async {
await apiServices.execute(context, () async {
List<Device> originalDevices = await apiServices.getOwnerDevices();
List<Device> devices =
DeviceUtils.instance.sortDeviceByState(originalDevices);
sinkAllDevices.add(devices);
}
});
}
void getDeviceLogByThingID(
BuildContext context,
int offset,
String thingID,
DateTime fromDate,
List<SensorLogs> sensors,
) async {
await apiServices.execute(context, () async {
sinkmessage.add(ApplicationConstants.LOADING);
String fromDateString =
DateTimeUtils.instance.formatDateTimeToString(fromDate);
String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
String now =
DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
Map<String, dynamic> params = {
'thing_id': thingID,
'from': fromDateString,
@@ -65,10 +64,8 @@ class DeviceLogsBloc extends BlocBase {
"offset": offset.toString(),
"asc": "true"
};
final body = await apiServices.getLogsOfDevice(thingID, params);
if (body != "") {
final data = jsonDecode(body);
DeviceLog devicesListLog = DeviceLog.fromJson(data);
DeviceLog devicesListLog =
await apiServices.getLogsOfDevice(thingID, params);
if (devicesListLog.sensors!.isEmpty) {
bool hasMore = false;
sinkHasMore.add(hasMore);
@@ -81,6 +78,38 @@ class DeviceLogsBloc extends BlocBase {
sinkmessage.add(ApplicationConstants.NO_DATA);
}
sinkSensors.add(sensors);
}
});
// try {
// sinkmessage.add(ApplicationConstants.LOADING);
// String fromDateString =
// DateTimeUtils.instance.formatDateTimeToString(fromDate);
// String now =
// DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
// Map<String, dynamic> params = {
// 'thing_id': thingID,
// 'from': fromDateString,
// 'to': now,
// 'limit': '30',
// "offset": offset.toString(),
// "asc": "true"
// };
// DeviceLog devicesListLog =
// await apiServices.getLogsOfDevice(thingID, params);
// if (devicesListLog.sensors!.isEmpty) {
// bool hasMore = false;
// sinkHasMore.add(hasMore);
// }
// if (devicesListLog.sensors!.isNotEmpty) {
// for (var sensor in devicesListLog.sensors!) {
// sensors.add(sensor);
// }
// } else {
// sinkmessage.add(ApplicationConstants.NO_DATA);
// }
// sinkSensors.add(sensors);
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
}

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../feature/settings/device_notification_settings/device_notification_settings_model.dart';
@@ -36,7 +35,6 @@ class DeviceNotificationSettingsBloc extends BlocBase {
StreamSink<String> get sinkMessageChange => messageChange.sink;
Stream<String> get streaMmessageChange => messageChange.stream;
@override
void dispose() {}
}

View File

@@ -1,16 +1,16 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intl/intl.dart';
import '../product/services/api_services.dart';
import '../product/services/language_services.dart';
import '../product/shared/model/ward_model.dart';
import '../product/shared/shared_snack_bar.dart';
import '../product/utils/response_status_utils.dart';
import '../product/shared/model/district_model.dart';
import '../product/shared/model/province_model.dart';
import '../feature/devices/device_model.dart';
@@ -75,151 +75,281 @@ class DeviceUpdateBloc extends BlocBase {
// deviceInfo.done;
}
Future<void> getAllProvinces() async {
Future<void> getAllProvinces(BuildContext context) async {
List<DropdownMenuItem<Province>> provincesData = [];
provincesData.clear();
sinkListProvinces.add(provincesData);
final body = await apiServices.getAllProvinces();
final data = jsonDecode(body);
List<dynamic> items = data["items"];
final provinces = Province.fromJsonDynamicList(items);
await apiServices.execute(context, () async {
List<Province> provinces = await apiServices.getAllProvinces();
for (var province in provinces) {
provincesData.add(
DropdownMenuItem(value: province, child: Text(province.fullName!)));
}
sinkListProvinces.add(provincesData);
});
}
Future<void> getAllDistricts(String provinceID) async {
Future<void> getAllDistricts(BuildContext context, String provinceID) async {
List<DropdownMenuItem<District>> districtsData = [];
districtsData.clear();
sinkListDistricts.add(districtsData);
final body = await apiServices.getAllDistricts(provinceID);
final data = jsonDecode(body);
List<dynamic> items = data["items"];
final districts = District.fromJsonDynamicList(items);
await apiServices.execute(context, () async {
final districts = await apiServices.getAllDistricts(provinceID);
for (var district in districts) {
districtsData.add(
DropdownMenuItem(value: district, child: Text(district.fullName!)));
}
sinkListDistricts.add(districtsData);
});
}
Future<void> getAllWards(String districtID) async {
Future<void> getAllWards(BuildContext context, String districtID) async {
List<DropdownMenuItem<Ward>> wardsData = [];
wardsData.clear();
sinkListWards.add(wardsData);
final body = await apiServices.getAllWards(districtID);
final data = jsonDecode(body);
List<dynamic> items = data["items"];
final wards = Ward.fromJsonDynamicList(items);
await apiServices.execute(context, () async {
final wards = await apiServices.getAllWards(districtID);
for (var ward in wards) {
wardsData.add(DropdownMenuItem(value: ward, child: Text(ward.fullName!)));
wardsData
.add(DropdownMenuItem(value: ward, child: Text(ward.fullName!)));
}
sinkListWards.add(wardsData);
});
}
Future<void> getDeviceInfomation(
Future<void> getDeviceInformation(
BuildContext context,
String thingID,
List<DropdownMenuItem<District>> districtsData,
List<DropdownMenuItem<Ward>> wardsData,
TextEditingController deviceNameController,
TextEditingController latitudeController,
TextEditingController longitudeController) async {
String body = await apiServices.getDeviceInfomation(thingID);
final data = jsonDecode(body);
Device device = Device.fromJson(data);
await apiServices.execute(context, () async {
Device device = await apiServices.getDeviceInformation(thingID);
sinkDeviceInfo.add(device);
deviceNameController.text = device.name ?? "";
latitudeController.text = device.settings!.latitude ?? "";
longitudeController.text = device.settings!.longitude ?? "";
if (device.areaPath != "") {
if (device.areaPath != null && device.areaPath!.isNotEmpty) {
List<String> areaPath = device.areaPath!.split('_');
// Kiểm tra độ dài của areaPath
if (areaPath.length >= 3) {
String provinceCode = areaPath[0];
String districtCode = areaPath[1];
String wardCode = areaPath[2];
getAllDistricts(provinceCode);
getAllWards(districtCode);
final provinceResponse = await apiServices.getProvinceByID(provinceCode);
final provincesData = jsonDecode(provinceResponse);
Province province = Province.fromJson(provincesData['data']);
final districtResponse = await apiServices.getDistrictByID(districtCode);
final districtData = jsonDecode(districtResponse);
District district = District.fromJson(districtData['data']);
final wardResponse = await apiServices.getWardByID(wardCode);
final wardData = jsonDecode(wardResponse);
Ward ward = Ward.fromJson(wardData['data']);
// Kiểm tra các mã có hợp lệ không (không rỗng)
if (provinceCode.isNotEmpty &&
districtCode.isNotEmpty &&
wardCode.isNotEmpty) {
try {
// Lấy danh sách districts và wards
await getAllDistricts(context, provinceCode);
await getAllWards(context, districtCode);
// Xử lý Province
try {
Province province =
await apiServices.getProvinceByID(provinceCode);
Map<String, String> provinceData = {
"name": province.fullName!,
"code": province.code!
"name": province.fullName ?? "Unknown Province",
"code": province.code ?? provinceCode
};
sinkProvinceData.add(provinceData);
} catch (e) {
// Thêm dữ liệu mặc định khi lỗi
Map<String, String> provinceData = {
"name": "Error Loading Province",
"code": provinceCode
};
sinkProvinceData.add(provinceData);
if (!context.mounted) return;
showErrorTopSnackBarCustom(context, e.toString());
}
// Xử lý District
try {
District district =
await apiServices.getDistrictByID(districtCode);
Map<String, String> districData = {
"name": district.fullName!,
"code": district.code!,
"name": district.fullName ?? "Unknown District",
"code": district.code ?? districtCode,
};
sinkDistrictData.add(districData);
} catch (e) {
log("Lỗi khi lấy thông tin district ($districtCode): $e");
Map<String, String> districData = {
"name": "Error Loading District",
"code": districtCode,
};
sinkDistrictData.add(districData);
}
// Xử lý Ward
try {
Ward ward = await apiServices.getWardByID(wardCode);
Map<String, String> wardMap = {
"name": ward.fullName!,
"code": ward.code!,
"name": ward.fullName ?? "Unknown Ward",
"code": ward.code ?? wardCode,
};
sinkWardData.add(wardMap);
} catch (e) {
log("Lỗi khi lấy thông tin ward ($wardCode): $e");
Map<String, String> wardMap = {
"name": "Error Loading Ward",
"code": wardCode,
};
sinkWardData.add(wardMap);
}
} catch (e) {
log("Lỗi khi gọi getAllDistricts hoặc getAllWards: $e");
}
} else {
await getAllProvinces(context);
log("Một hoặc nhiều mã địa phương trống: Province: $provinceCode, District: $districtCode, Ward: $wardCode");
}
} else {
showNoIconTopSnackBar(
context,
"AreaPath không đủ thông tin: ${device.areaPath}",
Colors.orangeAccent,
Colors.white);
}
}
});
// try {
// Device device = await apiServices.getDeviceInformation(thingID);
// sinkDeviceInfo.add(device);
// deviceNameController.text = device.name ?? "";
// latitudeController.text = device.settings!.latitude ?? "";
// longitudeController.text = device.settings!.longitude ?? "";
// if (device.areaPath != null && device.areaPath!.isNotEmpty) {
// List<String> areaPath = device.areaPath!.split('_');
// // Kiểm tra độ dài của areaPath
// if (areaPath.length >= 3) {
// String provinceCode = areaPath[0];
// String districtCode = areaPath[1];
// String wardCode = areaPath[2];
// // Kiểm tra các mã có hợp lệ không (không rỗng)
// if (provinceCode.isNotEmpty &&
// districtCode.isNotEmpty &&
// wardCode.isNotEmpty) {
// try {
// // Lấy danh sách districts và wards
// await getAllDistricts(context, provinceCode);
// await getAllWards(context, districtCode);
// // Xử lý Province
// try {
// Province province =
// await apiServices.getProvinceByID(provinceCode);
// Map<String, String> provinceData = {
// "name": province.fullName ?? "Unknown Province",
// "code": province.code ?? provinceCode
// };
// sinkProvinceData.add(provinceData);
// } catch (e) {
// // Thêm dữ liệu mặc định khi lỗi
// Map<String, String> provinceData = {
// "name": "Error Loading Province",
// "code": provinceCode
// };
// sinkProvinceData.add(provinceData);
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
// // Xử lý District
// try {
// District district =
// await apiServices.getDistrictByID(districtCode);
// Map<String, String> districData = {
// "name": district.fullName ?? "Unknown District",
// "code": district.code ?? districtCode,
// };
// sinkDistrictData.add(districData);
// } catch (e) {
// log("Lỗi khi lấy thông tin district ($districtCode): $e");
// Map<String, String> districData = {
// "name": "Error Loading District",
// "code": districtCode,
// };
// sinkDistrictData.add(districData);
// }
// // Xử lý Ward
// try {
// Ward ward = await apiServices.getWardByID(wardCode);
// Map<String, String> wardMap = {
// "name": ward.fullName ?? "Unknown Ward",
// "code": ward.code ?? wardCode,
// };
// sinkWardData.add(wardMap);
// } catch (e) {
// print("Lỗi khi lấy thông tin ward ($wardCode): $e");
// Map<String, String> wardMap = {
// "name": "Error Loading Ward",
// "code": wardCode,
// };
// sinkWardData.add(wardMap);
// }
// } catch (e) {
// print("Lỗi khi gọi getAllDistricts hoặc getAllWards: $e");
// }
// } else {
// await getAllProvinces(context);
// print(
// "Một hoặc nhiều mã địa phương trống: Province: $provinceCode, District: $districtCode, Ward: $wardCode");
// }
// } else {
// showNoIconTopSnackBar(
// context,
// "AreaPath không đủ thông tin: ${device.areaPath}",
// Colors.orangeAccent,
// Colors.white);
// }
// }
// } catch (e) {
// showNoIconTopSnackBar(context, "Lỗi trong getDeviceInfomation: $e",
// Colors.orangeAccent, Colors.white);
// }
}
Future<Province> getProvinceByName(String name) async {
final response = await apiServices.getProvincesByName(name);
final data = jsonDecode(response);
if (data != null &&
data.containsKey('items') &&
data['items'] != null &&
data['items'].isNotEmpty) {
List<dynamic> items = data['items'];
List<Province> provinces = Province.fromJsonDynamicList(items);
Future<Province> getProvinceByName(BuildContext context, String name) async {
return await apiServices.execute(context, () async {
List<Province> provinces = await apiServices.getProvincesByName(name);
if (provinces.isNotEmpty) {
return provinces[0];
}
}
} else {
return Province(name: "null");
}
Future<District> getDistrictByName(String name, String provinceCode) async {
final response = await apiServices.getDistrictsByName(name);
if (response != "") {
final data = jsonDecode(response);
List<dynamic> items = data['items'];
if (items.isNotEmpty) {
List<District> districts = District.fromJsonDynamicList(items);
if (districts.isNotEmpty) {
for (var district in districts) {
if (district.provinceCode == provinceCode) {
return district;
}
}
}
}
}
return District(name: "null");
});
}
Future<Ward> getWardByName(String name, String districtCode) async {
final response = await apiServices.getWarsdByName(name);
final data = jsonDecode(response);
if (data != null && data['items'] != null) {
List<dynamic> items = data['items'];
if (items.isNotEmpty) {
List<Ward> wards = Ward.fromJsonDynamicList(items);
if (wards.isNotEmpty) {
for (var ward in wards) {
if (ward.districtCode == districtCode) {
return ward;
Future<District> getDistrictByName(
BuildContext context, String name, String provinceCode) async {
return apiServices.execute(context, () async {
final districts = await apiServices.getDistrictsByName(name);
return districts.firstWhere(
(district) => district.provinceCode == provinceCode,
orElse: () => District(name: "null"),
);
});
}
}
}
}
}
return Ward(name: "null");
Future<Ward> getWardByName(
BuildContext context, String name, String districtCode) async {
return apiServices.execute(context, () async {
final wards = await apiServices.getWardsByName(name);
return wards.firstWhere(
(ward) => ward.districtCode == districtCode,
orElse: () => Ward(name: "null"),
);
});
}
Future<void> updateDevice(
@@ -232,6 +362,7 @@ class DeviceUpdateBloc extends BlocBase {
String districtCode,
String wardCode,
) async {
await apiServices.execute(context, () async {
DateTime dateTime = DateTime.now();
String formattedDateTime =
DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
@@ -251,5 +382,30 @@ class DeviceUpdateBloc extends BlocBase {
appLocalization(context).notification_update_device_success,
appLocalization(context).notification_update_device_failed,
);
});
// try {
// DateTime dateTime = DateTime.now();
// String formattedDateTime =
// DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
// Map<String, dynamic> body = {
// "name": name,
// "area_province": provinceCode,
// "area_district": districtCode,
// "area_ward": wardCode,
// "latitude": latitude,
// "longitude": longitude,
// "note": "User updated device infomation at $formattedDateTime",
// };
// int statusCode = await apiServices.updateOwnerDevice(thingID, body);
// showSnackBarResponseByStatusCodeNoIcon(
// context,
// statusCode,
// appLocalization(context).notification_update_device_success,
// appLocalization(context).notification_update_device_failed,
// );
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
}

View File

@@ -1,11 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/constant/app/app_constants.dart';
import '../product/services/api_services.dart';
import '../product/shared/shared_snack_bar.dart';
import '../product/utils/device_utils.dart';
class DevicesManagerBloc extends BlocBase {
@@ -73,7 +73,8 @@ class DevicesManagerBloc extends BlocBase {
// }
// }
void getDeviceByState(int state) async {
void getDeviceByState(BuildContext context, int state) async {
try {
sinkTagStates.add([state]);
Map<String, List<Device>> deviceByState = {
@@ -85,34 +86,38 @@ class DevicesManagerBloc extends BlocBase {
};
List<Device> devices = [];
String body;
List<Device> originalDevices = [];
if (state != -2) {
body =
await apiServices.getOwnerDeviceByState({"state": state.toString()});
originalDevices = await apiServices
.getOwnerDeviceByState({"state": state.toString()});
} else {
body = await apiServices.getOwnerDevices();
originalDevices = await apiServices.getOwnerDevices();
}
if (body.isNotEmpty) {
final data = jsonDecode(body);
List<dynamic> items = data['items'];
List<Device> originalDevices = Device.fromJsonDynamicList(items);
List<Device> publicDevices = [];
for (var device in originalDevices) {
if (device.visibility == "PUBLIC") {
publicDevices.add(device);
}
}
devices = (state != -2)
? DeviceUtils.instance.sortDeviceAZByName(originalDevices)
: DeviceUtils.instance.sortDeviceByState(originalDevices);
? DeviceUtils.instance.sortDeviceAZByName(publicDevices)
: DeviceUtils.instance.sortDeviceByState(publicDevices);
if (state == -2) {
for (var device in originalDevices) {
for (var device in publicDevices) {
String stateKey = _getStateKey(device.state!);
deviceByState[stateKey]!.add(device);
}
sinkDeviceByState.add(deviceByState);
}
}
sinkAllDevices.add(devices);
} catch (e) {
if (!context.mounted) return;
showErrorTopSnackBarCustom(context, e.toString());
}
}
String _getStateKey(int state) {

View File

@@ -1,15 +1,13 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'package:flutter/widgets.dart';
import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/services/api_services.dart';
import '../product/services/language_services.dart';
import '../product/utils/response_status_utils.dart';
import '../feature/inter_family/group_detail/group_detail_model.dart';
class DetailGroupBloc extends BlocBase {
@@ -29,12 +27,10 @@ class DetailGroupBloc extends BlocBase {
@override
void dispose() {}
Future<void> getGroupDetail(String groupID) async {
final body = await apiServices.getGroupDetail(groupID);
final data = jsonDecode(body);
Future<void> getGroupDetail(BuildContext context, String groupID) async {
await apiServices.execute(context, () async {
List<DeviceOfGroup> warningDevices = [];
if (data != null) {
GroupDetail group = GroupDetail.fromJson(data);
GroupDetail group = await apiServices.getGroupDetail(groupID);
sinkDetailGroup.add(group);
if (group.devices != null) {
for (var device in group.devices!) {
@@ -44,43 +40,104 @@ class DetailGroupBloc extends BlocBase {
}
sinkWarningDevice.add(warningDevices);
}
}
});
// try {
// List<DeviceOfGroup> warningDevices = [];
// GroupDetail group = await apiServices.getGroupDetail(groupID);
// sinkDetailGroup.add(group);
// if (group.devices != null) {
// for (var device in group.devices!) {
// if (device.state == 1) {
// warningDevices.add(device);
// }
// }
// sinkWarningDevice.add(warningDevices);
// }
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
Future<void> approveUserToGroup(BuildContext context, String groupID,
String userID, String userName) async {
await apiServices.execute(context, () async {
Map<String, dynamic> body = {"group_id": groupID, "user_id": userID};
int statusCode = await apiServices.approveGroup(body);
showSnackBarResponseByStatusCode(context, statusCode,
"Đã duyệt $userName vào nhóm!", "Duyệt $userName thất bại!");
});
// try {
// Map<String, dynamic> body = {"group_id": groupID, "user_id": userID};
// int statusCode = await apiServices.approveGroup(body);
// showSnackBarResponseByStatusCode(context, statusCode,
// "Đã duyệt $userName vào nhóm!", "Duyệt $userName thất bại!");
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
Future<void> deleteOrUnapproveUser(BuildContext context, String groupID,
String userID, String userName) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteUserInGroup(groupID, userID);
showSnackBarResponseByStatusCode(context, statusCode,
"Đã xóa người dùng $userName", "Xóa người dùng $userName thất bại");
});
// try {
// int statusCode = await apiServices.deleteUserInGroup(groupID, userID);
// showSnackBarResponseByStatusCode(context, statusCode,
// "Đã xóa người dùng $userName", "Xóa người dùng $userName thất bại");
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
Future<void> deleteDevice(BuildContext context, String groupID,
String thingID, String deviceName) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteDeviceInGroup(groupID, thingID);
showSnackBarResponseByStatusCode(context, statusCode,
"Đã xóa thiết bị $deviceName", "Xóa thiết bị $deviceName thất bại");
});
// try {
// int statusCode = await apiServices.deleteDeviceInGroup(groupID, thingID);
// showSnackBarResponseByStatusCode(context, statusCode,
// "Đã xóa thiết bị $deviceName", "Xóa thiết bị $deviceName thất bại");
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
Future<void> leaveGroup(
BuildContext context, String groupID, String userID) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteUserInGroup(groupID, userID);
showSnackBarResponseByStatusCode(
context,
statusCode,
appLocalization(context).notification_leave_group_success,
appLocalization(context).notification_leave_group_failed);
});
// try {
// int statusCode = await apiServices.deleteUserInGroup(groupID, userID);
// showSnackBarResponseByStatusCode(
// context,
// statusCode,
// appLocalization(context).notification_leave_group_success,
// appLocalization(context).notification_leave_group_failed);
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
Future<void> updateDeviceNameInGroup(
BuildContext context, String thingID, String newAlias) async {
await apiServices.execute(context, () async {
Map<String, dynamic> body = {"thing_id": thingID, "alias": newAlias};
int statusCode = await apiServices.updateDeviceAlias(body);
showSnackBarResponseByStatusCode(
@@ -89,17 +146,29 @@ class DetailGroupBloc extends BlocBase {
appLocalization(context).notification_update_device_success,
appLocalization(context).notification_update_device_failed,
);
});
// try {
// Map<String, dynamic> body = {"thing_id": thingID, "alias": newAlias};
// int statusCode = await apiServices.updateDeviceAlias(body);
// showSnackBarResponseByStatusCode(
// context,
// statusCode,
// appLocalization(context).notification_update_device_success,
// appLocalization(context).notification_update_device_failed,
// );
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
Future<List<Device>> getOwnerDevices() async {
List<Device> allDevices = [];
String body = await apiServices.getOwnerDevices();
if (body != "") {
final data = jsonDecode(body);
List<dynamic> items = data['items'];
allDevices = Device.fromJsonDynamicList(items);
}
return allDevices;
Future<List<Device>> getOwnerDevices(BuildContext context) {
return apiServices.execute(context, () async {
final originalDevices = await apiServices.getOwnerDevices();
return originalDevices
.where((device) => device.visibility == "PUBLIC")
.toList();
});
}
Future<void> addDeviceToGroup(
@@ -107,6 +176,7 @@ class DetailGroupBloc extends BlocBase {
Map<String, dynamic> body = {
"thing_id": thingID,
};
await apiServices.execute(context, () async {
int statusCode = await apiServices.addDeviceToGroup(groupID, body);
showSnackBarResponseByStatusCode(
context,
@@ -114,5 +184,18 @@ class DetailGroupBloc extends BlocBase {
appLocalization(context).notification_add_device_success,
appLocalization(context).notification_add_device_failed,
);
});
// try {
// int statusCode = await apiServices.addDeviceToGroup(groupID, body);
// showSnackBarResponseByStatusCode(
// context,
// statusCode,
// appLocalization(context).notification_add_device_success,
// appLocalization(context).notification_add_device_failed,
// );
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
}

View File

@@ -1,34 +1,143 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../product/extension/context_extension.dart';
import '../product/services/api_services.dart';
import '../feature/home/device_alias_model.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/services/language_services.dart';
import '../product/utils/device_utils.dart';
class HomeBloc extends BlocBase {
APIServices apiServices = APIServices();
final allDevicesAliasMap = StreamController<Map<String,List<DeviceWithAlias>>>.broadcast();
StreamSink<Map<String,List<DeviceWithAlias>>> get sinkAllDevicesAliasMap =>
final allDevicesAliasMap =
StreamController<Map<String, List<DeviceWithAlias>>?>.broadcast();
StreamSink<Map<String, List<DeviceWithAlias>>?> get sinkAllDevicesAliasMap =>
allDevicesAliasMap.sink;
Stream<Map<String,List<DeviceWithAlias>>> get streamAllDevicesAliasMap =>
Stream<Map<String, List<DeviceWithAlias>>?> get streamAllDevicesAliasMap =>
allDevicesAliasMap.stream;
final allDevicesAliasJoinedMap = StreamController<Map<String,List<DeviceWithAlias>>>.broadcast();
StreamSink<Map<String,List<DeviceWithAlias>>> get sinkAllDevicesAliasJoinedMap =>
allDevicesAliasJoinedMap.sink;
Stream<Map<String,List<DeviceWithAlias>>> get streamAllDevicesAliasJoinedMap =>
allDevicesAliasJoinedMap.stream;
// final allDevicesAliasJoinedMap =
// StreamController<Map<String, List<DeviceWithAlias>>>.broadcast();
// StreamSink<Map<String, List<DeviceWithAlias>>>
// get sinkAllDevicesAliasJoinedMap => allDevicesAliasJoinedMap.sink;
// Stream<Map<String, List<DeviceWithAlias>>>
// get streamAllDevicesAliasJoinedMap => allDevicesAliasJoinedMap.stream;
final countNotification = StreamController<int>.broadcast();
StreamSink<int> get sinkCountNotification => countNotification.sink;
Stream<int> get streamCountNotification => countNotification.stream;
final hasJoinedDevice = StreamController<bool?>.broadcast();
StreamSink<bool?> get sinkHasJoinedDevice => hasJoinedDevice.sink;
Stream<bool?> get streamHasJoinedDevice => hasJoinedDevice.stream;
final ownerDevicesStatus =
StreamController<Map<String, List<DeviceWithAlias>>>.broadcast();
StreamSink<Map<String, List<DeviceWithAlias>>>
get sinkOwnerDevicesStatus => ownerDevicesStatus.sink;
StreamSink<Map<String, List<DeviceWithAlias>>> get sinkOwnerDevicesStatus =>
ownerDevicesStatus.sink;
Stream<Map<String, List<DeviceWithAlias>>> get streamOwnerDevicesStatus =>
ownerDevicesStatus.stream;
@override
void dispose() {}
final aliasDevices = StreamController<List<DeviceWithAlias>?>.broadcast();
StreamSink<List<DeviceWithAlias>?> get sinkAliasDevices => aliasDevices.sink;
Stream<List<DeviceWithAlias>?> get streamAliasDevices => aliasDevices.stream;
void getOwnerAndJoinedDevices(BuildContext context) async {
await apiServices.execute(context, () async {
List<DeviceWithAlias> devices = await apiServices.getDashBoardDevices().handleApiError();
List<DeviceWithAlias> publicDevices = [];
for (var device in devices) {
if (device.visibility == "PUBLIC") {
publicDevices.add(device);
}
}
// getDeviceStatusAliasMap(publicDevices);
sinkAllDevicesAliasMap.add(null);
sinkAliasDevices.add(publicDevices);
if (!context.mounted) return;
getOwnerDeviceState(context, publicDevices);
});
}
void getOwnerDeviceState(BuildContext context,List<DeviceWithAlias> allDevices) async {
// int notificationCount = 0;
Map<String, List<DeviceWithAlias>> ownerDevicesStatus = {};
if (!context.mounted) return;
sinkOwnerDevicesStatus.add(ownerDevicesStatus);
int count = 0;
for (var device in allDevices) {
// if (device.isOwner != true) continue;
if (!context.mounted) return;
Map<String, dynamic> sensorMap = DeviceUtils.instance
.getDeviceSensors(context, device.status?.sensors ?? []);
if (device.state == 1 || device.state == 3) {
ownerDevicesStatus["state"] ??= [];
ownerDevicesStatus["state"]!.add(device);
if (!context.mounted) return;
sinkOwnerDevicesStatus.add(ownerDevicesStatus);
count++;
}
final noDataMessage = appLocalization(context).no_data_message;
if (sensorMap['sensorBattery'] != noDataMessage) {
if (double.parse(sensorMap['sensorBattery']) <= 20) {
ownerDevicesStatus['battery'] ??= [];
ownerDevicesStatus['battery']!.add(device);
if (!context.mounted) return;
sinkOwnerDevicesStatus.add(ownerDevicesStatus);
count++;
}
}
}
if (!context.mounted) return;
sinkCountNotification.add(count);
}
void getDeviceStatusAliasMap(List<DeviceWithAlias> devices) {
Map<String, List<DeviceWithAlias>> allDevicesAliasMap = {};
for (var key in ['all', 'online', 'offline', 'warning', 'not-use']) {
allDevicesAliasMap[key] = [];
}
for (DeviceWithAlias device in devices) {
allDevicesAliasMap['all']!.add(device);
if (device.state == 0 || device.state == 1) {
allDevicesAliasMap['online']!.add(device);
}
if (device.state == -1) {
allDevicesAliasMap['offline']!.add(device);
}
if (device.state == 1) {
allDevicesAliasMap['warning']!.add(device);
}
if (device.state == -2) {
allDevicesAliasMap['not-use']!.add(device);
}
}
sinkAllDevicesAliasMap.add(allDevicesAliasMap);
}
@override
void dispose() {
allDevicesAliasMap.close();
// allDevicesAliasJoinedMap.close();
countNotification.close();
hasJoinedDevice.close();
ownerDevicesStatus.close();
aliasDevices.close();
}
}

View File

@@ -1,12 +1,9 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import '../product/constant/app/app_constants.dart';
import '../product/constant/app/app_constants.dart';
import '../product/services/api_services.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/services/language_services.dart';
@@ -35,43 +32,32 @@ class InterFamilyBloc extends BlocBase {
@override
void dispose() {}
void getAllGroup(String role) async {
void getAllGroup(BuildContext context, String role) async {
List<Group> groups = [];
sinkCurrentGroups.add(groups);
final body = await apiServices.getAllGroups();
if (body.isNotEmpty) {
final data = jsonDecode(body);
List<dynamic> items = data["items"];
groups = Group.fromJsonDynamicList(items);
await apiServices.execute(context, () async {
groups = await apiServices.getAllGroups();
groups = sortGroupByName(groups);
List<Group> currentGroups = groups.where(
(group) {
bool isPublic = group.visibility == "PUBLIC";
if (role == ApplicationConstants.OWNER_GROUP) {
return group.isOwner == true && isPublic;
}
if (role == ApplicationConstants.PARTICIPANT_GROUP) {
return group.isOwner == null && isPublic;
}
return false;
},
).toList();
sinkCurrentGroups.add(currentGroups);
} else {
log("Get groups from API failed");
}
log("Inter Family Role: $role");
});
}
Future<void> createGroup(
BuildContext context, String name, String description) async {
APIServices apiServices = APIServices();
await apiServices.execute(context, () async {
Map<String, dynamic> body = {"name": name, "description": description};
int? statusCode = await apiServices.createGroup(body);
showSnackBarResponseByStatusCode(
@@ -79,10 +65,12 @@ class InterFamilyBloc extends BlocBase {
statusCode,
appLocalization(context).notification_add_group_success,
appLocalization(context).notification_add_group_failed);
});
}
Future<void> changeGroupInfomation(BuildContext context, String groupID,
Future<void> changeGroupInformation(BuildContext context, String groupID,
String name, String description) async {
await apiServices.execute(context, () async {
Map<String, dynamic> body = {"name": name, "description": description};
int statusCode = await apiServices.updateGroup(body, groupID);
showSnackBarResponseByStatusCode(
@@ -90,27 +78,32 @@ class InterFamilyBloc extends BlocBase {
statusCode,
appLocalization(context).notification_update_group_success,
appLocalization(context).notification_update_group_failed);
});
}
Future<void> joinGroup(BuildContext context, String groupID) async {
Map<String, dynamic> body = {
"group_id": groupID,
};
await apiServices.execute(context, () async {
int statusCode = await apiServices.joinGroup(groupID, body);
showSnackBarResponseByStatusCode(
context,
statusCode,
appLocalization(context).notification_join_request_group_success,
appLocalization(context).notification_join_request_group_failed);
});
}
Future<void> deleteGroup(BuildContext context, String groupID) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteGroup(groupID);
showSnackBarResponseByStatusCode(
context,
statusCode,
appLocalization(context).notification_delete_group_success,
appLocalization(context).notification_delete_group_failed);
});
}
List<Group> sortGroupByName(List<Group> groups) {

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import '../product/base/bloc/base_bloc.dart';
class LoginBloc extends BlocBase {
final loginRequest = StreamController<Map<String, dynamic>>.broadcast();
StreamSink<Map<String, dynamic>> get sinkLoginRequest => loginRequest.sink;
Stream<Map<String, dynamic>> get streamLoginRequest => loginRequest.stream;
@@ -12,7 +11,5 @@ class LoginBloc extends BlocBase{
Stream<bool> get streamIsShowPassword => isShowPassword.stream;
@override
void dispose() {
}
void dispose() {}
}

View File

@@ -1,13 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/cache/local_manager.dart';
import 'package:sfm_app/product/constant/app/api_path_constant.dart';
import 'package:sfm_app/product/constant/enums/local_keys_enums.dart';
import 'package:sfm_app/product/network/network_manager.dart';
import '../product/cache/local_manager.dart';
import '../product/constant/app/api_path_constant.dart';
import '../product/constant/enums/local_keys_enums.dart';
import '../product/network/network_manager.dart';
import '../product/base/bloc/base_bloc.dart';
import '../product/services/api_services.dart';
import '../feature/bell/bell_model.dart';
@@ -42,14 +41,14 @@ class MainBloc extends BlocBase {
@override
void dispose() {}
void getUserProfile() async {
String data = await apiServices.getUserDetail();
User user = User.fromJson(jsonDecode(data));
void getUserProfile(BuildContext context) async {
await apiServices.execute(context, () async {
User user = await apiServices.getUserDetail();
sinkUserProfile.add(user);
});
}
getFCMTokenAndPresentations() async {
String? firebaseAppToken = await FirebaseMessaging.instance.getToken();
if (firebaseAppToken != null) {
@@ -62,12 +61,9 @@ class MainBloc extends BlocBase {
Future<int> sendNotificationToken(String token) async {
String uid = await getUID();
Map<String,dynamic> body = {
"user_id": uid,
"app_token": token
};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.NOTIFICATION_TOKEN_PATH, body);
Map<String, dynamic> body = {"user_id": uid, "app_token": token};
int statusCode = await NetworkManager.instance!
.updateDataInServer(APIPathConstants.NOTIFICATION_TOKEN_PATH, body);
return statusCode;
}

View File

@@ -1,9 +1,9 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../product/services/map_services.dart';
import '../product/shared/shared_snack_bar.dart';
import '../feature/devices/device_model.dart';
@@ -75,6 +75,4 @@ class MapBloc extends BlocBase {
context, "Không tìm thấy đường", Colors.orange, Colors.white);
}
}
}

View File

@@ -1,15 +1,17 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import '../product/services/api_services.dart';
import '../feature/settings/profile/profile_model.dart';
import '../product/base/bloc/base_bloc.dart';
class SettingsBloc extends BlocBase {
// Settings Screen
APIServices apiServices = APIServices();
final userProfile = StreamController<User>.broadcast();
StreamSink<User> get sinkUserProfile => userProfile.sink;
Stream<User> get streamUserProfile => userProfile.stream;
// Profile Screen
final isChangeProfileInfomation = StreamController<bool>.broadcast();
StreamSink<bool> get sinkIsChangeProfileInfomation =>
@@ -17,9 +19,13 @@ class SettingsBloc extends BlocBase {
Stream<bool> get streamIsChangeProfileInfomation =>
isChangeProfileInfomation.stream;
void getUserProfile(BuildContext context) async {
await apiServices.execute(context, () async {
User user = await apiServices.getUserDetail();
sinkUserProfile.add(user);
});
}
@override
void dispose() {
}
void dispose() {}
}

View File

@@ -0,0 +1,34 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../product/services/api_services.dart';
import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart';
class SimDataBloc extends BlocBase{
APIServices apiServices = APIServices();
final devices = StreamController<List<Device>>.broadcast();
StreamSink<List<Device>> get sinkDevices => devices.sink;
Stream<List<Device>> get streamDevices => devices.stream;
@override
void dispose() {}
void getOwnerDevices(BuildContext context) async {
await apiServices.execute(context, () async {
List<Device> devices = [];
devices = await apiServices.getOwnerDevices();
List<Device> publicDevices = [];
for (var device in devices) {
if (device.visibility == "PUBLIC") {
publicDevices.add(device);
}
}
sinkDevices.add(publicDevices);
});
}
}

View File

@@ -1,11 +1,12 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../product/extension/context_extension.dart';
import '../../product/services/language_services.dart';
import '../../bloc/bell_bloc.dart';
import '../../product/base/bloc/base_bloc.dart';
import '../../product/services/api_services.dart';
import '../../product/shared/shared_component_loading_animation.dart';
import '../../product/shared/shared_loading_animation.dart';
import 'bell_model.dart';
class BellScreen extends StatefulWidget {
@@ -56,11 +57,7 @@ class _BellScreenState extends State<BellScreen> {
initialData: items,
builder: (context, bellSnapshot) {
return check
? Center(
child: CircularProgressIndicator(
value: context.highValue,
),
)
? const SharedLoadingAnimation()
: bellSnapshot.data?.isEmpty ?? true
? Center(
child: Text(
@@ -78,16 +75,8 @@ class _BellScreenState extends State<BellScreen> {
if (index < bellSnapshot.data!.length) {
return GestureDetector(
onTap: () async {
List<String> read = [];
read.add(bellSnapshot.data![index].id!);
int code = await apiServices
.updateStatusOfNotification(read);
if (code == 200) {
read.clear();
} else {
read.clear();
}
refresh();
readNotification(
bellSnapshot.data![index].id!);
},
child: Column(
children: [
@@ -143,7 +132,7 @@ class _BellScreenState extends State<BellScreen> {
builder: (context, hasMoreSnapshot) {
return Center(
child: hasMoreSnapshot.data ?? hasMore
? const CircularProgressIndicator()
? const SharedComponentLoadingAnimation()
: Text(
appLocalization(context)
.bell_read_all,
@@ -173,10 +162,20 @@ class _BellScreenState extends State<BellScreen> {
getBellNotification(offset);
}
void readNotification(String id) async {
await apiServices.execute(context, () async {
List<String> read = [];
read.add(id);
await apiServices.updateStatusOfNotification(read);
read.clear();
});
refresh();
}
Future<void> getBellNotification(int offset) async {
apiServices.execute(context, () async {
bell = await apiServices.getBellNotifications(
offset.toString(), (offset + 20).toString());
if (bell.items!.isEmpty) {
hasMore = false;
bellBloc.sinkHasMore.add(hasMore);
@@ -187,6 +186,7 @@ class _BellScreenState extends State<BellScreen> {
}
bellBloc.bellItems.add(items);
check = false;
});
}
String timeAgo(BuildContext context, DateTime dateTime) {

View File

@@ -1,7 +1,10 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../product/shared/shared_component_loading_animation.dart';
import '../../product/shared/shared_loading_animation.dart';
import 'widgets/tag_widget.dart';
import '../devices/device_model.dart';
import '../../bloc/device_logs_bloc.dart';
@@ -12,7 +15,6 @@ import '../../product/services/language_services.dart';
import '../../product/shared/shared_snack_bar.dart';
import '../../product/utils/date_time_utils.dart';
import '../../product/utils/device_utils.dart';
import '../../product/base/bloc/base_bloc.dart';
import 'device_logs_model.dart';
@@ -42,7 +44,7 @@ class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
() {
if (controller.position.maxScrollExtent == controller.offset) {
offset += 30;
deviceLogsBloc.getDeviceLogByThingID(
deviceLogsBloc.getDeviceLogByThingID(context,
offset, thingID, dateTime!, sensors);
}
},
@@ -63,11 +65,9 @@ class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
body: StreamBuilder<List<Device>>(
stream: deviceLogsBloc.streamAllDevices,
builder: (context, allDevicesSnapshot) {
if (allDevicesSnapshot.data?[0].thingId == null) {
deviceLogsBloc.getAllDevices();
return const Center(
child: CircularProgressIndicator(),
);
if (allDevicesSnapshot.data == null) {
deviceLogsBloc.getAllDevices(context);
return const SharedLoadingAnimation();
} else {
return StreamBuilder<List<SensorLogs>>(
stream: deviceLogsBloc.streamSensors,
@@ -90,9 +90,7 @@ class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
hint: Text(
appLocalization(context)
.choose_device_dropdownButton,
style: const TextStyle(
fontSize: 14,
),
style: context.responsiveBodySmall
),
items: allDevicesSnapshot.data?.isNotEmpty ?? false
? allDevicesSnapshot.data!
@@ -101,9 +99,7 @@ class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
value: device.thingId,
child: Text(
device.name!,
style: const TextStyle(
fontSize: 14,
),
style: context.responsiveBodySmall
),
),
)
@@ -192,6 +188,7 @@ class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
);
} else {
deviceLogsBloc.getDeviceLogByThingID(
context,
offset,
thingID,
dateTime!,
@@ -250,7 +247,7 @@ class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
return Center(
child: hasMoreSnapshot.data ??
hasMore
? const CircularProgressIndicator()
? const SharedComponentLoadingAnimation()
: Text(
appLocalization(context)
.main_no_data),
@@ -314,7 +311,7 @@ class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
deviceLogsBloc.sensors.add(sensors);
hasMore = true;
deviceLogsBloc.sinkHasMore.add(hasMore);
deviceLogsBloc.getDeviceLogByThingID(offset, thingID, dateTime!, sensors);
deviceLogsBloc.getDeviceLogByThingID(context,offset, thingID, dateTime!, sensors);
}
Widget leadingList(SensorLogs sensor) {

View File

@@ -1,7 +1,9 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:sfm_app/product/shared/shared_snack_bar.dart';
import '../../bloc/devices_manager_bloc.dart';
import '../../product/shared/shared_snack_bar.dart';
import '../../product/utils/response_status_utils.dart';
import '../../product/constant/enums/role_enums.dart';
import '../../product/services/api_services.dart';
@@ -10,7 +12,8 @@ import '../../product/constant/icon/icon_constants.dart';
import '../../product/extension/context_extension.dart';
import '../../product/services/language_services.dart';
addNewDevice(BuildContext context, String role) async {
addNewDevice(BuildContext context, String role,
DevicesManagerBloc deviceManagerBloc) async {
TextEditingController extIDController = TextEditingController(text: "");
TextEditingController deviceNameController = TextEditingController(text: "");
ScaffoldMessenger.of(context).showSnackBar(
@@ -25,8 +28,7 @@ addNewDevice(BuildContext context, String role) async {
children: [
Text(
'${appLocalization(context).add_device_title}: ',
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
style: context.responsiveBodyLargeWithBold,
),
Container(
alignment: Alignment.centerRight,
@@ -77,7 +79,8 @@ addNewDevice(BuildContext context, String role) async {
Colors.white);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
} else {
addDevices(context, role, extID, deviceName);
addDevices(
context, role, extID, deviceName, deviceManagerBloc);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
}
},
@@ -90,11 +93,12 @@ addNewDevice(BuildContext context, String role) async {
);
}
void addDevices(
BuildContext context, String role, String extID, String deviceName) async {
void addDevices(BuildContext context, String role, String extID,
String deviceName, DevicesManagerBloc deviceManagerBloc) async {
APIServices apiServices = APIServices();
Map<String, dynamic> body = {};
if (role == RoleEnums.ADMIN.name) {
await apiServices.execute(context,() async {
body = {"ext_id": extID, "name": deviceName};
int statusCode = await apiServices.createDeviceByAdmin(body);
showSnackBarResponseByStatusCode(
@@ -102,7 +106,10 @@ void addDevices(
statusCode,
appLocalization(context).notification_create_device_success,
appLocalization(context).notification_create_device_failed);
deviceManagerBloc.getDeviceByState(context, -2);
});
} else {
await apiServices.execute(context,() async {
body = {"ext_id": extID};
int statusCode = await apiServices.registerDevice(body);
showSnackBarResponseByStatusCode(
@@ -110,5 +117,7 @@ void addDevices(
statusCode,
appLocalization(context).notification_add_device_success,
appLocalization(context).notification_device_not_exist);
deviceManagerBloc.getDeviceByState(context, -2);
});
}
}

View File

@@ -1,6 +1,7 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import '../../bloc/devices_manager_bloc.dart';
import '../../product/constant/enums/role_enums.dart';
import '../../product/services/api_services.dart';
@@ -47,20 +48,24 @@ deleteOrUnregisterDevice(BuildContext context, DevicesManagerBloc devicesBloc,
Map<String, dynamic> body = {
"ext_id": extID,
};
await apiServices.execute(context, () async {
int statusCode = await apiServices.unregisterDevice(body);
showSnackBarResponseByStatusCode(
context,
statusCode,
appLocalization(context).notification_delete_device_success,
appLocalization(context).notification_delete_device_failed);
devicesBloc.getDeviceByState(-2);
devicesBloc.getDeviceByState(context, -2);
});
} else {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteDeviceByAdmin(extID);
showSnackBarResponseByStatusCode(
context,
statusCode,
appLocalization(context).notification_delete_device_success,
appLocalization(context).notification_delete_device_failed);
devicesBloc.getDeviceByState(-2);
devicesBloc.getDeviceByState(context, -2);
});
}
}

View File

@@ -1,18 +1,19 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:simple_ripple_animation/simple_ripple_animation.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../product/shared/shared_component_loading_animation.dart';
import '../../../product/constant/image/image_constants.dart';
import '../../../product/shared/shared_line_chart.dart';
import '../../../product/shared/shared_curve.dart';
import '../../../product/shared/shared_loading_animation.dart';
import '../../device_log/device_logs_model.dart';
import '../device_model.dart';
import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../bloc/device_detail_bloc.dart';
@@ -46,8 +47,8 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
late DetailDeviceBloc detailDeviceBloc;
Completer<GoogleMapController> controller = Completer();
CameraPosition initialCamera =
const CameraPosition(target: LatLng(20.966048511844402, 105.74977710843086), zoom: 15);
CameraPosition initialCamera = const CameraPosition(
target: LatLng(20.966048511844402, 105.74977710843086), zoom: 15);
Timer? getDeviceDetailTimer;
@override
@@ -68,6 +69,7 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
@override
void dispose() {
getDeviceDetailTimer?.cancel();
super.dispose();
}
BoxDecoration boxDecoration = BoxDecoration(
@@ -84,16 +86,6 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
return StreamBuilder<Device>(
stream: detailDeviceBloc.streamDeviceInfo,
builder: (context, deviceSnapshot) {
if (deviceSnapshot.data?.extId == null) {
detailDeviceBloc.getDeviceDetail(
context,
widget.thingID,
controller,
);
return const Center(
child: CircularProgressIndicator(),
);
} else {
return StreamBuilder<Map<String, dynamic>>(
stream: detailDeviceBloc.streamDeviceSensor,
builder: (context, sensorSnapshot) {
@@ -118,32 +110,11 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
children: [
Positioned.fill(
child: Image.asset(
ImageConstants.instance.getImage('smoke-detector'),
ImageConstants.instance
.getImage('smoke-detector'),
fit: BoxFit.fill,
),
),
// Center(
// child: Container(
// height: 50,
// width: 400,
// // color: Colors.blueAccent,
// alignment: Alignment.centerRight,
// margin: const EdgeInsets.fromLTRB(0, 0, 0, 50),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// const SizedBox(),
// Text(
// deviceSnapshot.data?.name ?? "",
// style: const TextStyle(
// fontSize: 25,
// fontWeight: FontWeight.w600,
// ),
// ),
// ],
// ),
// ),
// ),
],
),
),
@@ -156,13 +127,14 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
height: context.dynamicHeight(0.08),
width: context.dynamicWidth(0.5),
decoration: BoxDecoration(
color: DeviceUtils.instance
.getTableRowColor(context,deviceSnapshot.data?.state ?? 3),
color: DeviceUtils.instance.getTableRowColor(
context, deviceSnapshot.data?.state ?? 3),
borderRadius: BorderRadius.circular(50),
),
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
SizedBox(
height: context.mediumValue,
@@ -170,17 +142,20 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
child: deviceSnapshot.data?.state == 1
? RippleAnimation(
color: Colors.red,
delay: context.dynamicMilliSecondDuration(
delay: context
.dynamicMilliSecondDuration(
800,
),
repeat: true,
minRadius: 10,
ripplesCount: 5,
duration: context.dynamicMilliSecondDuration(
duration: context
.dynamicMilliSecondDuration(
1800,
),
child: CircleAvatar(
backgroundColor: Colors.transparent,
backgroundColor:
Colors.transparent,
minRadius: context.mediumValue,
maxRadius: context.mediumValue,
backgroundImage: AssetImage(
@@ -191,8 +166,10 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
),
)
: CircleAvatar(
backgroundColor:
DeviceUtils.instance.getTableRowColor(context,
backgroundColor: DeviceUtils
.instance
.getTableRowColor(
context,
deviceSnapshot.data?.state ?? 3,
),
minRadius: context.mediumValue,
@@ -237,19 +214,18 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
child: Padding(
padding: context.paddingLow,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
.paginated_data_table_column_deviceSignal,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
style: context
.responsiveBodyLargeWithBold),
SizedBox(
height: context.dynamicWidth(0.12),
width: context.dynamicWidth(0.12),
@@ -259,7 +235,8 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
sensorSnapshot.data!['sensorCsq'],
),
size: 30,
color: DeviceUtils.instance.getSignalIconColor(
color: DeviceUtils.instance
.getSignalIconColor(
context,
sensorSnapshot.data!['sensorCsq'],
),
@@ -268,8 +245,10 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Container(
height: context.dynamicHeight(0.09),
@@ -277,9 +256,11 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
child: Text(
sensorSnapshot.data!['sensorCsq'],
style: TextStyle(
color: DeviceUtils.instance.getSignalIconColor(
color: DeviceUtils.instance
.getSignalIconColor(
context,
sensorSnapshot.data!['sensorCsq'],
sensorSnapshot
.data!['sensorCsq'],
),
fontSize: 40,
fontWeight: FontWeight.w900,
@@ -299,10 +280,12 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
child: Padding(
padding: context.paddingLow,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
@@ -316,14 +299,18 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
height: context.dynamicWidth(0.12),
width: context.dynamicWidth(0.12),
child: Image.asset(
DeviceUtils.instance.getDeviceBatteryImg(
DeviceUtils.instance
.getDeviceBatteryImg(
int.parse(
sensorSnapshot.data!['sensorBattery'],
sensorSnapshot
.data!['sensorBattery'],
),
),
color: DeviceUtils.instance.getDeviceBatteryColor(
color: DeviceUtils.instance
.getDeviceBatteryColor(
int.parse(
sensorSnapshot.data!['sensorBattery'],
sensorSnapshot
.data!['sensorBattery'],
),
),
),
@@ -331,18 +318,23 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Container(
height: context.dynamicHeight(0.09),
alignment: Alignment.centerLeft,
child: Text(
sensorSnapshot.data!['sensorBattery'],
sensorSnapshot
.data!['sensorBattery'],
style: TextStyle(
color: DeviceUtils.instance.getDeviceBatteryColor(
color: DeviceUtils.instance
.getDeviceBatteryColor(
int.parse(
sensorSnapshot.data!['sensorBattery'],
sensorSnapshot
.data!['sensorBattery'],
),
),
fontSize: 50,
@@ -360,9 +352,11 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
child: Text(
'%',
style: TextStyle(
color: DeviceUtils.instance.getDeviceBatteryColor(
color: DeviceUtils.instance
.getDeviceBatteryColor(
int.parse(
sensorSnapshot.data!['sensorBattery'],
sensorSnapshot
.data!['sensorBattery'],
),
),
fontSize: 30,
@@ -390,7 +384,8 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
@@ -405,9 +400,11 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
width: context.dynamicWidth(0.12),
child: Image.asset(
'assets/icons/temperature.png',
color: DeviceUtils.instance.getDeviceTempColor(
color: DeviceUtils.instance
.getDeviceTempColor(
int.parse(
sensorSnapshot.data!['sensorTemp'],
sensorSnapshot
.data!['sensorTemp'],
),
),
),
@@ -423,20 +420,28 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
width: double.infinity,
height: 20,
decoration: BoxDecoration(
color: Colors.grey.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10),
color: Colors.grey
.withValues(alpha: 0.3),
borderRadius:
BorderRadius.circular(10),
),
),
LayoutBuilder(
builder: (context, constraints) => Container(
builder: (context, constraints) =>
Container(
width: constraints.maxWidth *
(int.parse(sensorSnapshot.data!['sensorTemp']) / 75),
(int.parse(sensorSnapshot
.data!['sensorTemp']) /
75),
height: 20,
decoration: BoxDecoration(
color: DeviceUtils.instance.getDeviceTempColor(
int.parse(sensorSnapshot.data!['sensorTemp']),
color: DeviceUtils.instance
.getDeviceTempColor(
int.parse(sensorSnapshot
.data!['sensorTemp']),
),
borderRadius: BorderRadius.circular(10),
borderRadius:
BorderRadius.circular(10),
),
),
)
@@ -446,25 +451,26 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"${sensorSnapshot.data!['sensorTemp']} °C",
style: TextStyle(
color: DeviceUtils.instance.getDeviceTempColor(
color: DeviceUtils.instance
.getDeviceTempColor(
int.parse(
sensorSnapshot.data!['sensorTemp'],
sensorSnapshot
.data!['sensorTemp'],
),
),
fontSize: 30,
fontWeight: FontWeight.w900,
),
),
const Text(
Text(
"75 °C",
style: TextStyle(
fontSize: 20,
),
style: context.responsiveBodyLarge
),
],
)
@@ -480,11 +486,9 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context).paginated_data_table_column_devicePower,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
appLocalization(context)
.paginated_data_table_column_devicePower,
style: context.responsiveBodyLargeWithBold
),
SizedBox(
height: context.dynamicWidth(0.12),
@@ -504,12 +508,11 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
stream: detailDeviceBloc.streamSensorTemps,
builder: (context, sensorTempsSnapshot) {
if (sensorTempsSnapshot.data == null) {
detailDeviceBloc.getNearerSensorValue(widget.thingID);
detailDeviceBloc
.getNearerSensorValue(context,widget.thingID);
return const AspectRatio(
aspectRatio: 3,
child: Center(
child: CircularProgressIndicator(),
),
child: SharedComponentLoadingAnimation(),
);
} else if (sensorTempsSnapshot.data!.isEmpty) {
return Center(
@@ -523,7 +526,8 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
child: Container(
margin: context.paddingLow,
child: sharedLineChart(
appLocalization(context).detail_device_volt_message,
appLocalization(context)
.detail_device_volt_message,
sensorTempsSnapshot.data ?? [],
),
),
@@ -550,33 +554,48 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
Radius.circular(15),
),
),
child: deviceSnapshot.data!.settings!.latitude != ""
child: deviceSnapshot
.data!.settings!.latitude !=
""
? StreamBuilder<String>(
stream: detailDeviceBloc.streamDeviceLocation,
stream: detailDeviceBloc
.streamDeviceLocation,
builder: (context, locationSnapshot) {
if (locationSnapshot.data == null) {
detailDeviceBloc.findLocation(
context, deviceSnapshot.data!.areaPath!);
context,
deviceSnapshot
.data!.areaPath!);
}
return GoogleMap(
initialCameraPosition: initialCamera,
initialCameraPosition:
initialCamera,
mapType: MapType.normal,
markers: {
Marker(
infoWindow: InfoWindow(
title: locationSnapshot.data ?? "",
title:
locationSnapshot.data ??
"",
),
markerId: MarkerId(deviceSnapshot.data!.thingId!),
markerId: MarkerId(
deviceSnapshot
.data!.thingId!),
position: LatLng(
double.parse(
deviceSnapshot.data!.settings!.latitude!),
double.parse(
deviceSnapshot.data!.settings!.longitude!),
double.parse(deviceSnapshot
.data!
.settings!
.latitude!),
double.parse(deviceSnapshot
.data!
.settings!
.longitude!),
),
),
},
onMapCreated: (mapcontroller) {
controller.complete(mapcontroller);
controller
.complete(mapcontroller);
},
mapToolbarEnabled: false,
zoomControlsEnabled: false,
@@ -591,11 +610,9 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
),
),
Text(
appLocalization(context).device_update_location,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
appLocalization(context)
.device_update_location,
style: context.responsiveBodyLargeWithBold
)
],
),
@@ -605,18 +622,26 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
),
),
);
} else {
}
else {
detailDeviceBloc.getDeviceDetail(
context,
widget.thingID,
controller,
);
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(),
body: const Center(
child: CircularProgressIndicator(),
child: SharedLoadingAnimation(),
),
);
}
},
);
}
},
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:search_choices/search_choices.dart';
import '../../../product/shared/shared_loading_animation.dart';
import '../device_model.dart';
import '../../../bloc/device_update_bloc.dart';
import 'map_dialog.dart';
@@ -8,7 +9,6 @@ import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/api_services.dart';
import '../../../product/services/language_services.dart';
import '../../../product/shared/model/district_model.dart';
import '../../../product/shared/model/province_model.dart';
import '../../../product/shared/model/ward_model.dart';
@@ -47,7 +47,7 @@ class _DeviceUpdateScreenState extends State<DeviceUpdateScreen> {
void initState() {
super.initState();
deviceUpdateBloc = BlocProvider.of(context);
deviceUpdateBloc.getAllProvinces();
deviceUpdateBloc.getAllProvinces(context);
}
@override
@@ -72,16 +72,16 @@ class _DeviceUpdateScreenState extends State<DeviceUpdateScreen> {
initialData: device,
builder: (context, deviceInfoSnapshot) {
if (deviceInfoSnapshot.data!.thingId == null) {
deviceUpdateBloc.getDeviceInfomation(
deviceUpdateBloc.getDeviceInformation(
context,
widget.thingID,
districtsData,
wardsData,
// provincesData,
// districtsData,
// wardsData,
deviceNameController,
deviceLatitudeController,
deviceLongitudeController);
return const Center(
child: CircularProgressIndicator(),
);
return const SharedLoadingAnimation();
} else {
return StreamBuilder<bool>(
stream: deviceUpdateBloc.streamIsChanged,
@@ -245,7 +245,7 @@ class _DeviceUpdateScreenState extends State<DeviceUpdateScreen> {
.sinkProvinceData
.add(provinceData);
deviceUpdateBloc
.getAllDistricts(
.getAllDistricts(context,
value.code);
selectedDistrict = "";
districtData['name'] =
@@ -318,7 +318,7 @@ class _DeviceUpdateScreenState extends State<DeviceUpdateScreen> {
.sinkDistrictData
.add(districtData);
deviceUpdateBloc
.getAllWards(value.code);
.getAllWards(context,value.code);
selectedWard = "";
wardData['name'] =
selectedWard!;

View File

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

View File

@@ -1,9 +1,11 @@
import 'dart:async';
import 'package:data_table_2/data_table_2.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../product/shared/shared_component_loading_animation.dart';
import '../../product/shared/shared_loading_animation.dart';
import 'add_new_device_widget.dart';
import 'delete_device_widget.dart';
import 'device_model.dart';
@@ -29,7 +31,7 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
late DevicesManagerBloc devicesManagerBloc;
String role = "Undefine";
APIServices apiServices = APIServices();
List<Device> devices = [];
// List<Device> devices = [];
Timer? getAllDevicesTimer;
List<Widget> tags = [];
int tagIndex = -2;
@@ -41,7 +43,7 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
const duration = Duration(seconds: 10);
getAllDevicesTimer = Timer.periodic(
duration,
(Timer t) => devicesManagerBloc.getDeviceByState(tagIndex),
(Timer t) => devicesManagerBloc.getDeviceByState(context,tagIndex),
);
}
@@ -58,15 +60,43 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
body: StreamBuilder<List<int>>(
stream: devicesManagerBloc.streamTagStates,
builder: (context, tagSnapshot) {
return StreamBuilder<String>(
stream: devicesManagerBloc.streamUserRole,
initialData: role,
builder: (context, roleSnapshot) {
return SafeArea(
child: StreamBuilder<List<Device>>(
stream: devicesManagerBloc.streamAllDevices,
initialData: devices,
builder: (context, allDeviceSnapshot) {
if (allDeviceSnapshot.data?.isEmpty ?? devices.isEmpty) {
if(allDeviceSnapshot.data == null){
devicesManagerBloc
.getDeviceByState(tagSnapshot.data?[0] ?? -2);
return const Center(child: CircularProgressIndicator());
.getDeviceByState(context,tagSnapshot.data?[0] ?? -2);
return const SharedLoadingAnimation();
}
if (allDeviceSnapshot.data!.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).colorScheme.primary),
borderRadius: BorderRadius.circular(50)
),
child: IconButton(onPressed: (){
ScaffoldMessenger.of(context)
.clearSnackBars();
addNewDevice(context,
roleSnapshot.data ?? role, devicesManagerBloc);
}, iconSize: 50, icon: const Icon(Icons.add),),),
SizedBox(height: context.mediumValue,),
Text(appLocalization(context).dont_have_device, style: context.responsiveBodyMediumWithBold,)
],
),
);
} else {
if (tagSnapshot.data!.isNotEmpty) {
tagIndex = tagSnapshot.data![0];
@@ -83,11 +113,7 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
devicesManagerBloc: devicesManagerBloc,
),
SizedBox(height: context.lowValue),
StreamBuilder<String>(
stream: devicesManagerBloc.streamUserRole,
initialData: role,
builder: (context, roleSnapshot) {
return SizedBox(
SizedBox(
height: getTableHeight(allDeviceSnapshot.data?.length ?? 1),
child: PaginatedDataTable2(
wrapInCard: false,
@@ -103,7 +129,7 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
child: Text(
appLocalization(context)
.paginated_data_table_title,
style: context.headlineMediumTextStyle,
style: context.responsiveBodyLargeWithBold,
),
),
columns: [
@@ -111,7 +137,8 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
RoleEnums.ADMIN.name ||
roleSnapshot.data ==
RoleEnums.USER.name)
DataColumn(
DataColumn2(
fixedWidth: context.dynamicWidth(0.3),
label: Text(appLocalization(context)
.paginated_data_table_column_deviceName),
),
@@ -172,37 +199,31 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
ScaffoldMessenger.of(context)
.clearSnackBars();
addNewDevice(context,
roleSnapshot.data ?? role);
roleSnapshot.data ?? role, devicesManagerBloc);
},
icon: IconConstants.instance
.getMaterialIcon(Icons.add))
],
source: DeviceSource(
devices: allDeviceSnapshot.data ?? devices,
devices: allDeviceSnapshot.data ?? [],
context: context,
devicesBloc: devicesManagerBloc,
role: role,
),
),
);
},
),
SizedBox(height: context.lowValue),
Text(
appLocalization(context).overview_message,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
style: context.responsiveBodyLargeWithBold
),
StreamBuilder<Map<String, List<Device>>>(
stream: devicesManagerBloc.streamDeviceByState,
builder: (context, devicesByStateSnapshot) {
if (devicesByStateSnapshot.data == null) {
devicesManagerBloc.getDeviceByState(
devicesManagerBloc.getDeviceByState(context,
tagSnapshot.data?[0] ?? -2);
return const Center(
child: CircularProgressIndicator());
return const SharedComponentLoadingAnimation();
} else {
return SharedPieChart(
deviceByState:
@@ -219,6 +240,8 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
},
),
);
}
);
}),
);
}
@@ -230,7 +253,7 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
double getTableHeight(int dataLength){
if(dataLength < 3){
return context.dynamicHeight(0.3);
return context.dynamicHeight(0.35);
}else {
return context.dynamicHeight(0.4);
}
@@ -370,7 +393,7 @@ class TagState extends StatelessWidget {
),
GestureDetector(
onTap: () {
devicesManagerBloc.getDeviceByState(-2);
devicesManagerBloc.getDeviceByState(context,-2);
},
child: const Icon(
Icons.close,

View File

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

View File

@@ -1,11 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:alarm/alarm.dart';
import 'package:flutter/material.dart';
import '../../product/shared/shared_loading_animation.dart';
import '../../product/shared/shared_component_loading_animation.dart';
import 'shared/alert_card.dart';
import 'shared/warning_card.dart';
import '../../product/utils/device_utils.dart';
import 'device_alias_model.dart';
import 'shared/overview_card.dart';
import '../settings/device_notification_settings/device_notification_settings_model.dart';
@@ -17,7 +18,6 @@ import '../../product/base/bloc/base_bloc.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
@@ -25,33 +25,54 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> {
late HomeBloc homeBloc;
APIServices apiServices = APIServices();
Map<String, List<DeviceWithAlias>> allDevicesAliasMap = {};
Map<String, List<DeviceWithAlias>> allDevicesAliasJoinedMap = {};
List<DeviceWithAlias> devices = [];
bool isFunctionCall = false;
Timer? getAllDevicesTimer;
int notificationCount = 0;
Map<String, List<DeviceWithAlias>> ownerDevicesStatus = {};
List<String> ownerDevicesState = [];
@override
void initState() {
super.initState();
homeBloc = BlocProvider.of(context);
getOwnerAndJoinedDevices();
const duration = Duration(seconds: 10);
getAllDevicesTimer = Timer.periodic(duration, (Timer t) => getOwnerAndJoinedDevices());
WidgetsBinding.instance.addPostFrameCallback((_) {
// Code ở đây chạy sau khi giao diện render xong
getAllDevicesTimer = Timer.periodic(
duration, (Timer t) => homeBloc.getOwnerAndJoinedDevices(context));
// Ví dụ: gọi API, scroll tới vị trí nào đó, v.v.
});
}
Future<void> loadAlarms() async {
final alarms = await Alarm.getAlarms();
log("Alarms: $alarms");
}
@override
void dispose() {
getAllDevicesTimer?.cancel();
homeBloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<List<DeviceWithAlias>?>(
stream: homeBloc.streamAliasDevices,
builder: (context, aliasDevicesSnapshot) {
if (aliasDevicesSnapshot.data == null) {
homeBloc.getOwnerAndJoinedDevices(context);
return const SharedLoadingAnimation();
} else {
homeBloc.getOwnerDeviceState(
context, aliasDevicesSnapshot.data ?? []);
homeBloc.getDeviceStatusAliasMap(aliasDevicesSnapshot.data ?? []);
checkSettingDevice(aliasDevicesSnapshot.data ?? []);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
loadAlarms();
},
child: const Icon(Icons.alarm),
),
body: Padding(
padding: context.paddingLow,
child: SingleChildScrollView(
@@ -67,10 +88,16 @@ class _HomeScreenState extends State<HomeScreen> {
StreamBuilder<int>(
stream: homeBloc.streamCountNotification,
builder: (context, countSnapshot) {
if (countSnapshot.data == null) {
homeBloc.getOwnerDeviceState(
context, aliasDevicesSnapshot.data ?? []);
return const Text("0");
} else {
return Text(
"(${countSnapshot.data ?? 0})",
style: context.titleMediumTextStyle,
);
}
},
)
],
@@ -78,46 +105,109 @@ class _HomeScreenState extends State<HomeScreen> {
SizedBox(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: StreamBuilder<Map<String, List<DeviceWithAlias>>>(
child:
StreamBuilder<Map<String, List<DeviceWithAlias>>>(
stream: homeBloc.streamOwnerDevicesStatus,
builder: (context, snapshot) {
if (snapshot.data?['state'] != null || snapshot.data?['battery'] != null) {
return ConstrainedBox(
constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width),
builder: (context, ownerDevicesStatusSnapshot) {
if (ownerDevicesStatusSnapshot.data == null) {
homeBloc.getOwnerDeviceState(
context, aliasDevicesSnapshot.data ?? []);
return const SharedComponentLoadingAnimation();
} else {
return AnimatedSwitcher(
duration: context.lowDuration,
transitionBuilder: (Widget child,
Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.2),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
));
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: offsetAnimation,
child: child,
),
);
},
child: ownerDevicesStatusSnapshot
.data?['state'] !=
null ||
ownerDevicesStatusSnapshot
.data?['battery'] !=
null
? ConstrainedBox(
key: const ValueKey('data'),
constraints: BoxConstraints(
minWidth: MediaQuery.of(context)
.size
.width),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
if (snapshot.data?['state'] != null)
...snapshot.data!['state']!
if (ownerDevicesStatusSnapshot
.data?['state'] !=
null)
...ownerDevicesStatusSnapshot
.data!['state']!
.map(
(item) => SizedBox(
width: context.dynamicWidth(0.95),
child: FutureBuilder<Widget>(
future: warningCard(context, apiServices, item),
builder: (context, warningCardSnapshot) {
if (warningCardSnapshot.hasData) {
return warningCardSnapshot.data!;
width: context
.dynamicWidth(0.95),
child: FutureBuilder<
Widget>(
future: warningCard(
context,
apiServices,
item),
builder: (context,
warningCardSnapshot) {
if (warningCardSnapshot
.hasData) {
return warningCardSnapshot
.data!;
} else {
return const SizedBox.shrink();
return const SizedBox
.shrink();
}
},
),
),
)
.toList(),
if (snapshot.data?['battery'] != null)
...snapshot.data!['battery']!
if (ownerDevicesStatusSnapshot
.data?['battery'] !=
null)
...ownerDevicesStatusSnapshot
.data!['battery']!
.map(
(batteryItem) => SizedBox(
width: context.dynamicWidth(0.95),
child: FutureBuilder<Widget>(
future: notificationCard(
context, "lowBattery", appLocalization(context).low_battery_message, batteryItem),
builder: (context, warningCardSnapshot) {
if (warningCardSnapshot.hasData) {
return warningCardSnapshot.data!;
width: context
.dynamicWidth(0.95),
child: FutureBuilder<
Widget>(
future:
notificationCard(
context,
"lowBattery",
appLocalization(
context)
.low_battery_message,
batteryItem,
),
builder: (context,
warningCardSnapshot) {
if (warningCardSnapshot
.hasData) {
return warningCardSnapshot
.data!;
} else {
return const SizedBox.shrink();
return const SizedBox
.shrink();
}
},
),
@@ -126,48 +216,49 @@ class _HomeScreenState extends State<HomeScreen> {
.toList(),
],
),
);
} else {
return Padding(
)
: Padding(
key: const ValueKey('no_data'),
padding: context.paddingMedium,
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.check_circle_outline_rounded,
Icons
.check_circle_outline_rounded,
size: 40,
color: Colors.green,
),
SizedBox(width: context.lowValue),
SizedBox(
width: context.lowValue),
Text(
appLocalization(context).notification_description,
appLocalization(context)
.notification_description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
overflow:
TextOverflow.ellipsis,
softWrap: true,
textAlign: TextAlign.start,
),
],
),
),
),
);
}
},
),
),
),
StreamBuilder<Map<String, List<DeviceWithAlias>>>(
StreamBuilder<Map<String, List<DeviceWithAlias>>?>(
stream: homeBloc.streamAllDevicesAliasMap,
builder: (context, allDevicesAliasMapSnapshot) {
if (!allDevicesAliasMapSnapshot.hasData ||
allDevicesAliasMapSnapshot.data == null) {
return const Center(child: CircularProgressIndicator());
}
if (allDevicesAliasMapSnapshot.data == null) {
homeBloc.getDeviceStatusAliasMap(
aliasDevicesSnapshot.data ?? []);
return const SharedComponentLoadingAnimation();
} else {
final data = allDevicesAliasMapSnapshot.data!;
return OverviewCard(
isOwner: true,
@@ -175,34 +266,10 @@ class _HomeScreenState extends State<HomeScreen> {
active: data['online']?.length ?? 0,
inactive: data['offline']?.length ?? 0,
warning: data['warn']?.length ?? 0,
unused: data['not-use']?.length ?? 0);
}),
SizedBox(height: context.lowValue),
StreamBuilder<Map<String, List<DeviceWithAlias>>>(
stream: homeBloc.streamAllDevicesAliasJoinedMap,
builder: (context, allDevicesAliasJoinedMapSnapshot) {
if (!allDevicesAliasJoinedMapSnapshot.hasData ||
allDevicesAliasJoinedMapSnapshot.data == null) {
return const Center(child: CircularProgressIndicator());
}
final data = allDevicesAliasJoinedMapSnapshot.data!;
final total = data['all']?.length ?? 0;
final active = data['online']?.length ?? 0;
final inactive = data['offline']?.length ?? 0;
final warning = data['warn']?.length ?? 0;
final unused = data['not-use']?.length ?? 0;
if (total == 0 && active == 0 && inactive == 0 && warning == 0 && unused == 0) {
return const SizedBox.shrink();
}
return OverviewCard(
isOwner: false,
total: total,
active: active,
inactive: inactive,
warning: warning,
unused: unused,
unused: data['not-use']?.length ?? 0,
showUnused: false,
);
}
},
),
],
@@ -211,121 +278,31 @@ class _HomeScreenState extends State<HomeScreen> {
),
);
}
void getOwnerAndJoinedDevices() async {
String response = await apiServices.getDashBoardDevices();
final data = jsonDecode(response);
List<dynamic> result = data["items"];
devices = DeviceWithAlias.fromJsonDynamicList(result);
getOwnerDeviceState(devices);
checkSettingDevice(devices);
getDeviceStatusAliasMap(devices);
}
void getOwnerDeviceState(List<DeviceWithAlias> allDevices) async {
List<DeviceWithAlias> ownerDevices = [];
for (var device in allDevices) {
if (device.isOwner!) {
ownerDevices.add(device);
}
}
if (ownerDevicesState.isEmpty || ownerDevicesState.length < devices.length) {
ownerDevicesState.clear();
ownerDevicesStatus.clear();
homeBloc.sinkOwnerDevicesStatus.add(ownerDevicesStatus);
int count = 0;
for (var device in ownerDevices) {
Map<String, dynamic> sensorMap =
DeviceUtils.instance.getDeviceSensors(context, device.status?.sensors ?? []);
if (device.state == 1 || device.state == 3) {
ownerDevicesStatus["state"] ??= [];
ownerDevicesStatus["state"]!.add(device);
homeBloc.sinkOwnerDevicesStatus.add(ownerDevicesStatus);
count++;
}
if (sensorMap['sensorBattery'] != appLocalization(context).no_data_message) {
if (double.parse(sensorMap['sensorBattery']) <= 20) {
ownerDevicesStatus['battery'] ??= [];
ownerDevicesStatus['battery']!.add(device);
homeBloc.sinkOwnerDevicesStatus.add(ownerDevicesStatus);
count++;
}
}
notificationCount = count;
homeBloc.sinkCountNotification.add(notificationCount);
}
}
}
void getDeviceStatusAliasMap(List<DeviceWithAlias> devices) {
allDevicesAliasMap.clear();
allDevicesAliasJoinedMap.clear();
for (var key in ['all', 'online', 'offline', 'warning', 'not-use']) {
allDevicesAliasMap[key] = [];
allDevicesAliasJoinedMap[key] = [];
}
for (DeviceWithAlias device in devices) {
if (device.isOwner == true) {
allDevicesAliasMap['all']!.add(device);
if (device.state == 0 || device.state == 1) {
allDevicesAliasMap['online']!.add(device);
}
if (device.state == -1) {
allDevicesAliasMap['offline']!.add(device);
}
if (device.state == 1) {
allDevicesAliasMap['warning']!.add(device);
}
if (device.state == -2) {
allDevicesAliasMap['not-use']!.add(device);
}
} else {
allDevicesAliasJoinedMap['all']!.add(device);
if (device.state == 0 || device.state == 1) {
allDevicesAliasJoinedMap['online']!.add(device);
}
if (device.state == -1) {
allDevicesAliasJoinedMap['offline']!.add(device);
}
if (device.state == 1) {
allDevicesAliasJoinedMap['warning']!.add(device);
}
if (device.state == -2) {
allDevicesAliasJoinedMap['not-use']!.add(device);
}
}
}
homeBloc.sinkAllDevicesAliasMap.add(allDevicesAliasMap);
homeBloc.sinkAllDevicesAliasJoinedMap.add(allDevicesAliasJoinedMap);
});
}
void checkSettingDevice(List<DeviceWithAlias> devices) async {
if (isFunctionCall) {
log("Ham check setting da duoc goi");
} else {
String? response = await apiServices.getAllSettingsNotificationOfDevices();
if (response != "") {
final data = jsonDecode(response);
final result = data['data'];
// log("Data ${DeviceNotificationSettings.mapFromJson(jsonDecode(data)).values.toList()}");
await apiServices.execute(context, () async {
List<DeviceNotificationSettings> list =
DeviceNotificationSettings.mapFromJson(result).values.toList();
await apiServices.getAllSettingsNotificationOfDevices();
// log("List: $list");
Set<String> thingIdsInList = list.map((device) => device.thingId!).toSet();
Set<String> thingIdsInList =
list.map((device) => device.thingId!).toSet();
for (var device in devices) {
if (!thingIdsInList.contains(device.thingId)) {
log("Device with Thing ID ${device.thingId} is not in the notification settings list.");
await apiServices.setupDeviceNotification(device.thingId!, device.name!);
await apiServices.execute(context, () async {
await apiServices.setupDeviceNotification(
device.thingId!, device.name!);
});
} else {
log("All devices are in the notification settings list.");
}
}
} else {
log("apiServices: getAllSettingsNotificationofDevices error!");
}
});
}
isFunctionCall = true;
}

View File

@@ -3,24 +3,24 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:sfm_app/feature/home/device_alias_model.dart';
import '../../../product/shared/shared_rocket_container.dart';
import '../../../product/constant/enums/app_route_enums.dart';
import '../../../product/constant/image/image_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../device_alias_model.dart';
Future<Widget> notificationCard(BuildContext context, String notiticationType,
Future<Widget> notificationCard(BuildContext context, String notificationType,
String notificationTitle, DeviceWithAlias device) async {
String location = "";
if (device.areaPath != "") {
location = await DeviceUtils.instance
.getFullDeviceLocation(context, device.areaPath!);
.getFullDeviceLocation(context, device.areaPath!, "");
}
String path = "";
// DateTime time = DateTime.now();
String time = "";
for (var sensor in device.status!.sensors!) {
if (sensor.name! == "7") {
@@ -29,7 +29,7 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
time = DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
}
}
if (notiticationType == "lowBattery") {
if (notificationType == "lowBattery") {
path = ImageConstants.instance.getImage("low_battery");
}
return Card(
@@ -62,10 +62,7 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
SizedBox(
child: Text(
"${appLocalization(context).device_title} ${device.isOwner! ? device.name : device.alias}",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
style: context.responsiveBodyLargeWithBold,
maxLines: 2,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -92,7 +89,7 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
Expanded(
child: Text(
location,
style: const TextStyle(fontSize: 15),
style: context.responsiveBodySmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -111,7 +108,7 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
Expanded(
child: Text(
time.toString(),
style: const TextStyle(fontSize: 15),
style: context.responsiveBodySmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -120,11 +117,16 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
),
],
),
Align(
SizedBox(
height: context.lowValue,
),
device.isOwner!
? Align(
alignment: Alignment.centerRight,
child: OutlinedButton(
style: const ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.blueAccent),
backgroundColor:
WidgetStatePropertyAll(Colors.blueAccent),
),
onPressed: () {
context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
@@ -132,11 +134,28 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
},
child: Text(
appLocalization(context).detail_message,
style: const TextStyle(
color: Colors.white,
style: const TextStyle(color: Colors.white),
),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipPath(
clipper: SharedRocketContainer(),
child: Container(
padding: EdgeInsets.all(context.lowValue),
height: context.mediumValue,
width: context.dynamicWidth(0.22),
decoration: BoxDecoration(
color: Colors.green[300],
),
child: Text(
appLocalization(context).interfamily_page_name,
),
),
),
],
),
],
),

View File

@@ -1,69 +1,93 @@
import 'package:flutter/material.dart';
import 'status_card.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
class OverviewCard extends StatelessWidget {
class OverviewCard extends StatefulWidget {
final bool isOwner;
final int total;
final int active;
final int inactive;
final int warning;
final int unused;
final bool showTotal;
final bool showActive;
final bool showInactive;
final bool showWarning;
final bool showUnused;
const OverviewCard(
{super.key,
const OverviewCard({
super.key,
required this.isOwner,
required this.total,
required this.active,
required this.inactive,
required this.warning,
required this.unused});
required this.unused,
this.showTotal = true,
this.showActive = true,
this.showInactive = true,
this.showWarning = true,
this.showUnused = true,
});
@override
State<OverviewCard> createState() => _OverviewCardState();
}
class _OverviewCardState extends State<OverviewCard> {
@override
Widget build(BuildContext context) {
return Card(
elevation: 8,
return FittedBox(
alignment: Alignment.topCenter,
child: SizedBox(
width: context.width,
child: Card(
// elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: context.paddingNormal,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
isOwner
widget.isOwner
? appLocalization(context).overview_message
: appLocalization(context).interfamily_page_name,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
style: context.h2,
),
SizedBox(height: context.normalValue),
Column(
children: [
if (widget.showTotal)
StatusCard(
label: appLocalization(context).total_nof_devices_message,
count: total,
count: widget.total,
color: Colors.blue,
),
if (widget.showActive)
StatusCard(
label: appLocalization(context).active_devices_message,
count: active,
count: widget.active,
color: Colors.green,
),
if (widget.showInactive)
StatusCard(
label: appLocalization(context).inactive_devices_message,
count: inactive,
count: widget.inactive,
color: Colors.grey,
),
if (widget.showWarning)
StatusCard(
label: appLocalization(context).warning_devices_message,
count: warning,
count: widget.warning,
color: Colors.orange,
),
if (widget.showUnused)
StatusCard(
label: appLocalization(context).unused_devices_message,
count: unused,
count: widget.unused,
color: Colors.yellow,
),
],
@@ -71,6 +95,8 @@ class OverviewCard extends StatelessWidget {
],
),
),
),
),
);
}
}

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import '../../../product/extension/context_extension.dart';
class StatusCard extends StatelessWidget {
final String label;
final int count;
@@ -27,14 +29,8 @@ class StatusCard extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontSize: 18)),
Text(
count.toString(),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
Text(label, style: context.responsiveBodyLarge),
Text(count.toString(), style: context.h2),
],
),
);

View File

@@ -3,7 +3,8 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:badges/badges.dart' as badges;
import '../../../product/shared/shared_rocket_container.dart';
import '../device_alias_model.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/constant/image/image_constants.dart';
@@ -21,7 +22,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
String fullLocation = "";
if (device.areaPath != "") {
fullLocation = await DeviceUtils.instance
.getFullDeviceLocation(context, device.areaPath!);
.getFullDeviceLocation(context, device.areaPath!, "");
}
String time = "";
for (var sensor in device.status!.sensors!) {
@@ -33,7 +34,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
}
if (device.state! == 3) {
backgroundColor = Colors.grey;
textColor = Colors.black;
textColor = Colors.white;
message = appLocalization(context).in_progress_message;
} else if (device.state! == 2) {
backgroundColor = const Color.fromARGB(255, 6, 138, 72);
@@ -42,32 +43,18 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
} else if (device.state! == 1) {
backgroundColor = const Color.fromARGB(255, 250, 63, 63);
textColor = Colors.white;
if(device.isOwner == true){
message = appLocalization(context).button_fake_fire_message;
}else{
message = appLocalization(context).smoke_detecting_message_lowercase;
}
} else {
backgroundColor = Colors.black;
textColor = Colors.white;
message = appLocalization(context).disconnect_message_uppercase;
}
return badges.Badge(
badgeAnimation: const badges.BadgeAnimation.fade(),
position: badges.BadgePosition.bottomStart(bottom: 15, start: 10),
badgeContent: device.isOwner!
? null
: Text(
appLocalization(context).interfamily_page_name,
style: TextStyle(
fontSize: 20,
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold),
),
badgeStyle: badges.BadgeStyle(
shape: badges.BadgeShape.square,
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.white, width: 2),
badgeColor: Theme.of(context).colorScheme.surfaceDim,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
),
child: Card(
return Card(
child: Padding(
padding: context.paddingLow,
child: Column(
@@ -97,10 +84,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
SizedBox(
child: Text(
"${appLocalization(context).device_title}: ${device.isOwner! ? device.name : device.alias}",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
style: context.responsiveBodyLargeWithBold,
maxLines: 2,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -128,7 +112,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
Expanded(
child: Text(
fullLocation,
style: const TextStyle(fontSize: 15),
style: context.responsiveBodySmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -147,7 +131,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
Expanded(
child: Text(
time,
style: const TextStyle(fontSize: 15),
style: context.responsiveBodySmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -159,14 +143,16 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
SizedBox(
height: context.lowValue,
),
Row(
device.isOwner == true
? Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconButton.outlined(
onPressed: () async => {},
// displayListOfFireStationPhoneNumbers(testDevice),
icon: IconConstants.instance.getMaterialIcon(Icons.call),
icon:
IconConstants.instance.getMaterialIcon(Icons.call),
iconSize: 25,
style: ButtonStyle(
backgroundColor:
@@ -199,7 +185,8 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
WidgetStatePropertyAll(backgroundColor)),
onPressed: () async {
if (message ==
appLocalization(context).button_fake_fire_message) {
appLocalization(context)
.button_fake_fire_message) {
await showDialog(
context: context,
builder: (context) => AlertDialog(
@@ -212,8 +199,11 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
actions: [
TextButton(
onPressed: () async {
await apiServices.execute(context,
() async {
int statusCode = await apiServices
.confirmFakeFireByUser(device.thingId!);
.confirmFakeFireByUser(
device.thingId!);
if (statusCode == 200) {
showNoIconTopSnackBar(
context,
@@ -229,13 +219,15 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
Colors.red,
Colors.red);
}
});
Navigator.of(context).pop();
},
child: Text(
appLocalization(context)
.confirm_fake_fire_sure_message,
style:
const TextStyle(color: Colors.red)),
style: const TextStyle(
color: Colors.red)),
),
TextButton(
onPressed: () {
@@ -250,7 +242,8 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
} else {
showNoIconTopSnackBar(
context,
appLocalization(context).let_PCCC_handle_message,
appLocalization(context)
.let_PCCC_handle_message,
Colors.orange,
Colors.white);
}
@@ -263,10 +256,45 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
),
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipPath(
clipper: SharedRocketContainer(),
child: Container(
padding: EdgeInsets.all(context.lowValue),
height: context.mediumValue,
width: context.dynamicWidth(0.22),
decoration: BoxDecoration(
color: Colors.green[300],
),
child: Text(
appLocalization(context).interfamily_page_name,
),
),
),
SizedBox(width: context.mediumValue),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: OutlinedButton(
style: ButtonStyle(
backgroundColor:
WidgetStatePropertyAll(backgroundColor)),
onPressed: () async {},
child: Text(
message,
style: TextStyle(color: textColor),
),
),
),
),
],
),
],
),
),
),
);
}

View File

@@ -2,16 +2,17 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import '../../../product/extension/context_extension.dart';
import '../../../bloc/group_detail_bloc.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/services/language_services.dart';
import '../../devices/device_model.dart';
import 'group_detail_model.dart';
addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
String groupID, List<DeviceOfGroup> devices) async {
List<Device> ownerDevices = await detailGroupBloc.getOwnerDevices();
List<Device> ownerDevices = await detailGroupBloc.getOwnerDevices(context);
List<String> selectedItems = [];
List<String> selectedDevices = [];
if (devices.isNotEmpty) {
@@ -36,7 +37,7 @@ addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
),
hint: Text(
appLocalization(context).choose_device_dropdownButton,
style: const TextStyle(fontSize: 14),
style: context.responsiveBodySmall,
),
items: ownerDevices
.map(
@@ -73,7 +74,7 @@ addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
Expanded(
child: Text(
item.name!,
style: const TextStyle(fontSize: 14),
style: context.responsiveBodySmall,
),
),
],
@@ -130,7 +131,7 @@ addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
for (var device in selectedItems) {
await detailGroupBloc.addDeviceToGroup(
context, groupID, device);
await detailGroupBloc.getGroupDetail(groupID);
await detailGroupBloc.getGroupDetail(context,groupID);
}
Navigator.of(dialogContext).pop();

View File

@@ -1,9 +1,10 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../bloc/group_detail_bloc.dart';
import '../../../product/shared/shared_loading_animation.dart';
import 'group_detail_model.dart';
import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/constant/app/app_constants.dart';
@@ -13,7 +14,6 @@ import '../../../product/services/api_services.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../../product/utils/response_status_utils.dart';
import 'add_device_to_group_dialog.dart';
class DetailGroupScreen extends StatefulWidget {
@@ -35,14 +35,14 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
const duration = Duration(seconds: 10);
getGroupDetailTimer = Timer.periodic(
duration,
(Timer t) => detailGroupBloc.getGroupDetail(widget.group),
(Timer t) => detailGroupBloc.getGroupDetail(context, widget.group),
);
}
@override
void dispose() {
getGroupDetailTimer?.cancel();
super.dispose();
}
@override
@@ -51,10 +51,8 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
stream: detailGroupBloc.streamDetailGroup,
builder: (context, detailGroupSnapshot) {
if (detailGroupSnapshot.data?.id == null) {
detailGroupBloc.getGroupDetail(widget.group);
return const Center(
child: CircularProgressIndicator(),
);
detailGroupBloc.getGroupDetail(context, widget.group);
return const SharedLoadingAnimation();
} else {
return Scaffold(
key: scaffoldKey,
@@ -140,8 +138,8 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
widget.group,
user.id!,
user.name!);
detailGroupBloc
.getGroupDetail(widget.group);
detailGroupBloc.getGroupDetail(
context, widget.group);
},
icon: const Icon(
Icons.check,
@@ -158,8 +156,8 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
widget.group,
user.id!,
user.name!);
await detailGroupBloc
.getGroupDetail(widget.group);
await detailGroupBloc.getGroupDetail(
context, widget.group);
},
icon: const Icon(
Icons.close,
@@ -205,8 +203,8 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
widget.group,
user.id!,
user.name!);
await detailGroupBloc
.getGroupDetail(widget.group);
await detailGroupBloc.getGroupDetail(
context, widget.group);
},
value: 2,
child: Text(appLocalization(context)
@@ -239,7 +237,7 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
? PopupMenuButton(
icon: IconConstants.instance
.getMaterialIcon(Icons.more_horiz),
itemBuilder: (contex) => [
itemBuilder: (context) => [
PopupMenuItem(
onTap: () {
Navigator.pop(context);
@@ -325,6 +323,7 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
Future.delayed(context.lowDuration).then(
(value) => Navigator.pop(context),
);
await apiServices.execute(context, () async {
int statusCode = await apiServices
.deleteGroup(widget.group);
showSnackBarResponseByStatusCode(
@@ -334,6 +333,7 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
.notification_delete_group_success,
appLocalization(context)
.notification_delete_group_failed);
});
},
child: Text(
appLocalization(context)
@@ -429,7 +429,7 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
}
}
return Expanded(
return SafeArea(
child: Center(
child: devices.isEmpty
? Center(
@@ -461,7 +461,8 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
DeviceUtils.instance.checkStateDevice(
context, devices[index].state!),
style: TextStyle(
color: DeviceUtils.instance.getTableRowColor(context,
color: DeviceUtils.instance.getTableRowColor(
context,
devices[index].state!,
),
),
@@ -493,10 +494,7 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${appLocalization(context).map_result}: ',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black)),
style: context.h3),
Container(
alignment: Alignment.centerRight,
child: IconButton(
@@ -526,7 +524,7 @@ class _DetailGroupScreenState extends State<DetailGroupScreen> {
String alias = aliasController.text;
await detailGroupBloc.updateDeviceNameInGroup(
context, device.thingId!, alias);
await detailGroupBloc.getGroupDetail(widget.group);
await detailGroupBloc.getGroupDetail(context, widget.group);
},
child: Text(appLocalization(context).confirm_button_content)),
)

View File

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

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../bloc/inter_family_bloc.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import 'groups_model.dart';
shareGroup(BuildContext context, Group group) {
@@ -80,7 +80,8 @@ showActionDialog(
if (dialogTitle == appLocalization(context).delete_group_title) {
Navigator.of(dialogContext).pop();
await interFamilyBloc.deleteGroup(context, group.id!);
interFamilyBloc.getAllGroup(role);
// ignore: use_build_context_synchronously
interFamilyBloc.getAllGroup(context,role);
} else {}
},
child: Text(

View File

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

View File

@@ -122,7 +122,7 @@ createOrJoinGroupDialog(
try {
await interFamilyBloc.createGroup(
context, groupName, description);
interFamilyBloc.getAllGroup(role);
interFamilyBloc.getAllGroup(context, role);
Navigator.of(dialogContext).pop();
} catch (e) {
// log("Lỗi khi tạo nhóm: $e");
@@ -131,9 +131,9 @@ createOrJoinGroupDialog(
appLocalization(context)
.change_group_infomation_content) {
try {
await interFamilyBloc.changeGroupInfomation(
await interFamilyBloc.changeGroupInformation(
context, groupID, groupName, description);
interFamilyBloc.getAllGroup(role);
interFamilyBloc.getAllGroup(context, role);
Navigator.of(dialogContext).pop();
} catch (e) {
// log("Lỗi khi sửa nhóm: $e");

View File

@@ -1,24 +1,22 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:go_router/go_router.dart';
import 'package:badges/badges.dart' as badges;
import 'package:persistent_bottom_nav_bar/persistent_bottom_nav_bar.dart';
import 'package:sfm_app/product/utils/permission_handler.dart';
import '../../product/permission/notification_permission.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import '../../product/shared/shared_snack_bar.dart';
import '../../product/services/notification_services.dart';
import '../../product/utils/permission_handler.dart';
import '../../product/permission/notification_permission.dart';
import '../settings/profile/profile_model.dart';
import '../../product/extension/context_extension.dart';
import '../../bloc/home_bloc.dart';
import '../../product/constant/app/app_constants.dart';
import '../../product/constant/enums/app_route_enums.dart';
import '../../product/permission/location_permission.dart';
import '../../product/services/theme_services.dart';
import '../../bloc/devices_manager_bloc.dart';
import '../devices/devices_manager_screen.dart';
@@ -46,22 +44,10 @@ class MainScreen extends StatefulWidget {
State<MainScreen> createState() => _MainScreenState();
}
PersistentTabController controller = PersistentTabController(initialIndex: 0);
// @pragma('vm:entry-point')
// Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// log("Full background message payload: ${message.toMap()}");
// await Firebase.initializeApp();
// final notificationServices = NotificationServices();
// await notificationServices.initLocalNotifications(controller);
// await notificationServices.showNotification(message);
// log("Background message handled: ${message.data['title']}");
// }
class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
APIServices apiServices = APIServices();
// final NotificationServices notificationServices = NotificationServices();
final NotificationServices notificationServices = NotificationServices();
late MainBloc mainBloc;
bool isVN = true;
bool isLight = true;
@@ -89,7 +75,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
mainBloc.sinkIsVNIcon.add(isVN);
mainBloc.sinkThemeMode.add(isLight);
checkAndRequestPermission();
NotificationPermission.instance.checkNotificationPermission(context);
NotificationServices.requestNotificationPermission();
// NotificationPermission.instance.checkNotificationPermission(context);
// bool? notificationStatus = await NotificationPermission.instance.requestNotificationPermission();
// if(notificationStatus == false){
// showNoIconTopSnackBar(context, "Yêu cầu quyền thông báo không thành công", Colors.orange, Colors.white12);
// }
}
@override
@@ -100,7 +91,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
WidgetsBinding.instance.addObserver(this);
initialCheck();
getBellNotification();
mainBloc.getUserProfile();
mainBloc.getUserProfile(context);
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
log("New FCM Token: $newToken");
@@ -108,129 +99,86 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
mainBloc.sendNotificationToken(newToken);
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null ) {
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'your channel id', 'your channel name',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker');
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
flutterLocalNotificationsPlugin.show(
notification.hashCode, notification.title, notification.body, platformChannelSpecifics,
);
}
});
// notificationServices.initLocalNotifications(controller);
// notificationServices.firebaseInit(context);
// NotificationServices().setupInteractMessage(controller);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.inactive) {
log("App Inactive");
} else if (state == AppLifecycleState.resumed) {
log("App Resumed");
} else if (state == AppLifecycleState.paused) {
log("App paused");
} else if (state == AppLifecycleState.detached) {
log("App detached");
}
}
@override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
PersistentBottomNavBarItem(
icon: IconConstants.instance.getMaterialIcon(Icons.home),
title: appLocalization(context).home_page_destination,
activeColorPrimary: Colors.blue,
inactiveColorPrimary: Colors.grey,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.home_outlined),
),
PersistentBottomNavBarItem(
icon: IconConstants.instance.getMaterialIcon(Icons.settings),
title: appLocalization(context).manager_page_destination,
activeColorPrimary: Colors.blue,
inactiveColorPrimary: Colors.grey,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.settings_outlined),
),
PersistentBottomNavBarItem(
icon: IconConstants.instance.getMaterialIcon(Icons.location_on),
title: appLocalization(context).map_page_destination,
activeColorPrimary: Colors.blue,
inactiveColorPrimary: Colors.grey,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.location_on_outlined),
),
PersistentBottomNavBarItem(
icon: IconConstants.instance.getMaterialIcon(Icons.history),
title: appLocalization(context).history_page_destination,
activeColorPrimary: Colors.blue,
inactiveColorPrimary: Colors.grey,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.history_outlined),
),
PersistentBottomNavBarItem(
icon: IconConstants.instance.getMaterialIcon(Icons.group),
title: appLocalization(context).group_page_destination,
activeColorPrimary: Colors.blue,
inactiveColorPrimary: Colors.grey,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.group_outlined),
),
// PersistentBottomNavBarItem(
// icon: IconConstants.instance
// .getMaterialIcon(Icons.notifications_outlined),
// title: appLocalization(context).notification,
// activeColorPrimary: Colors.blue,
// inactiveColorPrimary: Colors.grey,
// inactiveIcon: IconConstants.instance
// .getMaterialIcon(Icons.notifications_outlined),
// ),
];
}
List<Widget> _buildScreens() {
return [
BlocProvider(
@override
Widget build(BuildContext context) {
List<PersistentTabConfig> tabs = [
PersistentTabConfig(
screen: BlocProvider(
child: const HomeScreen(),
blocBuilder: () => HomeBloc(),
),
BlocProvider(
item: ItemConfig(
icon: IconConstants.instance.getMaterialIcon(Icons.home),
title: appLocalization(context).home_page_destination,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.home_outlined),
iconSize: 30,
),
),
PersistentTabConfig(
screen: BlocProvider(
child: const DevicesManagerScreen(),
blocBuilder: () => DevicesManagerBloc()),
BlocProvider(
blocBuilder: () => DevicesManagerBloc(),
),
item: ItemConfig(
icon: IconConstants.instance.getMaterialIcon(Icons.settings),
title: appLocalization(context).manager_page_destination,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.settings_outlined),
iconSize: 30,
),
),
PersistentTabConfig(
screen: BlocProvider(
child: const MapScreen(),
blocBuilder: () => MapBloc(),
),
BlocProvider(
item: ItemConfig(
icon: IconConstants.instance.getMaterialIcon(Icons.location_on),
title: appLocalization(context).map_page_destination,
inactiveIcon: IconConstants.instance
.getMaterialIcon(Icons.location_on_outlined),
iconSize: 30,
),
),
PersistentTabConfig(
screen: BlocProvider(
child: const DeviceLogsScreen(),
blocBuilder: () => DeviceLogsBloc(),
),
BlocProvider(
item: ItemConfig(
icon: IconConstants.instance.getMaterialIcon(Icons.history),
title: appLocalization(context).history_page_destination,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.history_outlined),
iconSize: 30,
),
),
PersistentTabConfig(
screen: BlocProvider(
child: const InterFamilyScreen(),
blocBuilder: () => InterFamilyBloc(),
),
item: ItemConfig(
icon: IconConstants.instance.getMaterialIcon(Icons.group),
title: appLocalization(context).group_page_destination,
inactiveIcon:
IconConstants.instance.getMaterialIcon(Icons.group_outlined),
iconSize: 30,
),
),
];
}
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
stream: mainBloc.streamThemeMode,
initialData: isLight,
@@ -379,41 +327,38 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
],
),
body: PersistentTabView(
context,
stateManagement: false,
controller: controller,
screens: _buildScreens(),
items: _navBarsItems(),
handleAndroidBackButtonPress: true,
resizeToAvoidBottomInset: true,
stateManagement: true,
tabs: tabs,
navBarBuilder: (navBarConfig) => Style6BottomNavBar(
navBarConfig: navBarConfig,
navBarDecoration: NavBarDecoration(
color: themeModeSnapshot.data! ? Colors.white : Colors.black,
borderRadius: BorderRadius.circular(context.mediumValue),
padding: const EdgeInsets.all(10)),
),
backgroundColor:
themeModeSnapshot.data! ? Colors.white : Colors.black,
decoration: NavBarDecoration(
borderRadius: BorderRadius.circular(30.0),
colorBehindNavBar:
themeModeSnapshot.data! ? Colors.white : Colors.black,
),
animationSettings: const NavBarAnimationSettings(
navBarItemAnimation: ItemAnimationSettings(
duration: Duration(milliseconds: 200),
curve: Curves.bounceInOut,
),
screenTransitionAnimation: ScreenTransitionAnimationSettings(
animateTabTransition: true,
navBarOverlap: const NavBarOverlap.none(),
// margin: EdgeInsets.only(
// left: context.lowValue,
// bottom: context.dynamicHeight(0.02),
// right: context.lowValue),
screenTransitionAnimation: const ScreenTransitionAnimation(
curve: Curves.bounceInOut,
duration: Duration(milliseconds: 200),
),
),
navBarStyle: NavBarStyle.style13,
),
);
},
);
}
Future<void> getBellNotification() async {
await apiServices.execute(context, () async {
bell = await apiServices.getBellNotifications("0", "20");
mainBloc.bellBloc.add(bell);
});
}
bool checkStatus(List<BellItems> bells) {

View File

@@ -1,22 +1,20 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_cluster_manager_2/google_maps_cluster_manager_2.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'
hide ClusterManager, Cluster;
import 'package:sfm_app/feature/devices/device_model.dart';
import 'package:sfm_app/bloc/map_bloc.dart';
import 'package:sfm_app/feature/map/widget/on_tap_marker_widget.dart';
import 'package:sfm_app/product/base/bloc/base_bloc.dart';
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
import 'package:sfm_app/product/permission/location_permission.dart';
import 'package:sfm_app/product/services/api_services.dart';
import 'package:sfm_app/product/utils/permission_handler.dart';
import '../../bloc/map_bloc.dart';
import '../../product/base/bloc/base_bloc.dart';
import '../../product/constant/enums/app_theme_enums.dart';
import '../../product/constant/icon/icon_constants.dart';
import '../../product/services/api_services.dart';
import '../../product/utils/permission_handler.dart';
import '../devices/device_model.dart';
import 'widget/on_tap_marker_widget.dart';
class MapScreen extends StatefulWidget {
const MapScreen({super.key});
@@ -38,7 +36,7 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
APIServices apiServices = APIServices();
final streamController = StreamController<GoogleMapController>.broadcast();
List<Device> devices = [];
Completer<GoogleMapController> _controller = Completer();
final Completer<GoogleMapController> _controller = Completer();
List<String> imageAssets = [
IconConstants.instance.getIcon("normal_icon"),
IconConstants.instance.getIcon("offline_icon"),
@@ -58,6 +56,7 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
Timer? checkThemeTimer;
Timer? getMarker;
String themeMode = '';
bool _isDisposed = false;
@override
void initState() {
super.initState();
@@ -73,19 +72,21 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
@override
void dispose() {
super.dispose();
_isDisposed = true;
checkThemeTimer?.cancel();
getMarker?.cancel();
_controller = Completer();
streamController.close();
super.dispose();
}
void onMapCreated(GoogleMapController controller) {
if (!_isDisposed && !streamController.isClosed) {
_controller.complete(controller);
streamController.add(controller);
clusterManager.setMapId(controller.mapId);
checkTheme();
}
}
@override
Widget build(BuildContext context) {
@@ -117,12 +118,15 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
clusterManager.updateMap();
},
polylines: {
if (polylinesSnapshot.data != null &&
polylinesSnapshot.data!.isNotEmpty) ...[
Polyline(
polylineId: const PolylineId('router'),
points: polylinesSnapshot.data ?? [],
points: polylinesSnapshot.data!,
color: Colors.deepPurpleAccent,
width: 8,
),
]
},
style: mapThemeSnapshot.data,
);
@@ -170,11 +174,11 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
return ClusterManager<Device>(
devices,
_updateMarkers,
markerBuilder: _getmarkerBuilder(),
markerBuilder: _getMarkerBuilder(),
);
}
Future<Marker> Function(Cluster<Device>) _getmarkerBuilder() =>
Future<Marker> Function(Cluster<Device>) _getMarkerBuilder() =>
(cluster) async {
return Marker(
markerId: MarkerId(
@@ -183,7 +187,8 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
position: cluster.location,
onTap: () async {
LocationPermission permission = await checkAndRequestPermission();
if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
if (permission == LocationPermission.whileInUse ||
permission == LocationPermission.always) {
Position position = await Geolocator.getCurrentPosition();
onTapMarker(
// ignore: use_build_context_synchronously
@@ -271,23 +276,12 @@ class _MapScreenState extends State<MapScreen> with WidgetsBindingObserver {
}
void getAllMarkers() async {
String response = await apiServices.getOwnerDevices();
if (response != "") {
final data = jsonDecode(response);
List<dynamic> result = data['items'];
if (result.isNotEmpty) {
await apiServices.execute(context, () async {
devices.clear();
final devicesList = Device.fromJsonDynamicList(result);
final devicesList = await apiServices.getOwnerDevices();
for (var device in devicesList) {
devices.add(device);
}
} else {}
});
}
}
// Future<bool> checkLocationPermission(context) async {
// bool check = await LocationPermissionRequest.instance
// .checkLocationPermission(context);
// return check;
// }
}

View File

@@ -3,6 +3,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'show_direction_widget.dart';
import 'show_nearest_place.dart';
import '../../../product/constant/icon/icon_constants.dart';
@@ -11,7 +12,6 @@ import '../../../bloc/map_bloc.dart';
import '../../../product/services/api_services.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../devices/device_model.dart';
onTapMarker(
@@ -72,14 +72,14 @@ onTapMarker(
double.parse(device.settings!.latitude!),
double.parse(device.settings!.longitude!),
);
mapBloc.findTheWay(
await mapBloc.findTheWay(
context,
controller,
myLocation,
destination,
);
String deviceLocations = await DeviceUtils.instance
.getFullDeviceLocation(context, device.areaPath!);
.getFullDeviceLocation(context, device.areaPath!,device.name);
String yourLocation =
appLocalization(context).map_your_location;
showDirections(

View File

@@ -1,12 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:maps_launcher/maps_launcher.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../bloc/map_bloc.dart';
showDirections(
@@ -16,16 +15,14 @@ showDirections(
MapBloc mapBloc,
String originalName,
String destinationLocation,
double devicelat,
double devicelng,
double deviceLat,
double deviceLng,
) {
TextEditingController originController =
TextEditingController(text: originalName);
TextEditingController destinationController =
TextEditingController(text: destinationLocation);
TextEditingController originController = TextEditingController(text: originalName);
TextEditingController destinationController = TextEditingController(text: destinationLocation);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.surface,
// dismissDirection: DismissDirection.none,
duration: const Duration(minutes: 5),
content: Column(
@@ -38,21 +35,20 @@ showDirections(
children: [
Text(
appLocalization(context).map_show_direction,
style: context.titleLargeTextStyle,
style: context.responsiveBodyLargeWithBold,
),
Container(
alignment: Alignment.centerRight,
child: IconButton.outlined(
onPressed: () async {
mapBloc.sinkPolylines.add([]);
markers.clear();
await mapBloc.updateCameraPosition(
controller,
devicelat,
devicelng,
deviceLat,
deviceLng,
13.0,
);
List<LatLng> polylineCoordinates = [];
mapBloc.sinkPolylines.add(polylineCoordinates);
markers.clear();
if (context.mounted) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
}
@@ -103,17 +99,12 @@ showDirections(
ScaffoldMessenger.of(context).hideCurrentSnackBar();
List<LatLng> polylineCoordinates = [];
mapBloc.sinkPolylines.add(polylineCoordinates);
MapsLauncher.launchCoordinates(devicelat, devicelng);
MapsLauncher.launchCoordinates(deviceLat, deviceLng);
},
icon: IconConstants.instance
.getMaterialIcon(Icons.near_me_rounded),
icon: IconConstants.instance.getMaterialIcon(Icons.near_me_rounded),
label: Text(
appLocalization(context).map_stream,
),
style: ButtonStyle(
backgroundColor:
WidgetStateProperty.all<Color>(Colors.blue[300]!),
),
),
],
),

View File

@@ -1,9 +1,9 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../bloc/map_bloc.dart';
import 'show_direction_widget.dart';
import '../../../product/constant/icon/icon_constants.dart';
@@ -52,7 +52,6 @@ showNearPlacesSideSheet(
padding: context.paddingLow,
width: screenWidth,
height: screenHeight / 3,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -62,10 +61,7 @@ showNearPlacesSideSheet(
Center(
child: Text(
'${appLocalization(modalBottomSheetContext).map_result}: ',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
style: context.h3
),
),
Container(
@@ -119,10 +115,7 @@ showNearPlacesSideSheet(
children: [
Text(
place.result!.name!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
style: context.responsiveBodyMediumWithBold,
),
SizedBox(height: listViewContext.lowValue),
Text(

View File

@@ -1,8 +1,8 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:convert';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import '../../../product/shared/shared_snack_bar.dart';
import '../../../bloc/device_notification_settings_bloc.dart';
import 'device_notification_settings_model.dart';
@@ -73,7 +73,7 @@ class _DeviceNotificationSettingsScreenState
hint: Text(
appLocalization(context)
.choose_device_dropdownButton,
style: const TextStyle(fontSize: 14),
style: context.responsiveBodySmall,
),
iconStyleData: const IconStyleData(
icon: Icon(
@@ -111,14 +111,12 @@ class _DeviceNotificationSettingsScreenState
}
void getNotificationSetting() async {
String? response = await apiServices.getAllSettingsNotificationOfDevices();
final data = jsonDecode(response);
final result = data['data'];
// log("Data ${DeviceNotificationSettings.mapFromJson(jsonDecode(data)).values.toList()}");
await apiServices.execute(context, () async {
deviceNotifications =
DeviceNotificationSettings.mapFromJson(result).values.toList();
await apiServices.getAllSettingsNotificationOfDevices();
deviceNotificationSettingsBloc.sinkListNotifications
.add(deviceNotifications);
});
}
Widget listNotificationSetting(
@@ -292,12 +290,14 @@ class _DeviceNotificationSettingsScreenState
void updateDeviceNotification(String thingID, Map<String, int> notifiSettings,
bool isDataChange) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.updateDeviceNotificationSettings(
thingID, notifiSettings);
if (statusCode == 200) {
showNoIconTopSnackBar(
context,
appLocalization(context).notification_update_device_settings_success,
appLocalization(context)
.notification_update_device_settings_success,
Colors.green,
Colors.white);
} else {
@@ -309,5 +309,6 @@ class _DeviceNotificationSettingsScreenState
}
isDataChange = false;
deviceNotificationSettingsBloc.sinkIsDataChange.add(isDataChange);
});
}
}

View File

@@ -1,6 +1,7 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import '../../../product/shared/shared_snack_bar.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/services/api_services.dart';
@@ -8,7 +9,6 @@ import '../../../bloc/settings_bloc.dart';
import '../../../product/shared/shared_input_decoration.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import 'profile_model.dart';
changeUserInfomation(
@@ -38,6 +38,7 @@ changeUserInfomation(
? IconButton(
onPressed: () async {
if (formKey.currentState!.validate()) {
await apiServices.execute(context,() async {
formKey.currentState!.save();
String latitude = user.latitude ?? "";
String longitude = user.longitude ?? "";
@@ -66,7 +67,44 @@ changeUserInfomation(
Colors.redAccent,
Colors.white);
}
settingsBloc.getUserProfile(context);
Navigator.pop(modalBottomSheetContext);
});
// try {
// formKey.currentState!.save();
// String latitude = user.latitude ?? "";
// String longitude = user.longitude ?? "";
// Map<String, dynamic> body = {
// "name": username,
// "email": email,
// "phone": tel,
// "address": address,
// "latitude": latitude,
// "longitude": longitude
// };
// int statusCode =
// await apiServices.updateUserProfile(body);
// if (statusCode == 200) {
// showNoIconTopSnackBar(
// modalBottomSheetContext,
// appLocalization(context)
// .notification_update_profile_success,
// Colors.green,
// Colors.white);
// } else {
// showNoIconTopSnackBar(
// modalBottomSheetContext,
// appLocalization(context)
// .notification_update_profile_failed,
// Colors.redAccent,
// Colors.white);
// }
// settingsBloc.getUserProfile(context);
// Navigator.pop(modalBottomSheetContext);
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(context, e.toString());
// }
}
},
icon:
@@ -205,6 +243,7 @@ changeUserInfomation(
child: TextButton(
onPressed: () async {
if (formKey.currentState!.validate()) {
await apiServices.execute(context,() async {
formKey.currentState!.save();
String latitude = user.latitude ?? "";
String longitude = user.longitude ?? "";
@@ -233,7 +272,9 @@ changeUserInfomation(
Colors.redAccent,
Colors.white);
}
settingsBloc.getUserProfile(context);
Navigator.pop(modalBottomSheetContext);
});
}
},
style: const ButtonStyle(
@@ -283,6 +324,7 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
? IconButton(
onPressed: () async {
if (formKey.currentState!.validate()) {
await apiServices.execute(context,() async {
formKey.currentState!.save();
Map<String, dynamic> body = {
"password_old": oldPass,
@@ -306,6 +348,7 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
Colors.white);
}
Navigator.pop(modalBottomSheetContext);
});
}
},
icon:
@@ -390,6 +433,7 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
? Center(
child: TextButton(
onPressed: () async {
await apiServices.execute(context,() async {
if (formKey.currentState!.validate()) {
formKey.currentState!.save();
Map<String, dynamic> body = {
@@ -415,6 +459,38 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
}
Navigator.pop(modalBottomSheetContext);
}
});
// try {
// if (formKey.currentState!.validate()) {
// formKey.currentState!.save();
// Map<String, dynamic> body = {
// "password_old": oldPass,
// "password_new": newPass,
// };
// int statusCode = await apiServices
// .updateUserPassword(body);
// if (statusCode == 200) {
// showNoIconTopSnackBar(
// modalBottomSheetContext,
// appLocalization(context)
// .notification_update_password_success,
// Colors.green,
// Colors.white);
// } else {
// showNoIconTopSnackBar(
// modalBottomSheetContext,
// appLocalization(context)
// .notification_update_password_failed,
// Colors.redAccent,
// Colors.white);
// }
// Navigator.pop(modalBottomSheetContext);
// }
// } catch (e) {
// if (!context.mounted) return;
// showErrorTopSnackBarCustom(
// context, e.toString());
// }
},
style: const ButtonStyle(
backgroundColor:

View File

@@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../product/constant/app/app_constants.dart';
import '../../product/shared/shared_loading_animation.dart';
import 'profile/profile_screen.dart';
import '../../product/constant/icon/icon_constants.dart';
import '../../product/extension/context_extension.dart';
@@ -27,7 +27,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
void initState() {
super.initState();
settingsBloc = BlocProvider.of(context);
getUserProfile();
}
@override
@@ -39,15 +38,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
body: StreamBuilder<User>(
stream: settingsBloc.streamUserProfile,
initialData: user,
builder: (context, userSnapshot) {
return userSnapshot.data?.id == "" || user.id == ""
? Center(
child: CircularProgressIndicator(
value: context.highValue,
),
)
: Column(
if (userSnapshot.data == null) {
settingsBloc.getUserProfile(context);
return const SharedLoadingAnimation();
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@@ -60,12 +56,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: CircleAvatar(
radius: 50,
child: Text(
getAvatarContent(
userSnapshot.data?.username ?? ""),
style: const TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
),
getAvatarContent(userSnapshot.data?.username ?? ""),
style: context.dynamicResponsiveSize(36),
),
),
),
@@ -76,8 +68,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [
Text(
userSnapshot.data?.name ?? "User Name",
style: const TextStyle(
fontWeight: FontWeight.w900, fontSize: 26),
style: context.h2,
)
],
),
@@ -89,29 +80,36 @@ class _SettingsScreenState extends State<SettingsScreen> {
cardContent(
Icons.account_circle_rounded,
appLocalization(context).profile_change_info,
),
userSnapshot.data ?? user),
SizedBox(height: context.lowValue),
cardContent(
Icons.lock_outline,
appLocalization(context).profile_change_pass,
),
userSnapshot.data ?? user),
SizedBox(height: context.lowValue),
cardContent(
Icons.settings_outlined,
appLocalization(context).profile_setting,
),
userSnapshot.data ?? user),
SizedBox(height: context.lowValue),
cardContent(
Icons.sim_card,
appLocalization(context).profile_sim_data,
userSnapshot.data ?? user),
SizedBox(height: context.lowValue),
cardContent(
Icons.logout_outlined,
appLocalization(context).log_out,
),
userSnapshot.data ?? user),
],
);
}),
}
},
),
);
}
cardContent(IconData icon, String content) {
cardContent(IconData icon, String content, User user) {
return GestureDetector(
onTap: () async {
if (icon == Icons.account_circle_rounded) {
@@ -120,7 +118,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
changeUserPassword(context, settingsBloc);
} else if (icon == Icons.settings_outlined) {
context.push(ApplicationConstants.DEVICE_NOTIFICATIONS_SETTINGS);
} else {
} else if(icon == Icons.sim_card){
context.push(ApplicationConstants.SIM_DATA_SETTINGS);
}
else {
await apiServices.logOut(context);
}
},
@@ -132,7 +133,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
leading: IconConstants.instance.getMaterialIcon(icon),
title: Text(
content,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
style: context.responsiveBodyMediumWithBold,
),
trailing: const Icon(
Icons.arrow_forward_ios_outlined,
@@ -142,12 +143,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
void getUserProfile() async {
String data = await apiServices.getUserDetail();
user = User.fromJson(jsonDecode(data));
settingsBloc.sinkUserProfile.add(user);
}
String getAvatarContent(String username) {
String name = "";
if (username.isNotEmpty) {

View File

@@ -0,0 +1,170 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../devices/device_model.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
class SharedSimComponent extends StatelessWidget {
const SharedSimComponent({super.key, required this.device});
final Device device;
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: screenWidth,
height: screenHeight / 5.5,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
gradient: getGradientColor(getMonthLeft())
),
child: Padding(
padding: context.paddingLow,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
device.name ?? "name",
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
_buildStatusChip(context,device.state ?? -1),
],
),
SizedBox(height: context.lowValue),
// Time period
Text(
"${appLocalization(context).time_title}: ${convertStartTime()} - ${convertExpireTime()}",
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
SizedBox(height: context.lowValue),
_buildDurationDisplay(context),
],
),
),
),
);
}
Widget _buildStatusChip(BuildContext context,int state) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(15),
color: Colors.white,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.circle,
color: DeviceUtils.instance.getTableRowColor(context, state),
size: 12,
),
const SizedBox(width: 5),
Text(
DeviceUtils.instance.checkStateDevice(context, state),
style: TextStyle(
color: DeviceUtils.instance.getTableRowColor(context, state),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Widget _buildDurationDisplay(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
"${getMonthLeft()}",
style: const TextStyle(
fontSize: 40,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
Text(
appLocalization(context).sim_data_month_left_message,
style: const TextStyle(
fontSize: 20,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
],
);
}
String convertStartTime(){
return DateFormat('dd/MM/yyyy').format(device.createdAt!);
}
String convertExpireTime() {
final expireDate = _calculateExpireDate();
return DateFormat('dd/MM/yyyy').format(expireDate);
}
int getMonthLeft() {
final expireDate = _calculateExpireDate();
final now = DateTime.now();
int totalMonths = (expireDate.year - now.year) * 12 + (expireDate.month - now.month);
if (expireDate.day < now.day) {
totalMonths -= 1;
}
return totalMonths;
}
DateTime _calculateExpireDate() {
return DateTime(
device.createdAt!.year + 3,
device.createdAt!.month,
device.createdAt!.day,
device.createdAt!.hour,
device.createdAt!.minute,
device.createdAt!.second,
device.createdAt!.millisecond,
device.createdAt!.microsecond,
);
}
LinearGradient getGradientColor(int monthLeft){
if(monthLeft <= 3){
return const LinearGradient(
colors: [Color(0xFFff4b1f), Color(0xFFff9068)],
);
}else{
return const LinearGradient(
colors: [Color(0xFF56ab2f), Color(0xFFa8e063)],
);
}
}
}

View File

@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'shared_sim_component.dart';
import '../../../product/services/language_services.dart';
import '../../../product/shared/shared_loading_animation.dart';
import '../../../bloc/sim_data_bloc.dart';
import '../../../product/base/bloc/base_bloc.dart';
class SimDataScreen extends StatefulWidget {
const SimDataScreen({super.key});
@override
State<SimDataScreen> createState() => _SimDataScreenState();
}
class _SimDataScreenState extends State<SimDataScreen> {
late SimDataBloc simDataBloc;
@override
void initState() {
super.initState();
simDataBloc = BlocProvider.of(context);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(appLocalization(context).profile_sim_data),
centerTitle: true,
),
body: StreamBuilder(
stream: simDataBloc.streamDevices,
builder: (context, devicesSnapshot) {
if (devicesSnapshot.data == null) {
simDataBloc.getOwnerDevices(context);
return const SharedLoadingAnimation();
} else if (devicesSnapshot.data!.isEmpty) {
return Center(
child: Text(appLocalization(context).main_no_data),
);
} else {
return SafeArea(
child: Column(
children: devicesSnapshot.data!.map(
(device) {
return SharedSimComponent(device: device,);
},
).toList(),
),
);
}
},
),
);
}
}

View File

@@ -1,12 +1,12 @@
import 'dart:developer';
import 'package:alarm/alarm.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:sfm_app/firebase_options.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'
show PersistentTabController;
import 'firebase_options.dart';
import 'product/lang/l10n/app_localizations.dart';
import 'product/services/api_services.dart';
import 'product/services/notification_services.dart';
import 'product/services/theme_services.dart';
@@ -15,90 +15,17 @@ import 'bloc/main_bloc.dart';
import 'product/base/bloc/base_bloc.dart';
import 'product/constant/navigation/navigation_router.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await setupFlutterNotifications();
showFlutterNotification(message);
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
print('Handling a background message ${message.messageId}');
}
/// Create a [AndroidNotificationChannel] for heads up notifications
late AndroidNotificationChannel channel;
bool isFlutterLocalNotificationsInitialized = false;
Future<void> setupFlutterNotifications() async {
if (isFlutterLocalNotificationsInitialized) {
return;
}
channel = const AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
description:
'This channel is used for important notifications.', // description
importance: Importance.high,
);
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Create an Android Notification Channel.
///
/// We use this channel in the `AndroidManifest.xml` file to override the
/// default FCM channel to enable heads up notifications.
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
/// Update the iOS foreground notification presentation options to allow
/// heads up notifications.
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
isFlutterLocalNotificationsInitialized = true;
}
void showFlutterNotification(RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null && !kIsWeb) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
// TODO add a proper drawable resource to android, for now using
// one that already exists in example app.
icon: 'launch_background',
),
),
);
}
}
/// Initialize the [FlutterLocalNotificationsPlugin] package.
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
PersistentTabController controller = PersistentTabController(initialIndex: 0);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Set the background messaging handler early on, as a named top-level function
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
if (!kIsWeb) {
await setupFlutterNotifications();
}
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
name: "sfm-notification");
FirebaseMessaging.onBackgroundMessage(
NotificationServices.firebaseMessagingBackgroundHandler);
await Alarm.init();
await Alarm.stopAll();
runApp(
BlocProvider(
child: const MyApp(),
@@ -107,7 +34,6 @@ void main() async {
);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@@ -130,7 +56,7 @@ class _MyAppState extends State<MyApp> {
late MainBloc mainBloc;
LanguageServices languageServices = LanguageServices();
ThemeServices themeServices = ThemeServices();
// final NotificationServices notificationServices = NotificationServices();
final NotificationServices notificationServices = NotificationServices();
APIServices apiServices = APIServices();
setLocale(Locale locale) {
_locale = locale;
@@ -146,20 +72,11 @@ class _MyAppState extends State<MyApp> {
void initState() {
super.initState();
mainBloc = BlocProvider.of(context);
// // notificationServices.initLocalNotifications();
// // notificationServices.firebaseInit(context);
// // notificationServices.setupInteractMessage();
// notificationServices.getDeviceToken().then((token){
// print("Firebase Token: $token");
// sendNotificationToken(token);
// });
notificationServices.initialize();
notificationServices.firebaseInit(context);
notificationServices.setupInteractMessage(controller);
}
// void sendNotificationToken (String token) async {
// int statusCode = await apiServices.sendNotificationToken(token);
// log("Notification Send StatusCode : $statusCode");
// }
@override
void didChangeDependencies() {
languageServices.getLocale().then((locale) => {setLocale(locale)});
@@ -186,8 +103,7 @@ class _MyAppState extends State<MyApp> {
supportedLocales: AppLocalizations.supportedLocales,
locale: languageSnapshot.data,
);
}
);
});
},
);
}

View File

@@ -30,8 +30,7 @@ class RequestPermissionDialog {
child: Text(
"Alow app to use $content permission",
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
style: context.responsiveBodyLargeWithBold,
),
),
Divider(height: dialogContext.lowValue),

View File

@@ -1,4 +1,4 @@
// ignore_for_file: constant_identifier_names
// ignore_for_file: constant_identifier_names, non_constant_identifier_names
class ApplicationConstants {
static const APP_NAME = "Smatec SFM";
@@ -21,8 +21,10 @@ class ApplicationConstants {
static const DEVICE_LOGS_PATH = "/device-logs";
static const GROUP_PATH = "/groups";
static const DEVICE_NOTIFICATIONS_SETTINGS = "/device-notifications-settings";
static const SIM_DATA_SETTINGS = "/sim-data-settings";
static const OWNER_GROUP = "owner";
static const PARTICIPANT_GROUP = "participant";
static const NO_DATA = "no_data";
static const LOADING = "loading";
static int CALL_API_TIMEOUT = 30;
}

View File

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

View File

@@ -1,4 +1,7 @@
import 'package:go_router/go_router.dart';
import '../../../bloc/sim_data_bloc.dart';
import '../../../feature/settings/sim_data/sim_data_screen.dart';
import '../../../bloc/device_detail_bloc.dart';
import '../../../feature/devices/device_detail/device_detail_screen.dart';
import '../../../bloc/device_notification_settings_bloc.dart';
@@ -151,6 +154,16 @@ GoRouter goRouter() {
),
transitionsBuilder: transitionsRightToLeft),
),
GoRoute(
path: ApplicationConstants.SIM_DATA_SETTINGS,
name: AppRoutes.SIM_DATA_SETTING.name,
pageBuilder: (context, state) => CustomTransitionPage(
child: BlocProvider(
child: const SimDataScreen(),
blocBuilder: () => SimDataBloc(),
),
transitionsBuilder: transitionsRightToLeft),
),
],
);
}

View File

@@ -1,7 +1,8 @@
import 'dart:math';
import 'package:flutter/material.dart';
import '../utils/app_logger_utils.dart';
import '../utils/responsive_text_utils.dart';
import '../theme/app_theme_light.dart';
// MEDIA
@@ -9,33 +10,77 @@ extension ContextExtension on BuildContext {
MediaQueryData get mediaQuery => MediaQuery.of(this);
}
extension ResponsiveTextStyle on BuildContext {
TextStyle get h1 => TextStyle(
fontSize: ResponsiveText.getSize(this, 32), fontWeight: FontWeight.bold);
TextStyle get h2 => TextStyle(
fontSize: ResponsiveText.getSize(this, 24), fontWeight: FontWeight.bold);
TextStyle get h3 => TextStyle(
fontSize: ResponsiveText.getSize(this, 20), fontWeight: FontWeight.bold);
TextStyle get responsiveBodyLarge =>
TextStyle(fontSize: ResponsiveText.getSize(this, 18));
TextStyle get responsiveBodyLargeWithBold => TextStyle(
fontSize: ResponsiveText.getSize(this, 18), fontWeight: FontWeight.bold);
TextStyle get responsiveBodyMedium =>
TextStyle(fontSize: ResponsiveText.getSize(this, 16));
TextStyle get responsiveBodyMediumWithBold => TextStyle(
fontSize: ResponsiveText.getSize(this, 16), fontWeight: FontWeight.bold);
TextStyle get responsiveBodySmall =>
TextStyle(fontSize: ResponsiveText.getSize(this, 14));
TextStyle get responsiveBodySmallWithBold => TextStyle(
fontSize: ResponsiveText.getSize(this, 14), fontWeight: FontWeight.bold);
TextStyle dynamicResponsiveSize(double val) =>
TextStyle(fontSize: ResponsiveText.getSize(this, val));
TextStyle dynamicResponsiveSizeWithBold(double val) => TextStyle(
fontSize: ResponsiveText.getSize(this, val), fontWeight: FontWeight.bold);
}
// 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;
}
@@ -44,20 +89,26 @@ extension PaddingExtensionAll on BuildContext {
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);
}
@@ -70,34 +121,63 @@ extension PageExtension on BuildContext {
// DURATION
extension DurationExtension on BuildContext {
Duration get lowDuration => const Duration(milliseconds: 150);
Duration get normalDuration => const Duration(milliseconds: 500);
Duration dynamicMilliSecondDuration(int milliseconds) =>
Duration(milliseconds: milliseconds);
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);
Radius dynamicRadius(double radius) => Radius.circular(radius);
}
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!;
}
extension FutureExtension<T> on Future<T> {
Future<T> handleApiError() async {
try {
return await this;
} catch (e) {
AppLoggerUtils.error(e.toString());
return Future.error(e);
}
}
}

View File

@@ -22,6 +22,8 @@
"let_PCCC_handle_message": "Let the Fire Prevention and Fighting Team handle it!",
"overview_message": "Overview",
"total_nof_devices_message": "Total number of devices",
"over_view_owner_devices":"Owner Devices",
"over_view_joined_devices":"Joined Devices",
"active_devices_message": "Active",
"inactive_devices_message": "Inactive",
"warning_devices_message": "Warning",
@@ -109,7 +111,10 @@
"profile_page_title": "Settings Page",
"profile_change_info": "Change information",
"profile_change_pass": "Change password",
"profile_sim_data": "Device SIM information",
"profile_setting": "Notification Setting",
"sim_data_month_left_message": "months left",
"time_title": "Time",
"change_profile_title": "Personal information",
"change_profile_username": "Username: ",
"change_profile_username_hint": "Enter username ",

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -21,6 +21,8 @@
"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",
"over_view_owner_devices":"Thiết bị sở hữu",
"over_view_joined_devices":"Thiết bị tham gia",
"total_nof_devices_message": "Tổng số",
"active_devices_message": "Bình thường",
"inactive_devices_message": "Đang tắt",
@@ -110,6 +112,9 @@
"profile_change_info": "Đổi thông tin cá nhân",
"profile_change_pass": "Đổi mật khẩu",
"profile_setting": "Cài đặt thông báo",
"profile_sim_data": "Thông tin sim thiết bị",
"sim_data_month_left_message": "tháng còn lại",
"time_title": "Thời gian",
"change_profile_title": "Thông tin người dùng",
"change_profile_username": "Tên người dùng: ",
"change_profile_username_hint": "Nhập tên ",

View File

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

View File

@@ -1,6 +1,9 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import '../utils/app_logger_utils.dart';
import '../constant/status_code/status_code_constants.dart';
import '../cache/local_manager.dart';
@@ -13,6 +16,7 @@ class NetworkManager {
static NetworkManager? _instance;
static NetworkManager? get instance => _instance ??= NetworkManager._init();
Future<Map<String, String>> getHeaders() async {
String? token =
LocaleManager.instance.getStringValue(PreferencesKeys.TOKEN);
@@ -35,15 +39,26 @@ class NetworkManager {
/// [String] if the request is successful (status code 200), or an empty
/// string if the request fails
Future<String> getDataFromServer(String path) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET url: $url");
AppLoggerUtils.info("GET url: $url");
// log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET url: $url");
final headers = await getHeaders();
final response = await http.get(url, headers: headers);
final response = await http.get(url, headers: headers).timeout(
Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT),
onTimeout: () =>
throw TimeoutException('Yêu cầu GET hết thời gian'),
);
if (response.statusCode == StatusCodeConstants.OK ||
response.statusCode == StatusCodeConstants.CREATED) {
return response.body;
} else {
return "";
throw Exception('Lỗi server: ${response.statusCode}');
}
} catch (e) {
// AppLoggerUtils.error(message)
// log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace');
throw Exception('Lỗi khi lấy dữ liệu: $e');
}
}
@@ -60,16 +75,36 @@ class NetworkManager {
/// Returns a [Future<String>] containing the server response body.
Future<String> getDataFromServerWithParams(
String path, Map<String, dynamic> params) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path, params);
log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET Params url: $url");
AppLoggerUtils.info("GET Params url: $url");
// log("[${DateTime.now().toLocal().toString().split(' ')[1]}] 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) {
final response = await http.get(url, headers: headers).timeout(
Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT),
onTimeout: () =>
throw TimeoutException('Yêu cầu GET+PARAM hết thời gian'),
);
if (response.statusCode == StatusCodeConstants.OK ||
response.statusCode == StatusCodeConstants.CREATED) {
return response.body;
} else {
return "";
throw Exception('Lỗi server: ${response.statusCode}');
}
} catch (e, stackTrace) {
log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace');
throw Exception('Lỗi khi lấy dữ liệu: $e');
}
// final url = Uri.https(ApplicationConstants.DOMAIN, path, params);
// log("[${DateTime.now().toLocal().toString().split(' ')[1]}] 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.
@@ -77,12 +112,28 @@ class NetworkManager {
/// [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 {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("[${DateTime.now().toLocal().toString().split(' ')[1]}] POST url: $url");
AppLoggerUtils.info("POST url: $url");
// log("[${DateTime.now().toLocal().toString().split(' ')[1]}] POST url: $url");
final headers = await getHeaders();
final response =
await http.post(url, headers: headers, body: jsonEncode(body));
final response = await http
.post(url, headers: headers, body: jsonEncode(body))
.timeout(
Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT),
onTimeout: () =>
throw TimeoutException('Yêu cầu POST hết thời gian'),
);
if (response.statusCode == StatusCodeConstants.OK ||
response.statusCode == StatusCodeConstants.CREATED) {
return response.statusCode;
} else {
throw Exception('Lỗi server: ${response.statusCode}');
}
} catch (e, stackTrace) {
log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace');
throw Exception('Lỗi khi lấy dữ liệu: $e');
}
}
/// Updates existing data on the server using a PUT request.
@@ -90,12 +141,27 @@ class NetworkManager {
/// [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 {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("[${DateTime.now().toLocal().toString().split(' ')[1]}] PUT url: $url");
AppLoggerUtils.info("PUT url: $url");
// log("[${DateTime.now().toLocal().toString().split(' ')[1]}] PUT url: $url");
final headers = await getHeaders();
final response =
await http.put(url, headers: headers, body: jsonEncode(body));
await http.put(url, headers: headers, body: jsonEncode(body)).timeout(
Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT),
onTimeout: () =>
throw TimeoutException('Yêu cầu PUT hết thời gian'),
);
if (response.statusCode == StatusCodeConstants.OK ||
response.statusCode == StatusCodeConstants.CREATED) {
return response.statusCode;
} else {
throw Exception('Lỗi server: ${response.statusCode}');
}
} catch (e, stackTrace) {
log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace');
throw Exception('Lỗi khi lấy dữ liệu: $e');
}
}
/// Deletes data from the server using a DELETE request.
@@ -105,10 +171,25 @@ class NetworkManager {
/// A status code of 200 indicates success, while other codes indicate
/// failure or an error.
Future<int> deleteDataInServer(String path) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
log("[${DateTime.now().toLocal().toString().split(' ')[1]}] DELETE url: $url");
// log("[${DateTime.now().toLocal().toString().split(' ')[1]}] DELETE url: $url");
AppLoggerUtils.info("DELETE url: $url");
final headers = await getHeaders();
final response = await http.delete(url, headers: headers);
final response = await http.delete(url, headers: headers).timeout(
Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT),
onTimeout: () =>
throw TimeoutException('Yêu cầu DELETE hết thời gian'),
);
if (response.statusCode == StatusCodeConstants.OK ||
response.statusCode == StatusCodeConstants.CREATED) {
return response.statusCode;
} else {
throw Exception('Lỗi server: ${response.statusCode}');
}
} catch (e, stackTrace) {
log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace');
throw Exception('Lỗi khi lấy dữ liệu: $e');
}
}
}

View File

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

View File

@@ -1,8 +1,10 @@
import 'dart:developer';
import 'dart:io';
import 'package:app_settings/app_settings.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';
import '../base/widget/dialog/request_permission_dialog.dart';
@@ -11,7 +13,7 @@ class NotificationPermission {
static NotificationPermission? _instance;
static NotificationPermission get instance =>
_instance ??= NotificationPermission._init();
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
Future<bool> checkNotificationPermission(context) async {
var status = await Permission.notification.status;
log("Status: $status");
@@ -44,4 +46,21 @@ class NotificationPermission {
Icons.location_on_outlined, "ABCDE", AppSettingsType.notification);
}
}
Future<bool?> requestNotificationPermission() async {
try {
if (Platform.isAndroid) {
return await _notificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission();
} else if (Platform.isIOS) {
return await _notificationsPlugin
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(alert: true, sound: true, badge: true);
}
return null;
} catch (e) {
log("Error requesting notification permission: $e");
return null;
}
}
}

View File

@@ -0,0 +1,35 @@
import 'dart:io';
import 'package:alarm/alarm.dart';
@pragma('vm:entry-point')
class AlarmServices {
Future<void> showAlarm(String title, String body) async {
final DateTime now = DateTime.now();
final AlarmSettings alarmSettings = AlarmSettings(
id: 42,
dateTime: now,
assetAudioPath: 'assets/sounds/warning_alarm.mp3',
loopAudio: true,
vibrate: true,
warningNotificationOnKill: Platform.isIOS,
androidFullScreenIntent: true,
allowAlarmOverlap: true,
volumeSettings: VolumeSettings.fade(
volume: 1.0,
fadeDuration: const Duration(seconds: 3),
volumeEnforced: true,
),
notificationSettings: NotificationSettings(
title: title,
body: body,
stopButton: 'Dừng cảnh báo',
icon: 'ic_launcher',
),
);
await Alarm.set(alarmSettings: alarmSettings);
}
void cancelAlarm({int id = 42}) async {
await Alarm.stop(id);
}
}

View File

@@ -1,10 +1,24 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import '../shared/model/province_model.dart';
import '../utils/app_logger_utils.dart';
import '../../feature/device_log/device_logs_model.dart';
import '../../feature/devices/device_model.dart';
import '../../feature/home/device_alias_model.dart';
import '../../feature/inter_family/group_detail/group_detail_model.dart';
import '../../feature/inter_family/groups/groups_model.dart';
import '../../feature/settings/device_notification_settings/device_notification_settings_model.dart';
import '../../feature/settings/profile/profile_model.dart';
import '../constant/app/api_path_constant.dart';
import '../shared/model/district_model.dart';
import '../shared/model/ward_model.dart';
import '../shared/shared_snack_bar.dart';
import '../constant/enums/app_route_enums.dart';
import 'language_services.dart';
@@ -41,6 +55,86 @@ class APIServices {
return headers;
}
Future<T> executeApiCall<T>(Future<dynamic> Function() apiCall,
{T Function(dynamic)? parser,
String errorMessage = 'Lỗi khi gọi API',
T Function(int)? statusCodeHandler}) async {
try {
final response = await apiCall().timeout(
Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT),
onTimeout: () => throw TimeoutException('Yêu cầu hết thời gian'),
);
if (statusCodeHandler != null && response is int) {
return statusCodeHandler(response);
}
if (response is String && response != "") {
if (parser != null) {
try {
return parser(jsonDecode(response));
} catch (e) {
throw Exception('Lỗi parsing dữ liệu: $e');
}
}
return response as T;
} else {
throw Exception('Dữ liệu trả về rỗng');
}
} catch (e, stackTrace) {
AppLoggerUtils.error("Lỗi gọi API", e, stackTrace);
throw Exception('$errorMessage: $e');
}
}
/// Most Used Function
// Future<T> execute<T>(Future<T> Function() apiCall) async {
// try {
// return await apiCall();
// } catch (e) {
// AppLoggerUtils.error(e.toString());
// return Future.error(e);
// }
// }
Future<T> execute<T>(
BuildContext context,
Future<T> Function() apiCall, {
bool checkMounted = true,
}) async {
try {
// Quick connectivity check before attempting network calls.
final conn = await Connectivity().checkConnectivity();
if (conn == ConnectivityResult.none) {
AppLoggerUtils.warning('No network connectivity');
if (checkMounted && context.mounted) {
showErrorTopSnackBarCustom(context, "Không có kết nối mạng");
}
return Future.error(const SocketException('No network connectivity'));
}
return await apiCall();
} on SocketException catch (e, stackTrace) {
// Network-related errors (DNS, timeout, host lookup...)
AppLoggerUtils.warning('Network error when calling API: $e');
if (checkMounted && context.mounted) {
AppLoggerUtils.error("Không có kết nối mạng");
}
return Future.error(e);
} catch (e, stackTrace) {
// If widget was unmounted (e.g. background isolate), preserve previous behavior
if (checkMounted && !context.mounted) {
return Future.error('Widget not mounted');
}
AppLoggerUtils.error("Lỗi hệ thống khi gọi API", e, stackTrace);
if (checkMounted && context.mounted) {
showErrorTopSnackBarCustom(context, "Lỗi hệ thống");
}
return Future.error(e);
}
}
Future<String> login(String path, Map<String, dynamic> loginRequest) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path);
final headers = await getHeaders();
@@ -51,12 +145,9 @@ class APIServices {
Future<int> sendNotificationToken(String token) async {
String uid = await getUID();
Map<String,dynamic> body = {
"user_id": uid,
"app_token": token
};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.NOTIFICATION_TOKEN_PATH, body);
Map<String, dynamic> body = {"user_id": uid, "app_token": token};
int statusCode = await NetworkManager.instance!
.updateDataInServer(APIPathConstants.NOTIFICATION_TOKEN_PATH, body);
return statusCode;
}
@@ -69,7 +160,7 @@ class APIServices {
actions: [
TextButton(
onPressed: () async {
var url = Uri.http(ApplicationConstants.DOMAIN,
var url = Uri.https(ApplicationConstants.DOMAIN,
APIPathConstants.LOGOUT_PATH);
final headers = await NetworkManager.instance!.getHeaders();
final response = await http.post(url, headers: headers);
@@ -122,67 +213,89 @@ class APIServices {
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<Bell> getBellNotifications(String offset, String pageSize) async {
final params = {"offset": offset, "page_size": pageSize};
return executeApiCall(
() => NetworkManager.instance!.getDataFromServerWithParams(
APIPathConstants.BELL_NOTIFICATIONS_PATH, params),
parser: (json) => Bell.fromJson(json),
errorMessage: 'Lỗi khi GET /${APIPathConstants.BELL_NOTIFICATIONS_PATH}',
);
}
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;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
APIPathConstants.BELL_UPDATE_READ_NOTIFICATIONS_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi PUT /${APIPathConstants.BELL_NOTIFICATIONS_PATH}',
);
}
Future<String> getUserDetail() async {
Future<User> getUserDetail() async {
String uid = await getUID();
String? response = await NetworkManager.instance!
.getDataFromServer('${APIPathConstants.USER_PATH}/$uid');
return response;
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer('${APIPathConstants.USER_PATH}/$uid'),
parser: (json) => User.fromJson(json),
errorMessage: 'Lỗi khi GET /${APIPathConstants.USER_PATH}',
);
}
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;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
"${APIPathConstants.USER_PROFILE_PATH}/$uid", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi PUT /${APIPathConstants.USER_PROFILE_PATH}',
);
}
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;
// int statusCode = await NetworkManager.instance!.updateDataInServer(
// "${APIPathConstants.USER_PATH}/$uid/password", body);
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
"${APIPathConstants.USER_PATH}/$uid/password", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi PUT /${APIPathConstants.USER_PATH}');
}
Future<String> getAllSettingsNotificationOfDevices() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.DEVICE_NOTIFICATION_SETTINGS);
return data;
Future<List<DeviceNotificationSettings>>
getAllSettingsNotificationOfDevices() async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer(APIPathConstants.DEVICE_NOTIFICATION_SETTINGS),
parser: (json) =>
DeviceNotificationSettings.mapFromJson(json['data']).values.toList(),
errorMessage:
'Lỗi khi GET /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}',
);
}
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;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}');
}
Future<String> getDashBoardDevices() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.DASHBOARD_DEVICES);
return data;
Future<List<DeviceWithAlias>> getDashBoardDevices() async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer(APIPathConstants.DASHBOARD_DEVICES),
parser: (json) => DeviceWithAlias.fromJsonDynamicList(json['items']),
errorMessage: 'Lỗi khi GET /${APIPathConstants.DASHBOARD_DEVICES}',
);
}
Future<int> setupDeviceNotification(String thingID, String deviceName) async {
@@ -202,68 +315,103 @@ class APIServices {
"104": 1,
}
};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}');
}
Future<String> getAllProvinces() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.PROVINCES_PATH);
return data;
Future<List<Province>> getAllProvinces() async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer(APIPathConstants.PROVINCES_PATH),
parser: (json) => Province.fromJsonDynamicList(json['items']),
errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}');
}
Future<String> getProvincesByName(String name) async {
Future<List<Province>> getProvincesByName(String name) async {
final params = {'name': name};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.PROVINCES_PATH, params);
return data;
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.PROVINCES_PATH, params),
parser: (json) => Province.fromJsonDynamicList(json['items']),
errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$name',
);
}
Future<String> getProvinceByID(String provinceID) async {
String data = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.PROVINCES_PATH}/$provinceID");
return data;
Future<Province> getProvinceByID(String provinceID) async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer("${APIPathConstants.PROVINCES_PATH}/$provinceID"),
parser: (json) => Province.fromJson(json['data']),
errorMessage:
'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$provinceID}',
);
}
Future<String> getAllDistricts(String provinceID) async {
Future<List<District>> getAllDistricts(String provinceID) async {
final params = {"parent": provinceID};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params);
return data;
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params),
parser: (json) => District.fromJsonDynamicList(json['items']),
errorMessage:
'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH} by parentCode $provinceID',
);
}
Future<String> getDistrictsByName(String districtName) async {
Future<List<District>> getDistrictsByName(String districtName) async {
final params = {"name": districtName};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params);
return data;
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params),
parser: (json) => District.fromJsonDynamicList(json['items']),
errorMessage:
'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH} by name $districtName',
);
}
Future<String> getDistrictByID(String districtID) async {
String? data = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.DISTRICTS_PATH}/$districtID");
return data;
Future<District> getDistrictByID(String districtID) async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer("${APIPathConstants.DISTRICTS_PATH}/$districtID"),
parser: (json) => District.fromJson(json['data']),
errorMessage:
'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH}/$districtID',
);
}
Future<String> getAllWards(String districtID) async {
Future<List<Ward>> getAllWards(String districtID) async {
final params = {'parent': districtID};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params);
return data;
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params),
parser: (json) => Ward.fromJsonDynamicList(json['items']),
errorMessage:
'Lỗi khi GET /${APIPathConstants.WARDS_PATH} by parent $districtID',
);
}
Future<String> getWarsdByName(String wardName) async {
Future<List<Ward>> getWardsByName(String wardName) async {
final params = {"name": wardName};
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params);
return data;
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params),
parser: (json) => Ward.fromJsonDynamicList(json['items']),
errorMessage:
'Lỗi khi GET /${APIPathConstants.WARDS_PATH} by name $wardName',
);
}
Future<String> getWardByID(String wardID) async {
String? data = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.WARDS_PATH}/$wardID");
return data;
Future<Ward> getWardByID(String wardID) async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer("${APIPathConstants.WARDS_PATH}/$wardID"),
parser: (json) => Ward.fromJson(json['data']),
errorMessage: 'Lỗi khi GET /${APIPathConstants.WARDS_PATH}/$wardID',
);
}
Future<int> confirmFakeFireByUser(String thingID) async {
@@ -271,131 +419,187 @@ class APIServices {
"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;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
"${APIPathConstants.DEVICE_PATH}/$thingID", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_PATH}/$thingID');
}
Future<String> getOwnerDevices() async {
String? data = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.DEVICE_PATH);
return data;
Future<List<Device>> getOwnerDevices() async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer(APIPathConstants.DEVICE_PATH),
parser: (json) => Device.fromJsonDynamicList(json['items']),
errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}',
);
}
Future<String> getOwnerDeviceByState(Map<String, dynamic> params) async {
String? data = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DEVICE_PATH, params);
return data;
Future<List<Device>> getOwnerDeviceByState(
Map<String, dynamic> params) async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DEVICE_PATH, params),
parser: (json) => Device.fromJsonDynamicList(json['items']),
errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}',
);
}
Future<int> createDeviceByAdmin(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_PATH, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_PATH}');
}
Future<int> registerDevice(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_REGISTER_PATH}');
}
Future<int> deleteDeviceByAdmin(String thingID) async {
int statusCode = await NetworkManager.instance!
.deleteDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID");
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.deleteDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID"),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi DELETE /${APIPathConstants.DEVICE_PATH}/$thingID');
}
Future<int> unregisterDevice(Map<String, dynamic> body) async {
int statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_UNREGISTER_PATH, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.createDataInServer(APIPathConstants.DEVICE_UNREGISTER_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi DELETE /${APIPathConstants.DEVICE_UNREGISTER_PATH} by USER');
}
Future<String> getDeviceInfomation(String thingID) async {
String? response = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID");
return response;
Future<Device> getDeviceInformation(String thingID) async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID"),
parser: (json) => Device.fromJson(json),
errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}/$thingID',
);
}
Future<String> getAllGroups() async {
String? body = await NetworkManager.instance!
.getDataFromServer(APIPathConstants.ALL_GROUPS_PATH);
return body;
Future<List<Group>> getAllGroups() async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer(APIPathConstants.ALL_GROUPS_PATH),
parser: (json) => Group.fromJsonDynamicList(json['items']),
errorMessage: 'Lỗi khi GET /${APIPathConstants.USER_PATH}',
);
}
Future<int> createGroup(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.GROUPS_PATH, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.createDataInServer(APIPathConstants.GROUPS_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.GROUPS_PATH}');
}
Future<int> updateGroup(Map<String, dynamic> body, String groupID) async {
int? statusCode = await NetworkManager.instance!
.updateDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID", body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi PUT /${APIPathConstants.GROUPS_PATH}/$groupID');
}
Future<int> joinGroup(String groupID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.JOIN_GROUP_PATH}');
}
Future<int> deleteGroup(String groupID) async {
int? statusCode = await NetworkManager.instance!
.deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID");
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID"),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID');
}
Future<String> getGroupDetail(String groupID) async {
String? body = await NetworkManager.instance!
.getDataFromServer("${APIPathConstants.GROUPS_PATH}/$groupID");
return body;
Future<GroupDetail> getGroupDetail(String groupID) async {
return executeApiCall(
() => NetworkManager.instance!
.getDataFromServer("${APIPathConstants.GROUPS_PATH}/$groupID"),
parser: (json) => GroupDetail.fromJson(json),
errorMessage: 'Lỗi khi GET /${APIPathConstants.GROUPS_PATH}/$groupID',
);
}
Future<int> approveGroup(Map<String, dynamic> body) async {
int statusCode = await NetworkManager.instance!
.createDataInServer(APIPathConstants.APPROVE_GROUP_PATH, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!
.createDataInServer(APIPathConstants.APPROVE_GROUP_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.APPROVE_GROUP_PATH}');
}
Future<int> deleteUserInGroup(String groupID, String userID) async {
int? statusCode = await NetworkManager.instance!.deleteDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID");
return statusCode;
return executeApiCall(
() => NetworkManager.instance!.deleteDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID"),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID');
}
Future<int> deleteDeviceInGroup(String groupID, String thingID) async {
int? statusCode = await NetworkManager.instance!.deleteDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID");
return statusCode;
return executeApiCall(
() => NetworkManager.instance!.deleteDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID"),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID');
}
Future<int> updateDeviceAlias(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}');
}
Future<int> addDeviceToGroup(
String groupID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!.createDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/things", body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!.createDataInServer(
"${APIPathConstants.GROUPS_PATH}/$groupID/things", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.GROUPS_PATH}/$groupID/things');
}
Future<int> updateOwnerDevice(
String thingID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!
.updateDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID", body);
return statusCode;
return executeApiCall(
() => NetworkManager.instance!.updateDataInServer(
"${APIPathConstants.DEVICE_PATH}/$thingID", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_PATH}/$thingID');
}
Future<String> getLogsOfDevice(
Future<DeviceLog> getLogsOfDevice(
String thingID, Map<String, dynamic> params) async {
String? body = await NetworkManager.instance!
.getDataFromServerWithParams(APIPathConstants.DEVICE_LOGS_PATH, params);
return body;
return executeApiCall(
() => NetworkManager.instance!.getDataFromServerWithParams(
APIPathConstants.DEVICE_LOGS_PATH, params),
parser: (json) => DeviceLog.fromJson(json),
errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_LOGS_PATH}',
);
}
}

View File

@@ -2,7 +2,7 @@ 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';
import '../lang/l10n/app_localizations.dart';
class LanguageServices {
Future<Locale> setLocale(String languageCode) async {

View File

@@ -10,9 +10,8 @@ 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 {
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}');
@@ -43,22 +42,22 @@ class MapServices {
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 [];
// }
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
googleApiKey: ApplicationConstants.MAP_KEY,
request: PolylineRequest(
origin: PointLatLng(origin.latitude, origin.longitude),
destination: PointLatLng(destination.latitude, destination.longitude),
mode: TravelMode.driving,
optimizeWaypoints: true));
if (result.points.isNotEmpty) {
for (var point in result.points) {
polylineCoordinates.add(LatLng(point.latitude, point.longitude));
}
return polylineCoordinates;
} else {
log("Lỗi khi tìm đường");
return [];
}
}
}

View File

@@ -1,121 +1,262 @@
import 'dart:developer' as dev;
import 'dart:io';
import 'dart:math' as math;
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:alarm/alarm.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'
as firebase_messaging;
import 'package:flutter/material.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:persistent_bottom_nav_bar/persistent_bottom_nav_bar.dart';
import '../utils/app_logger_utils.dart';
import '../../firebase_options.dart';
import 'alarm_services.dart';
@pragma('vm:entry-point')
class NotificationServices {
FirebaseMessaging messaging = FirebaseMessaging.instance;
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
final firebase_messaging.FirebaseMessaging _messaging =
firebase_messaging.FirebaseMessaging.instance;
AlarmServices alarmServices = AlarmServices();
Future<void> initLocalNotifications(PersistentTabController controller) async {
const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings();
const InitializationSettings initializationSettings = InitializationSettings(
android: androidInitializationSettings,
iOS: iosInitializationSettings,
Future<void> initialize() async {
await initializeLocalNotifications();
dev.log("NotificationService initialized");
}
@pragma('vm:entry-point')
Future<void> initializeLocalNotifications() async {
try {
const androidInitSettings =
AndroidInitializationSettings('@mipmap/ic_launcher');
final darwinInitSettings = DarwinInitializationSettings(
onDidReceiveLocalNotification: _onDidReceiveLocalNotification,
);
final initSettings = InitializationSettings(
android: androidInitSettings,
iOS: darwinInitSettings,
);
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
dev.log("Người dùng click thông báo ở foreground với payload: ${response.payload}");
handleMessage(response.payload,controller);
},
await _notificationsPlugin.initialize(
initSettings,
onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
);
dev.log("Local notifications initialized");
} catch (e) {
AppLoggerUtils.error("Failed to initialize local notifications", e);
}
}
Future<void> _onDidReceiveLocalNotification(
int id, String? title, String? body, String? payload) async {
// Handle local notification tap when app is foreground (iOS)
dev.log("Local notification tapped (foreground): payload=$payload");
}
Future<void> _onDidReceiveNotificationResponse(
NotificationResponse response) async {
// Handle local notification tap (both foreground & background)
final payload = response.payload;
dev.log("Notification tapped: payload=$payload");
if (payload != null && payload.isNotEmpty) {
final controller = PersistentTabController(initialIndex: 0);
handleMessage(payload, controller);
}
}
void firebaseInit(BuildContext context) {
FirebaseMessaging.onMessage.listen((message) {
dev.log("Foreground message payload: ${message.toMap()}");
if (WidgetsBinding.instance != null) {
showNotification(message);
} else {
dev.log("App is in background, skipping foreground notification");
}
firebase_messaging.FirebaseMessaging.onMessage.listen((message) {
_handleForegroundMessage(message);
});
}
Future<String> getDeviceToken() async {
print("GET FB TOKEN");
String? token = await messaging.getAPNSToken();
print("GET FB: ${token}");
return token!;
}
void isTokenRefresh() {
messaging.onTokenRefresh.listen((newToken) {
_messaging.onTokenRefresh.listen((newToken) {
dev.log("Refresh Firebase Messaging Token: $newToken");
});
}
Future<void> showNotification(RemoteMessage message) async {
dev.log(message.toString());
dev.log(message.data.toString());
dev.log(message.data["notification"].toString());
String? title = message.data['title'];
String? body = message.data['body'];
String type = message.data['type'] ?? "normal";
if (title == null || body == null) {
dev.log("Skipping notification due to missing title or body");
return;
static Future<bool?> requestNotificationPermission() async {
try {
final messaging = firebase_messaging.FirebaseMessaging.instance;
final settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
provisional: false,
sound: true,
);
return settings.authorizationStatus ==
firebase_messaging.AuthorizationStatus.authorized;
} catch (e) {
AppLoggerUtils.error("Failed to request notification permission", e);
return null;
}
}
AndroidNotificationChannel androidNotificationChannel = AndroidNotificationChannel(
math.Random.secure().nextInt(1000000).toString(),
'High Importance Notification',
importance: Importance.max,
/// Xử lý message khi app foreground
Future<void> _handleForegroundMessage(
firebase_messaging.RemoteMessage message) async {
try {
final type = message.data['type'] as String? ?? 'normal';
final title =
message.notification?.title ?? message.data['title'] ?? 'SFM';
final body = message.notification?.body ?? message.data['body'] ?? '';
dev.log('Foreground message: type=$type, title=$title, body=$body');
if (type == 'smoke_warning') {
// Hiển thị alarm, không hiển thị notification
await _showAlarm(title, body);
} else {
// Hiển thị notification local
await _showForegroundNotification(title, body, type);
}
} catch (e, stackTrace) {
AppLoggerUtils.error('Error handling foreground message', e, stackTrace);
}
}
/// Hiển thị alarm (smoke warning)
Future<void> _showAlarm(String title, String body) async {
try {
await Alarm.init();
final alarmSettings = AlarmSettings(
id: 42,
dateTime: DateTime.now(),
assetAudioPath: 'assets/sounds/warning_alarm.mp3',
loopAudio: true,
vibrate: true,
warningNotificationOnKill: Platform.isIOS,
androidFullScreenIntent: true,
volumeSettings: VolumeSettings.fade(
volume: 0.8,
fadeDuration: const Duration(seconds: 5),
volumeEnforced: true,
),
notificationSettings: NotificationSettings(
title: title,
body: body,
stopButton: 'Dừng thông báo',
icon: "ic_launcher",
),
);
final androidPlugin = _flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
await androidPlugin?.deleteNotificationChannel(androidNotificationChannel.id);
await Alarm.set(alarmSettings: alarmSettings);
dev.log('Alarm set successfully: $title');
} catch (e, stackTrace) {
AppLoggerUtils.error('Failed to set alarm', e, stackTrace);
}
}
AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
androidNotificationChannel.id,
androidNotificationChannel.name,
channelDescription: "Channel description",
/// Hiển thị notification local khi app foreground
Future<void> _showForegroundNotification(
String title, String body, String type) async {
try {
final androidDetails = AndroidNotificationDetails(
'sfm_channel_${math.Random.secure().nextInt(1000000)}',
'SFM Notifications',
channelDescription: 'Notifications from SFM app',
sound: getSound(type),
importance: androidNotificationChannel.importance,
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
actions: type == "smoke_warning"
? [
const AndroidNotificationAction(
"id1",
"Hogg xóa được",
// true thì khi nhấn vào button sẽ mở giao diện ra
showsUserInterface: true,
cancelNotification: false,
)
]
: null
);
const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails(
final iosDetails = DarwinNotificationDetails(
sound: _getSoundNameForIOS(type),
presentAlert: true,
presentBadge: true,
presentBanner: true,
presentSound: true,
);
NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails,
iOS: darwinNotificationDetails,
final notificationDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
// Truyền payload vào thông báo
String payload = message.data['type'] ?? "default";
await _flutterLocalNotificationsPlugin.show(
await _notificationsPlugin.show(
math.Random.secure().nextInt(1000000),
title,
body,
notificationDetails,
payload: payload,
payload: type,
);
dev.log("Displayed notification with title: $title, body: $body, type: $type");
dev.log('Foreground notification shown: title=$title, type=$type');
} catch (e, stackTrace) {
AppLoggerUtils.error(
'Failed to show foreground notification', e, stackTrace);
}
}
/// Hiển thị notification cho background/terminate
Future<void> showBackgroundOrTerminateNotification(
firebase_messaging.RemoteMessage message) async {
try {
final type = message.data['type'] as String? ?? 'normal';
final title =
message.notification?.title ?? message.data['title'] ?? 'SFM';
final body = message.notification?.body ?? message.data['body'] ?? '';
dev.log('Background/terminate notification: type=$type, title=$title');
// Cho Android: nếu type = smoke_warning, hiển thị alarm
// Cho iOS: chỉ hiển thị notification, APNs payload sẽ phát sound natively
if (type == 'smoke_warning' && Platform.isAndroid) {
dev.log('→ Showing alarm for Android background');
await _showAlarm(title, body);
return;
}
// Hiển thị notification (cho cả iOS lẫn Android, nhưng iOS không có alarm)
final channelId = math.Random.secure().nextInt(1000000).toString();
const channelName = 'SFM Notifications';
const channelDescription = 'Notifications from SFM app';
final androidDetails = AndroidNotificationDetails(
channelId,
channelName,
channelDescription: channelDescription,
sound: getSound(type),
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
);
final iosDetails = DarwinNotificationDetails(
sound: _getSoundNameForIOS(type),
presentAlert: true,
presentBadge: true,
presentBanner: true,
presentSound: true,
);
final notificationDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _notificationsPlugin.show(
int.parse(channelId),
title,
body,
notificationDetails,
payload: type,
);
dev.log(
'Background/terminate notification shown: title=$title, type=$type');
} catch (e, stackTrace) {
AppLoggerUtils.error(
'Failed to show background/terminate notification', e, stackTrace);
}
}
AndroidNotificationSound getSound(String type) {
@@ -128,35 +269,110 @@ class NotificationServices {
} else if (type == "battery_warning") {
return const RawResourceAndroidNotificationSound("new_alarm");
} else {
return const RawResourceAndroidNotificationSound("normal");
return const RawResourceAndroidNotificationSound("warning_alarm");
}
}
String _getSoundNameForIOS(String type) {
// iOS tìm file trong app bundle (không có đuôi)
if (type == "smoke_warning" || type == "battery_warning") {
return "warning_alarm"; // file: warning_alarm.caf trong bundle
}
return "default";
}
void handleMessage(String? payload, PersistentTabController controller) {
dev.log("Handling notification tap with payload: $payload");
AppLoggerUtils.info("Handling notification tap with payload: $payload");
controller.jumpToTab(1);
if (payload == "smoke_warning") {
// TODO: Navigate to smoke warning screen nếu cần
// NavigationRouter.navigateToSmokeWarningScreen();
} else if (payload == "battery_warning") {
// TODO: Navigate to battery warning screen nếu cần
// NavigationRouter.navigateToBatteryWarningScreen();
}
}
Future<void> setupInteractMessage(PersistentTabController controller) async {
// Khi app terminated
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
// Khi app terminated, được mở bằng notification
firebase_messaging.RemoteMessage? initialMessage =
await firebase_messaging.FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final type = initialMessage.data['type'] as String?;
handleMessage(type, controller);
});
}
// Khi app background, user tap notification
firebase_messaging.FirebaseMessaging.onMessageOpenedApp.listen((message) {
try {
handleMessage(initialMessage.data['type'],controller);
} catch (e, stack) {
dev.log("Error handling initial message: $e\n$stack");
final type = message.data['type'] as String?;
handleMessage(type, controller);
} catch (e, stackTrace) {
AppLoggerUtils.error('Error in onMessageOpenedApp', e, stackTrace);
}
});
}
// Khi app ở background
FirebaseMessaging.onMessageOpenedApp.listen((message) {
@pragma('vm:entry-point')
static Future<void> firebaseMessagingBackgroundHandler(
firebase_messaging.RemoteMessage message) async {
try {
handleMessage(message.data['type'],controller);
} catch (e, stack) {
dev.log("Error in onMessageOpenedApp: $e\n$stack");
// Log to device storage for debugging background isolate
dev.log('═══ BACKGROUND HANDLER STARTED ═══');
dev.log('Message data: ${message.data}');
dev.log('Notification: ${message.notification}');
await Alarm.init();
dev.log('✓ Alarm initialized');
if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
name: "sfm-notification",
);
dev.log('✓ Firebase initialized in background');
} else {
dev.log('✓ Firebase already initialized');
}
final type = message.data['type'] as String? ?? 'normal';
final title = message.notification?.title ??
message.data['title'] ??
'Thông báo từ SmartFM';
final body = message.notification?.body ??
message.data['body'] ??
'Bạn có một thông báo mới';
dev.log('Message type: $type');
dev.log('Title: $title');
dev.log('Body: $body');
// Logic: Hiển thị local notification cho background/terminate
// iOS sẽ phát sound natively thông qua APNs payload (aps.sound)
// Android sẽ phát sound thông qua local notification
try {
final notificationService = NotificationServices();
await notificationService.initialize();
dev.log('✓ Notification service initialized');
await notificationService
.showBackgroundOrTerminateNotification(message);
dev.log('✓ Background/terminate notification shown');
} catch (notifError, notifStackTrace) {
dev.log('✗ ERROR showing notification: $notifError');
dev.log('Stack trace: $notifStackTrace');
rethrow;
}
dev.log('═══ BACKGROUND HANDLER COMPLETED ═══');
} catch (e, stackTrace) {
dev.log('✗ BACKGROUND HANDLER FAILED');
dev.log('Error: $e');
dev.log('Stack trace: $stackTrace');
}
});
}
}

View File

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

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class SharedComponentLoadingAnimation extends StatefulWidget {
const SharedComponentLoadingAnimation({super.key});
@override
State<SharedComponentLoadingAnimation> createState() => _SharedComponentLoadingAnimationState();
}
class _SharedComponentLoadingAnimationState extends State<SharedComponentLoadingAnimation> {
@override
Widget build(BuildContext context) {
return Center(
child: LottieBuilder.asset(
'assets/animations/component_loading.json',
width: 80,
height: 80,
fit: BoxFit.fill,
),
);
}
}

View File

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

View File

@@ -1,7 +1,7 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
import 'package:sfm_app/product/utils/date_time_utils.dart';
import '../../feature/device_log/device_logs_model.dart';
import '../utils/date_time_utils.dart';
Widget sharedLineChart(String chartName, List<SensorLogs> sensors) {
return LineChart(

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class SharedLoadingAnimation extends StatefulWidget {
const SharedLoadingAnimation({super.key});
@override
State<SharedLoadingAnimation> createState() => _SharedLoadingAnimationState();
}
class _SharedLoadingAnimationState extends State<SharedLoadingAnimation> {
@override
Widget build(BuildContext context) {
return Center(
child: LottieBuilder.asset(
'assets/animations/loading.json',
width: 100,
height: 100,
fit: BoxFit.fill,
),
);
}
}

View File

@@ -182,23 +182,23 @@ class _SharedPieChartState extends State<SharedPieChart> {
switch (originalIndex) {
case 0: // OFFLINE_STATE
log("Touched Index device state = -1");
widget.devicesManagerBloc.getDeviceByState(-1);
widget.devicesManagerBloc.getDeviceByState(context,-1);
break;
case 1: // NORMAL_STATE
log("Touched Index Get device state = 0");
widget.devicesManagerBloc.getDeviceByState(0);
widget.devicesManagerBloc.getDeviceByState(context,0);
break;
case 2: // WARNING_STATE
log("Touched Index Get device state = 1");
widget.devicesManagerBloc.getDeviceByState(1);
widget.devicesManagerBloc.getDeviceByState(context,1);
break;
case 3: // INPROGRESS_STATE
log("Touched Index Get device state = 2");
widget.devicesManagerBloc.getDeviceByState(2);
widget.devicesManagerBloc.getDeviceByState(context,2);
break;
case 4: // ERROR_STATE
log("Touched Index Get device state = 3");
widget.devicesManagerBloc.getDeviceByState(3);
widget.devicesManagerBloc.getDeviceByState(context,3);
break;
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
class SharedRocketContainer extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final double width = size.width;
final double height = size.height;
const double pointyWidth = 20.0;
Path path = Path();
path.moveTo(0, 0);
path.lineTo(width - pointyWidth, 0);
path.lineTo(width, height / 2);
path.lineTo(width - pointyWidth, height);
path.lineTo(0, height);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}

View File

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

View File

@@ -1,7 +1,7 @@
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:sfm_app/product/theme/app_theme.dart';
import 'app_theme.dart';
class AppThemeDark extends AppTheme {
static AppThemeDark? _instance;
static AppThemeDark get instance {
@@ -13,6 +13,7 @@ class AppThemeDark extends AppTheme {
@override
ThemeData get theme => FlexThemeData.dark(
scaffoldBackground: Colors.black,
useMaterial3: true,
scheme: FlexScheme.flutterDash,
subThemesData: const FlexSubThemesData(

View File

@@ -13,6 +13,7 @@ class AppThemeLight extends AppTheme {
@override
ThemeData get theme => FlexThemeData.light(
scaffoldBackground: Colors.white,
useMaterial3: true,
scheme: FlexScheme.flutterDash,
bottomAppBarElevation: 20.0,

View File

@@ -0,0 +1,39 @@
import 'package:logger/logger.dart';
class AppLoggerUtils{
static final Logger _logger = Logger(
printer: PrettyPrinter(
methodCount: 2,
errorMethodCount: 8,
lineLength: 120,
colors: true,
printEmojis: true,
dateTimeFormat: (DateTime dateTime) {
// Tùy chỉnh định dạng thời gian
return '[${DateTime.now().toLocal().toString().split(' ')[1]}]';
},
// dateTimeFormat: DateTimeFormat.dateAndTime
),
level: Level.debug, // Cấp độ log tối thiểu (có thể thay đổi trong môi trường production)
);
static void debug(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.d(message);
}
static void info(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.i(message);
}
static void warning(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.w(message, error: error, stackTrace: stackTrace);
}
static void error(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.e(message, error: error, stackTrace: stackTrace);
}
static void trace(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.t(message, error: error, stackTrace: stackTrace);
}
}

View File

@@ -1,12 +1,10 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
import 'package:sfm_app/product/services/api_services.dart';
import 'package:sfm_app/product/services/language_services.dart';
import 'package:sfm_app/product/shared/model/district_model.dart';
import 'package:sfm_app/product/shared/model/province_model.dart';
import '../../feature/device_log/device_logs_model.dart';
import '../services/api_services.dart';
import '../services/language_services.dart';
import '../shared/model/district_model.dart';
import '../shared/model/province_model.dart';
import '../../feature/devices/device_model.dart';
import '../constant/icon/icon_constants.dart';
import '../shared/model/ward_model.dart';
@@ -93,30 +91,34 @@ class DeviceUtils {
return map;
}
Future<String> getFullDeviceLocation(
BuildContext context, String areaPath) async {
if (areaPath != "") {
BuildContext context, String areaPath, String? deviceName) async {
if (areaPath.isNotEmpty) {
List<String> parts = areaPath.split('_');
if (parts.length < 3 || parts[0].isEmpty || parts[1].isEmpty || parts[2].isEmpty) {
if (deviceName != null && deviceName.isNotEmpty) {
return deviceName;
} else {
return appLocalization(context).no_data_message;
}
}
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']);
Province province = await apiServices.getProvinceByID(provinceID);
District district = await apiServices.getDistrictByID(districtID);
Ward ward = await apiServices.getWardByID(wardID);
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) {
@@ -157,11 +159,11 @@ class DeviceUtils {
} else if (state == 0) {
return const Color(0xFF9EF16D);
} else if (state == 2) {
return const Color(0xFFF5EF44);;
return const Color(0xFFF5EF44);
} else if (state == -1) {
return const Color(0xFFBBBAC2);;
return const Color(0xFFBBBAC2);
} else {
return const Color(0xFFF5EF44);;
return const Color(0xFFF5EF44);
}
}

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More