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, # The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices. # 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 include: package:flutter_lints/flutter.yaml
linter: linter:
@@ -22,8 +26,8 @@ linter:
# `// ignore_for_file: name_of_lint` syntax on the line or in the file # `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint. # producing the lint.
rules: rules:
- prefer_relative_imports: true
# avoid_print: false # Uncomment to disable the `avoid_print` rule # avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # 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 localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { 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') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@@ -21,21 +23,12 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' 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 keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} }
android { android {
namespace "vn.smatec.sfm" namespace "vn.smatec.sfm"
compileSdkVersion 35 compileSdkVersion 35
@@ -44,12 +37,12 @@ android {
compileOptions { compileOptions {
// Flag to enable support for the new language APIs // Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '17'
} }
sourceSets { sourceSets {
@@ -102,9 +95,9 @@ flutter {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.firebase:firebase-messaging-directboot:20.2.0' implementation platform('com.google.firebase:firebase-bom:33.15.0')
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
implementation 'androidx.window:window:1.0.0' implementation 'androidx.window:window:1.0.0'
implementation 'androidx.window:window-java: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.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 --> <!-- Permissions options for the `camera` group -->
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<!-- Permissions options for the `location` group --> <!-- Permissions options for the `location` group -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_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 --> <!-- END Permissions Package -->
<application <application
android:label="SFM" android:label="SFM"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"> android:icon="@mipmap/launcher_icon">
<meta-data android:name="com.google.android.geo.API_KEY" <meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyA9C7Pmxw6Gw3H2mM4WA_XGngRIIr2VS7k"/> android:value="AIzaSyDI8b-PUgKUgj5rHdtgEHCwWjUXYJrqYhE"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
@@ -34,6 +40,7 @@
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true" android:showWhenLocked="true"
android:turnScreenOn="true" android:turnScreenOn="true"
android:enableOnBackInvokedCallback="true"
android:exported="true"> android:exported="true">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
@@ -60,7 +67,25 @@
<service <service
android:name="com.dexterous.flutterlocalnotifications.ForegroundService" android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
android:exported="false" 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.ActionBroadcastReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" /> <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"> <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 { plugins {
ext.kotlin_version = '1.8.22' id 'com.google.gms.google-services' version '4.3.15' apply false
repositories {
google()
mavenCentral()
} }
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 { allprojects {
repositories { repositories {
gradlePluginPortal()
google() google()
mavenCentral() mavenCentral()
} }
@@ -43,3 +31,7 @@ subprojects {
tasks.register("clean", Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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 { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.2.1" apply false id "com.android.application" version "8.3.2" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" 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" 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 # 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. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -1,62 +1,197 @@
PODS: 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 (1.0.0)
- flutter_barcode_scanner_plus (3.0.7): - flutter_barcode_scanner_plus (3.0.7):
- Flutter - Flutter
- flutter_fgbg (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1): - flutter_local_notifications (0.0.1):
- Flutter - Flutter
- geolocator_apple (1.2.0):
- Flutter
- FlutterMacOS
- Google-Maps-iOS-Utils (5.0.0): - Google-Maps-iOS-Utils (5.0.0):
- GoogleMaps (~> 8.0) - GoogleMaps (~> 8.0)
- google_maps_flutter_ios (0.0.1): - google_maps_flutter_ios (0.0.1):
- Flutter - Flutter
- Google-Maps-iOS-Utils (< 7.0, >= 5.0) - Google-Maps-iOS-Utils (< 7.0, >= 5.0)
- GoogleMaps (< 10.0, >= 8.4) - GoogleMaps (< 10.0, >= 8.4)
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleMaps (8.4.0): - GoogleMaps (8.4.0):
- GoogleMaps/Maps (= 8.4.0) - GoogleMaps/Maps (= 8.4.0)
- GoogleMaps/Base (8.4.0) - GoogleMaps/Base (8.4.0)
- GoogleMaps/Maps (8.4.0): - GoogleMaps/Maps (8.4.0):
- GoogleMaps/Base - 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): - maps_launcher (0.0.1):
- Flutter - 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): - permission_handler_apple (9.3.0):
- Flutter - Flutter
- PromisesObjC (2.4.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES: 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 (from `Flutter`)
- flutter_barcode_scanner_plus (from `.symlinks/plugins/flutter_barcode_scanner_plus/ios`) - 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`) - 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`) - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`)
- maps_launcher (from `.symlinks/plugins/maps_launcher/ios`) - maps_launcher (from `.symlinks/plugins/maps_launcher/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/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: SPEC REPOS:
trunk: trunk:
- Firebase
- FirebaseCore
- FirebaseCoreInternal
- FirebaseInstallations
- FirebaseMessaging
- Google-Maps-iOS-Utils - Google-Maps-iOS-Utils
- GoogleDataTransport
- GoogleMaps - GoogleMaps
- GoogleUtilities
- nanopb
- PromisesObjC
EXTERNAL SOURCES: 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: Flutter:
:path: Flutter :path: Flutter
flutter_barcode_scanner_plus: flutter_barcode_scanner_plus:
:path: ".symlinks/plugins/flutter_barcode_scanner_plus/ios" :path: ".symlinks/plugins/flutter_barcode_scanner_plus/ios"
flutter_fgbg:
:path: ".symlinks/plugins/flutter_fgbg/ios"
flutter_local_notifications: flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios" :path: ".symlinks/plugins/flutter_local_notifications/ios"
geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/darwin"
google_maps_flutter_ios: google_maps_flutter_ios:
:path: ".symlinks/plugins/google_maps_flutter_ios/ios" :path: ".symlinks/plugins/google_maps_flutter_ios/ios"
maps_launcher: maps_launcher:
:path: ".symlinks/plugins/maps_launcher/ios" :path: ".symlinks/plugins/maps_launcher/ios"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :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: 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: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_barcode_scanner_plus: 5777819a85622aed4284fb14d1fa33b72a64003d flutter_barcode_scanner_plus: e5ef7f41cdbf3086e1b9348dce986dde3aa08696
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_fgbg: d3da78df78454b1808f0829a5da9cd17dfe16444
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321
google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 google_maps_flutter_ios: 0291eb2aa252298a769b04d075e4a9d747ff7264
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 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 COCOAPODS: 1.16.2

View File

@@ -17,6 +17,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -64,6 +65,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -109,6 +111,7 @@
97C146E51CF9000F007C117D = { 97C146E51CF9000F007C117D = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A241E2BE2EAA2F1C00664284 /* warning_alarm.caf */,
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
@@ -256,6 +259,7 @@
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
5EFD34A62DA7D89800351DB2 /* GoogleService-Info.plist in Resources */, 5EFD34A62DA7D89800351DB2 /* GoogleService-Info.plist in Resources */,
A241E2BF2EAA2F1C00664284 /* warning_alarm.caf in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
); );
@@ -465,7 +469,7 @@
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SmartFM; INFOPLIST_KEY_CFBundleDisplayName = SmartFM;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -648,7 +652,7 @@
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SmartFM; INFOPLIST_KEY_CFBundleDisplayName = SmartFM;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -678,7 +682,7 @@
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SmartFM; INFOPLIST_KEY_CFBundleDisplayName = SmartFM;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@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" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
@@ -72,11 +73,13 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> 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 GoogleMaps
import FirebaseCore import FirebaseCore
import FirebaseMessaging import FirebaseMessaging
import UserNotifications
import alarm
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
@@ -14,6 +16,7 @@ import FirebaseMessaging
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
} }
SwiftAlarmPlugin.registerBackgroundTasks()
FirebaseApp.configure() FirebaseApp.configure()
GMSServices.provideAPIKey("AIzaSyA9C7Pmxw6Gw3H2mM4WA_XGngRIIr2VS7k") GMSServices.provideAPIKey("AIzaSyA9C7Pmxw6Gw3H2mM4WA_XGngRIIr2VS7k")
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<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"> <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> <dependencies>
<deployment identifier="iOS"/> <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> </dependencies>
<scenes> <scenes>
<!--Flutter View Controller--> <!--Flutter View Controller-->
@@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <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"/> <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> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="121" y="-34"/>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Sfm App</string> <string>SmartFM</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@@ -39,6 +39,7 @@
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>fetch</string> <string>fetch</string>
<string>audio</string>
<string>remote-notification</string> <string>remote-notification</string>
</array> </array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
@@ -60,5 +61,9 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.gdelataillade.fetch</string>
</array>
</dict> </dict>
</plist> </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'; import '../feature/bell/bell_model.dart';
class BellBloc extends BlocBase { class BellBloc extends BlocBase {
final bellItems = StreamController<List<BellItems>>.broadcast(); final bellItems = StreamController<List<BellItems>>.broadcast();
StreamSink<List<BellItems>> get sinkBellItems => bellItems.sink; StreamSink<List<BellItems>> get sinkBellItems => bellItems.sink;
Stream<List<BellItems>> get streamBellItems => bellItems.stream; Stream<List<BellItems>> get streamBellItems => bellItems.stream;

View File

@@ -1,19 +1,15 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.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 '../product/utils/date_time_utils.dart';
import '../feature/device_log/device_logs_model.dart'; import '../feature/device_log/device_logs_model.dart';
import '../feature/devices/device_model.dart'; import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart'; import '../product/base/bloc/base_bloc.dart';
import '../product/utils/device_utils.dart';
class DetailDeviceBloc extends BlocBase { class DetailDeviceBloc extends BlocBase {
APIServices apiServices = APIServices(); APIServices apiServices = APIServices();
@@ -42,14 +38,12 @@ class DetailDeviceBloc extends BlocBase {
String thingID, String thingID,
Completer<GoogleMapController> controller, Completer<GoogleMapController> controller,
) async { ) async {
String body = await apiServices.getDeviceInfomation(thingID); await apiServices.execute(context, () async {
if (body != "") { Device device = await apiServices.getDeviceInformation(thingID);
final data = jsonDecode(body);
Device device = Device.fromJson(data);
sinkDeviceInfo.add(device); sinkDeviceInfo.add(device);
if (device.areaPath != null) { if (device.areaPath != null) {
String fullLocation = await DeviceUtils.instance String fullLocation = await DeviceUtils.instance
.getFullDeviceLocation(context, device.areaPath!); .getFullDeviceLocation(context, device.areaPath!, "");
log("Location: $fullLocation"); log("Location: $fullLocation");
sinkDeviceLocation.add(fullLocation); sinkDeviceLocation.add(fullLocation);
} }
@@ -74,20 +68,56 @@ class DetailDeviceBloc extends BlocBase {
mapController mapController
.animateCamera(CameraUpdate.newCameraPosition(cameraPosition)); .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 { void findLocation(BuildContext context, String areaPath) async {
String fullLocation = String fullLocation =
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath); await DeviceUtils.instance.getFullDeviceLocation(context, areaPath, "");
sinkDeviceLocation.add(fullLocation); sinkDeviceLocation.add(fullLocation);
} }
void getNearerSensorValue(String thingID) async { void getNearerSensorValue(BuildContext context, String thingID) async {
apiServices.execute(context, () async {
List<SensorLogs> sensorTemps = []; List<SensorLogs> sensorTemps = [];
DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2)); DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2));
String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo); String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo);
String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now()); String now =
DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
Map<String, dynamic> params = { Map<String, dynamic> params = {
'thing_id': thingID, 'thing_id': thingID,
'from': from, 'from': from,
@@ -95,10 +125,8 @@ class DetailDeviceBloc extends BlocBase {
'limit': '100', 'limit': '100',
'n': '7', 'n': '7',
}; };
final body = await apiServices.getLogsOfDevice(thingID, params); DeviceLog devicesListLog =
if (body != "") { await apiServices.getLogsOfDevice(thingID, params);
final data = jsonDecode(body);
DeviceLog devicesListLog = DeviceLog.fromJson(data);
if (devicesListLog.sensors!.isNotEmpty) { if (devicesListLog.sensors!.isNotEmpty) {
for (var sensor in devicesListLog.sensors!) { for (var sensor in devicesListLog.sensors!) {
sensorTemps.add(sensor); sensorTemps.add(sensor);
@@ -108,6 +136,35 @@ class DetailDeviceBloc extends BlocBase {
} else { } else {
sinkSensorTemps.add([]); 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:async';
import 'dart:convert'; import 'package:flutter/material.dart';
import '../feature/devices/device_model.dart'; import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart'; import '../product/base/bloc/base_bloc.dart';
import '../product/constant/app/app_constants.dart'; import '../product/constant/app/app_constants.dart';
import '../product/services/api_services.dart'; import '../product/services/api_services.dart';
import '../product/utils/date_time_utils.dart'; import '../product/utils/date_time_utils.dart';
import '../product/utils/device_utils.dart'; import '../product/utils/device_utils.dart';
import '../feature/device_log/device_logs_model.dart'; import '../feature/device_log/device_logs_model.dart';
@@ -35,28 +34,28 @@ class DeviceLogsBloc extends BlocBase {
@override @override
void dispose() {} void dispose() {}
void getAllDevices() async { void getAllDevices(BuildContext context) async {
String body = await apiServices.getOwnerDevices(); await apiServices.execute(context, () async {
if (body != "") { List<Device> originalDevices = await apiServices.getOwnerDevices();
final data = jsonDecode(body);
List<dynamic> items = data['items'];
List<Device> originalDevices = Device.fromJsonDynamicList(items);
List<Device> devices = List<Device> devices =
DeviceUtils.instance.sortDeviceByState(originalDevices); DeviceUtils.instance.sortDeviceByState(originalDevices);
sinkAllDevices.add(devices); sinkAllDevices.add(devices);
} });
} }
void getDeviceLogByThingID( void getDeviceLogByThingID(
BuildContext context,
int offset, int offset,
String thingID, String thingID,
DateTime fromDate, DateTime fromDate,
List<SensorLogs> sensors, List<SensorLogs> sensors,
) async { ) async {
await apiServices.execute(context, () async {
sinkmessage.add(ApplicationConstants.LOADING); sinkmessage.add(ApplicationConstants.LOADING);
String fromDateString = String fromDateString =
DateTimeUtils.instance.formatDateTimeToString(fromDate); DateTimeUtils.instance.formatDateTimeToString(fromDate);
String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now()); String now =
DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
Map<String, dynamic> params = { Map<String, dynamic> params = {
'thing_id': thingID, 'thing_id': thingID,
'from': fromDateString, 'from': fromDateString,
@@ -65,10 +64,8 @@ class DeviceLogsBloc extends BlocBase {
"offset": offset.toString(), "offset": offset.toString(),
"asc": "true" "asc": "true"
}; };
final body = await apiServices.getLogsOfDevice(thingID, params); DeviceLog devicesListLog =
if (body != "") { await apiServices.getLogsOfDevice(thingID, params);
final data = jsonDecode(body);
DeviceLog devicesListLog = DeviceLog.fromJson(data);
if (devicesListLog.sensors!.isEmpty) { if (devicesListLog.sensors!.isEmpty) {
bool hasMore = false; bool hasMore = false;
sinkHasMore.add(hasMore); sinkHasMore.add(hasMore);
@@ -81,6 +78,38 @@ class DeviceLogsBloc extends BlocBase {
sinkmessage.add(ApplicationConstants.NO_DATA); sinkmessage.add(ApplicationConstants.NO_DATA);
} }
sinkSensors.add(sensors); 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 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../feature/settings/device_notification_settings/device_notification_settings_model.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; StreamSink<String> get sinkMessageChange => messageChange.sink;
Stream<String> get streaMmessageChange => messageChange.stream; Stream<String> get streaMmessageChange => messageChange.stream;
@override @override
void dispose() {} void dispose() {}
} }

View File

@@ -1,16 +1,16 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../product/services/api_services.dart'; import '../product/services/api_services.dart';
import '../product/services/language_services.dart'; import '../product/services/language_services.dart';
import '../product/shared/model/ward_model.dart'; import '../product/shared/model/ward_model.dart';
import '../product/shared/shared_snack_bar.dart';
import '../product/utils/response_status_utils.dart'; import '../product/utils/response_status_utils.dart';
import '../product/shared/model/district_model.dart'; import '../product/shared/model/district_model.dart';
import '../product/shared/model/province_model.dart'; import '../product/shared/model/province_model.dart';
import '../feature/devices/device_model.dart'; import '../feature/devices/device_model.dart';
@@ -75,151 +75,281 @@ class DeviceUpdateBloc extends BlocBase {
// deviceInfo.done; // deviceInfo.done;
} }
Future<void> getAllProvinces() async { Future<void> getAllProvinces(BuildContext context) async {
List<DropdownMenuItem<Province>> provincesData = []; List<DropdownMenuItem<Province>> provincesData = [];
provincesData.clear(); provincesData.clear();
sinkListProvinces.add(provincesData); sinkListProvinces.add(provincesData);
final body = await apiServices.getAllProvinces(); await apiServices.execute(context, () async {
final data = jsonDecode(body); List<Province> provinces = await apiServices.getAllProvinces();
List<dynamic> items = data["items"];
final provinces = Province.fromJsonDynamicList(items);
for (var province in provinces) { for (var province in provinces) {
provincesData.add( provincesData.add(
DropdownMenuItem(value: province, child: Text(province.fullName!))); DropdownMenuItem(value: province, child: Text(province.fullName!)));
} }
sinkListProvinces.add(provincesData); sinkListProvinces.add(provincesData);
});
} }
Future<void> getAllDistricts(String provinceID) async { Future<void> getAllDistricts(BuildContext context, String provinceID) async {
List<DropdownMenuItem<District>> districtsData = []; List<DropdownMenuItem<District>> districtsData = [];
districtsData.clear(); districtsData.clear();
sinkListDistricts.add(districtsData); sinkListDistricts.add(districtsData);
final body = await apiServices.getAllDistricts(provinceID); await apiServices.execute(context, () async {
final data = jsonDecode(body); final districts = await apiServices.getAllDistricts(provinceID);
List<dynamic> items = data["items"];
final districts = District.fromJsonDynamicList(items);
for (var district in districts) { for (var district in districts) {
districtsData.add( districtsData.add(
DropdownMenuItem(value: district, child: Text(district.fullName!))); DropdownMenuItem(value: district, child: Text(district.fullName!)));
} }
sinkListDistricts.add(districtsData); sinkListDistricts.add(districtsData);
});
} }
Future<void> getAllWards(String districtID) async { Future<void> getAllWards(BuildContext context, String districtID) async {
List<DropdownMenuItem<Ward>> wardsData = []; List<DropdownMenuItem<Ward>> wardsData = [];
wardsData.clear(); wardsData.clear();
sinkListWards.add(wardsData); sinkListWards.add(wardsData);
final body = await apiServices.getAllWards(districtID); await apiServices.execute(context, () async {
final data = jsonDecode(body); final wards = await apiServices.getAllWards(districtID);
List<dynamic> items = data["items"];
final wards = Ward.fromJsonDynamicList(items);
for (var ward in wards) { 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); sinkListWards.add(wardsData);
});
} }
Future<void> getDeviceInfomation( Future<void> getDeviceInformation(
BuildContext context,
String thingID, String thingID,
List<DropdownMenuItem<District>> districtsData,
List<DropdownMenuItem<Ward>> wardsData,
TextEditingController deviceNameController, TextEditingController deviceNameController,
TextEditingController latitudeController, TextEditingController latitudeController,
TextEditingController longitudeController) async { TextEditingController longitudeController) async {
String body = await apiServices.getDeviceInfomation(thingID); await apiServices.execute(context, () async {
final data = jsonDecode(body); Device device = await apiServices.getDeviceInformation(thingID);
Device device = Device.fromJson(data);
sinkDeviceInfo.add(device); sinkDeviceInfo.add(device);
deviceNameController.text = device.name ?? ""; deviceNameController.text = device.name ?? "";
latitudeController.text = device.settings!.latitude ?? ""; latitudeController.text = device.settings!.latitude ?? "";
longitudeController.text = device.settings!.longitude ?? ""; longitudeController.text = device.settings!.longitude ?? "";
if (device.areaPath != "") {
if (device.areaPath != null && device.areaPath!.isNotEmpty) {
List<String> areaPath = device.areaPath!.split('_'); List<String> areaPath = device.areaPath!.split('_');
// Kiểm tra độ dài của areaPath
if (areaPath.length >= 3) {
String provinceCode = areaPath[0]; String provinceCode = areaPath[0];
String districtCode = areaPath[1]; String districtCode = areaPath[1];
String wardCode = areaPath[2]; String wardCode = areaPath[2];
getAllDistricts(provinceCode);
getAllWards(districtCode); // Kiểm tra các mã có hợp lệ không (không rỗng)
final provinceResponse = await apiServices.getProvinceByID(provinceCode); if (provinceCode.isNotEmpty &&
final provincesData = jsonDecode(provinceResponse); districtCode.isNotEmpty &&
Province province = Province.fromJson(provincesData['data']); wardCode.isNotEmpty) {
final districtResponse = await apiServices.getDistrictByID(districtCode); try {
final districtData = jsonDecode(districtResponse); // Lấy danh sách districts và wards
District district = District.fromJson(districtData['data']); await getAllDistricts(context, provinceCode);
final wardResponse = await apiServices.getWardByID(wardCode); await getAllWards(context, districtCode);
final wardData = jsonDecode(wardResponse);
Ward ward = Ward.fromJson(wardData['data']); // Xử lý Province
try {
Province province =
await apiServices.getProvinceByID(provinceCode);
Map<String, String> provinceData = { Map<String, String> provinceData = {
"name": province.fullName!, "name": province.fullName ?? "Unknown Province",
"code": province.code! "code": province.code ?? provinceCode
}; };
sinkProvinceData.add(provinceData); 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 = { Map<String, String> districData = {
"name": district.fullName!, "name": district.fullName ?? "Unknown District",
"code": district.code!, "code": district.code ?? districtCode,
}; };
sinkDistrictData.add(districData); 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 = { Map<String, String> wardMap = {
"name": ward.fullName!, "name": ward.fullName ?? "Unknown Ward",
"code": ward.code!, "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); 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 { Future<Province> getProvinceByName(BuildContext context, String name) async {
final response = await apiServices.getProvincesByName(name); return await apiServices.execute(context, () async {
final data = jsonDecode(response); List<Province> provinces = await apiServices.getProvincesByName(name);
if (data != null &&
data.containsKey('items') &&
data['items'] != null &&
data['items'].isNotEmpty) {
List<dynamic> items = data['items'];
List<Province> provinces = Province.fromJsonDynamicList(items);
if (provinces.isNotEmpty) { if (provinces.isNotEmpty) {
return provinces[0]; return provinces[0];
} } else {
}
return Province(name: "null"); 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 { Future<District> getDistrictByName(
final response = await apiServices.getWarsdByName(name); BuildContext context, String name, String provinceCode) async {
final data = jsonDecode(response); return apiServices.execute(context, () async {
if (data != null && data['items'] != null) { final districts = await apiServices.getDistrictsByName(name);
List<dynamic> items = data['items']; return districts.firstWhere(
if (items.isNotEmpty) { (district) => district.provinceCode == provinceCode,
List<Ward> wards = Ward.fromJsonDynamicList(items); orElse: () => District(name: "null"),
if (wards.isNotEmpty) { );
for (var ward in wards) { });
if (ward.districtCode == districtCode) {
return ward;
} }
}
} Future<Ward> getWardByName(
} BuildContext context, String name, String districtCode) async {
} return apiServices.execute(context, () async {
return Ward(name: "null"); final wards = await apiServices.getWardsByName(name);
return wards.firstWhere(
(ward) => ward.districtCode == districtCode,
orElse: () => Ward(name: "null"),
);
});
} }
Future<void> updateDevice( Future<void> updateDevice(
@@ -232,6 +362,7 @@ class DeviceUpdateBloc extends BlocBase {
String districtCode, String districtCode,
String wardCode, String wardCode,
) async { ) async {
await apiServices.execute(context, () async {
DateTime dateTime = DateTime.now(); DateTime dateTime = DateTime.now();
String formattedDateTime = String formattedDateTime =
DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime); 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_success,
appLocalization(context).notification_update_device_failed, 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:async';
import 'dart:convert'; import 'package:flutter/cupertino.dart';
import '../feature/devices/device_model.dart'; import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart'; import '../product/base/bloc/base_bloc.dart';
import '../product/constant/app/app_constants.dart'; import '../product/constant/app/app_constants.dart';
import '../product/services/api_services.dart'; import '../product/services/api_services.dart';
import '../product/shared/shared_snack_bar.dart';
import '../product/utils/device_utils.dart'; import '../product/utils/device_utils.dart';
class DevicesManagerBloc extends BlocBase { 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]); sinkTagStates.add([state]);
Map<String, List<Device>> deviceByState = { Map<String, List<Device>> deviceByState = {
@@ -85,34 +86,38 @@ class DevicesManagerBloc extends BlocBase {
}; };
List<Device> devices = []; List<Device> devices = [];
String body; List<Device> originalDevices = [];
if (state != -2) { if (state != -2) {
body = originalDevices = await apiServices
await apiServices.getOwnerDeviceByState({"state": state.toString()}); .getOwnerDeviceByState({"state": state.toString()});
} else { } else {
body = await apiServices.getOwnerDevices(); originalDevices = await apiServices.getOwnerDevices();
} }
if (body.isNotEmpty) { List<Device> publicDevices = [];
final data = jsonDecode(body);
List<dynamic> items = data['items'];
List<Device> originalDevices = Device.fromJsonDynamicList(items);
for (var device in originalDevices) {
if (device.visibility == "PUBLIC") {
publicDevices.add(device);
}
}
devices = (state != -2) devices = (state != -2)
? DeviceUtils.instance.sortDeviceAZByName(originalDevices) ? DeviceUtils.instance.sortDeviceAZByName(publicDevices)
: DeviceUtils.instance.sortDeviceByState(originalDevices); : DeviceUtils.instance.sortDeviceByState(publicDevices);
if (state == -2) { if (state == -2) {
for (var device in originalDevices) { for (var device in publicDevices) {
String stateKey = _getStateKey(device.state!); String stateKey = _getStateKey(device.state!);
deviceByState[stateKey]!.add(device); deviceByState[stateKey]!.add(device);
} }
sinkDeviceByState.add(deviceByState); sinkDeviceByState.add(deviceByState);
} }
}
sinkAllDevices.add(devices); sinkAllDevices.add(devices);
} catch (e) {
if (!context.mounted) return;
showErrorTopSnackBarCustom(context, e.toString());
}
} }
String _getStateKey(int state) { String _getStateKey(int state) {

View File

@@ -1,15 +1,13 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../feature/devices/device_model.dart'; import '../feature/devices/device_model.dart';
import '../product/base/bloc/base_bloc.dart'; import '../product/base/bloc/base_bloc.dart';
import '../product/services/api_services.dart'; import '../product/services/api_services.dart';
import '../product/services/language_services.dart'; import '../product/services/language_services.dart';
import '../product/utils/response_status_utils.dart'; import '../product/utils/response_status_utils.dart';
import '../feature/inter_family/group_detail/group_detail_model.dart'; import '../feature/inter_family/group_detail/group_detail_model.dart';
class DetailGroupBloc extends BlocBase { class DetailGroupBloc extends BlocBase {
@@ -29,12 +27,10 @@ class DetailGroupBloc extends BlocBase {
@override @override
void dispose() {} void dispose() {}
Future<void> getGroupDetail(String groupID) async { Future<void> getGroupDetail(BuildContext context, String groupID) async {
final body = await apiServices.getGroupDetail(groupID); await apiServices.execute(context, () async {
final data = jsonDecode(body);
List<DeviceOfGroup> warningDevices = []; List<DeviceOfGroup> warningDevices = [];
if (data != null) { GroupDetail group = await apiServices.getGroupDetail(groupID);
GroupDetail group = GroupDetail.fromJson(data);
sinkDetailGroup.add(group); sinkDetailGroup.add(group);
if (group.devices != null) { if (group.devices != null) {
for (var device in group.devices!) { for (var device in group.devices!) {
@@ -44,43 +40,104 @@ class DetailGroupBloc extends BlocBase {
} }
sinkWarningDevice.add(warningDevices); 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, Future<void> approveUserToGroup(BuildContext context, String groupID,
String userID, String userName) async { String userID, String userName) async {
await apiServices.execute(context, () async {
Map<String, dynamic> body = {"group_id": groupID, "user_id": userID}; Map<String, dynamic> body = {"group_id": groupID, "user_id": userID};
int statusCode = await apiServices.approveGroup(body); int statusCode = await apiServices.approveGroup(body);
showSnackBarResponseByStatusCode(context, statusCode, showSnackBarResponseByStatusCode(context, statusCode,
"Đã duyệt $userName vào nhóm!", "Duyệt $userName thất bại!"); "Đã 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, Future<void> deleteOrUnapproveUser(BuildContext context, String groupID,
String userID, String userName) async { String userID, String userName) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteUserInGroup(groupID, userID); int statusCode = await apiServices.deleteUserInGroup(groupID, userID);
showSnackBarResponseByStatusCode(context, statusCode, showSnackBarResponseByStatusCode(context, statusCode,
"Đã xóa người dùng $userName", "Xóa người dùng $userName thất bại"); "Đã 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, Future<void> deleteDevice(BuildContext context, String groupID,
String thingID, String deviceName) async { String thingID, String deviceName) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteDeviceInGroup(groupID, thingID); int statusCode = await apiServices.deleteDeviceInGroup(groupID, thingID);
showSnackBarResponseByStatusCode(context, statusCode, showSnackBarResponseByStatusCode(context, statusCode,
"Đã xóa thiết bị $deviceName", "Xóa thiết bị $deviceName thất bại"); "Đã 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( Future<void> leaveGroup(
BuildContext context, String groupID, String userID) async { BuildContext context, String groupID, String userID) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteUserInGroup(groupID, userID); int statusCode = await apiServices.deleteUserInGroup(groupID, userID);
showSnackBarResponseByStatusCode( showSnackBarResponseByStatusCode(
context, context,
statusCode, statusCode,
appLocalization(context).notification_leave_group_success, appLocalization(context).notification_leave_group_success,
appLocalization(context).notification_leave_group_failed); 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( Future<void> updateDeviceNameInGroup(
BuildContext context, String thingID, String newAlias) async { BuildContext context, String thingID, String newAlias) async {
await apiServices.execute(context, () async {
Map<String, dynamic> body = {"thing_id": thingID, "alias": newAlias}; Map<String, dynamic> body = {"thing_id": thingID, "alias": newAlias};
int statusCode = await apiServices.updateDeviceAlias(body); int statusCode = await apiServices.updateDeviceAlias(body);
showSnackBarResponseByStatusCode( showSnackBarResponseByStatusCode(
@@ -89,17 +146,29 @@ class DetailGroupBloc extends BlocBase {
appLocalization(context).notification_update_device_success, appLocalization(context).notification_update_device_success,
appLocalization(context).notification_update_device_failed, 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 { Future<List<Device>> getOwnerDevices(BuildContext context) {
List<Device> allDevices = []; return apiServices.execute(context, () async {
String body = await apiServices.getOwnerDevices(); final originalDevices = await apiServices.getOwnerDevices();
if (body != "") { return originalDevices
final data = jsonDecode(body); .where((device) => device.visibility == "PUBLIC")
List<dynamic> items = data['items']; .toList();
allDevices = Device.fromJsonDynamicList(items); });
}
return allDevices;
} }
Future<void> addDeviceToGroup( Future<void> addDeviceToGroup(
@@ -107,6 +176,7 @@ class DetailGroupBloc extends BlocBase {
Map<String, dynamic> body = { Map<String, dynamic> body = {
"thing_id": thingID, "thing_id": thingID,
}; };
await apiServices.execute(context, () async {
int statusCode = await apiServices.addDeviceToGroup(groupID, body); int statusCode = await apiServices.addDeviceToGroup(groupID, body);
showSnackBarResponseByStatusCode( showSnackBarResponseByStatusCode(
context, context,
@@ -114,5 +184,18 @@ class DetailGroupBloc extends BlocBase {
appLocalization(context).notification_add_device_success, appLocalization(context).notification_add_device_success,
appLocalization(context).notification_add_device_failed, 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 '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 '../feature/home/device_alias_model.dart';
import '../product/base/bloc/base_bloc.dart'; import '../product/base/bloc/base_bloc.dart';
import '../product/services/language_services.dart';
import '../product/utils/device_utils.dart';
class HomeBloc extends BlocBase { class HomeBloc extends BlocBase {
APIServices apiServices = APIServices();
final allDevicesAliasMap = StreamController<Map<String,List<DeviceWithAlias>>>.broadcast(); final allDevicesAliasMap =
StreamSink<Map<String,List<DeviceWithAlias>>> get sinkAllDevicesAliasMap => StreamController<Map<String, List<DeviceWithAlias>>?>.broadcast();
StreamSink<Map<String, List<DeviceWithAlias>>?> get sinkAllDevicesAliasMap =>
allDevicesAliasMap.sink; allDevicesAliasMap.sink;
Stream<Map<String,List<DeviceWithAlias>>> get streamAllDevicesAliasMap => Stream<Map<String, List<DeviceWithAlias>>?> get streamAllDevicesAliasMap =>
allDevicesAliasMap.stream; allDevicesAliasMap.stream;
final allDevicesAliasJoinedMap = StreamController<Map<String,List<DeviceWithAlias>>>.broadcast(); // final allDevicesAliasJoinedMap =
StreamSink<Map<String,List<DeviceWithAlias>>> get sinkAllDevicesAliasJoinedMap => // StreamController<Map<String, List<DeviceWithAlias>>>.broadcast();
allDevicesAliasJoinedMap.sink; // StreamSink<Map<String, List<DeviceWithAlias>>>
Stream<Map<String,List<DeviceWithAlias>>> get streamAllDevicesAliasJoinedMap => // get sinkAllDevicesAliasJoinedMap => allDevicesAliasJoinedMap.sink;
allDevicesAliasJoinedMap.stream; // Stream<Map<String, List<DeviceWithAlias>>>
// get streamAllDevicesAliasJoinedMap => allDevicesAliasJoinedMap.stream;
final countNotification = StreamController<int>.broadcast(); final countNotification = StreamController<int>.broadcast();
StreamSink<int> get sinkCountNotification => countNotification.sink; StreamSink<int> get sinkCountNotification => countNotification.sink;
Stream<int> get streamCountNotification => countNotification.stream; 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 = final ownerDevicesStatus =
StreamController<Map<String, List<DeviceWithAlias>>>.broadcast(); StreamController<Map<String, List<DeviceWithAlias>>>.broadcast();
StreamSink<Map<String, List<DeviceWithAlias>>> StreamSink<Map<String, List<DeviceWithAlias>>> get sinkOwnerDevicesStatus =>
get sinkOwnerDevicesStatus => ownerDevicesStatus.sink; ownerDevicesStatus.sink;
Stream<Map<String, List<DeviceWithAlias>>> get streamOwnerDevicesStatus => Stream<Map<String, List<DeviceWithAlias>>> get streamOwnerDevicesStatus =>
ownerDevicesStatus.stream; 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 // ignore_for_file: use_build_context_synchronously
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart'; 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/services/api_services.dart';
import '../product/base/bloc/base_bloc.dart'; import '../product/base/bloc/base_bloc.dart';
import '../product/services/language_services.dart'; import '../product/services/language_services.dart';
@@ -35,43 +32,32 @@ class InterFamilyBloc extends BlocBase {
@override @override
void dispose() {} void dispose() {}
void getAllGroup(String role) async { void getAllGroup(BuildContext context, String role) async {
List<Group> groups = []; List<Group> groups = [];
sinkCurrentGroups.add(groups); sinkCurrentGroups.add(groups);
final body = await apiServices.getAllGroups(); await apiServices.execute(context, () async {
groups = await apiServices.getAllGroups();
if (body.isNotEmpty) {
final data = jsonDecode(body);
List<dynamic> items = data["items"];
groups = Group.fromJsonDynamicList(items);
groups = sortGroupByName(groups); groups = sortGroupByName(groups);
List<Group> currentGroups = groups.where( List<Group> currentGroups = groups.where(
(group) { (group) {
bool isPublic = group.visibility == "PUBLIC"; bool isPublic = group.visibility == "PUBLIC";
if (role == ApplicationConstants.OWNER_GROUP) { if (role == ApplicationConstants.OWNER_GROUP) {
return group.isOwner == true && isPublic; return group.isOwner == true && isPublic;
} }
if (role == ApplicationConstants.PARTICIPANT_GROUP) { if (role == ApplicationConstants.PARTICIPANT_GROUP) {
return group.isOwner == null && isPublic; return group.isOwner == null && isPublic;
} }
return false; return false;
}, },
).toList(); ).toList();
sinkCurrentGroups.add(currentGroups); sinkCurrentGroups.add(currentGroups);
} else { });
log("Get groups from API failed");
}
log("Inter Family Role: $role");
} }
Future<void> createGroup( Future<void> createGroup(
BuildContext context, String name, String description) async { BuildContext context, String name, String description) async {
APIServices apiServices = APIServices(); await apiServices.execute(context, () async {
Map<String, dynamic> body = {"name": name, "description": description}; Map<String, dynamic> body = {"name": name, "description": description};
int? statusCode = await apiServices.createGroup(body); int? statusCode = await apiServices.createGroup(body);
showSnackBarResponseByStatusCode( showSnackBarResponseByStatusCode(
@@ -79,10 +65,12 @@ class InterFamilyBloc extends BlocBase {
statusCode, statusCode,
appLocalization(context).notification_add_group_success, appLocalization(context).notification_add_group_success,
appLocalization(context).notification_add_group_failed); 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 { String name, String description) async {
await apiServices.execute(context, () async {
Map<String, dynamic> body = {"name": name, "description": description}; Map<String, dynamic> body = {"name": name, "description": description};
int statusCode = await apiServices.updateGroup(body, groupID); int statusCode = await apiServices.updateGroup(body, groupID);
showSnackBarResponseByStatusCode( showSnackBarResponseByStatusCode(
@@ -90,27 +78,32 @@ class InterFamilyBloc extends BlocBase {
statusCode, statusCode,
appLocalization(context).notification_update_group_success, appLocalization(context).notification_update_group_success,
appLocalization(context).notification_update_group_failed); appLocalization(context).notification_update_group_failed);
});
} }
Future<void> joinGroup(BuildContext context, String groupID) async { Future<void> joinGroup(BuildContext context, String groupID) async {
Map<String, dynamic> body = { Map<String, dynamic> body = {
"group_id": groupID, "group_id": groupID,
}; };
await apiServices.execute(context, () async {
int statusCode = await apiServices.joinGroup(groupID, body); int statusCode = await apiServices.joinGroup(groupID, body);
showSnackBarResponseByStatusCode( showSnackBarResponseByStatusCode(
context, context,
statusCode, statusCode,
appLocalization(context).notification_join_request_group_success, appLocalization(context).notification_join_request_group_success,
appLocalization(context).notification_join_request_group_failed); appLocalization(context).notification_join_request_group_failed);
});
} }
Future<void> deleteGroup(BuildContext context, String groupID) async { Future<void> deleteGroup(BuildContext context, String groupID) async {
await apiServices.execute(context, () async {
int statusCode = await apiServices.deleteGroup(groupID); int statusCode = await apiServices.deleteGroup(groupID);
showSnackBarResponseByStatusCode( showSnackBarResponseByStatusCode(
context, context,
statusCode, statusCode,
appLocalization(context).notification_delete_group_success, appLocalization(context).notification_delete_group_success,
appLocalization(context).notification_delete_group_failed); appLocalization(context).notification_delete_group_failed);
});
} }
List<Group> sortGroupByName(List<Group> groups) { List<Group> sortGroupByName(List<Group> groups) {

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../product/services/map_services.dart'; import '../product/services/map_services.dart';
import '../product/shared/shared_snack_bar.dart'; import '../product/shared/shared_snack_bar.dart';
import '../feature/devices/device_model.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); context, "Không tìm thấy đường", Colors.orange, Colors.white);
} }
} }
} }

View File

@@ -1,15 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/cupertino.dart';
import '../product/services/api_services.dart';
import '../feature/settings/profile/profile_model.dart'; import '../feature/settings/profile/profile_model.dart';
import '../product/base/bloc/base_bloc.dart'; import '../product/base/bloc/base_bloc.dart';
class SettingsBloc extends BlocBase { class SettingsBloc extends BlocBase {
// Settings Screen // Settings Screen
APIServices apiServices = APIServices();
final userProfile = StreamController<User>.broadcast(); final userProfile = StreamController<User>.broadcast();
StreamSink<User> get sinkUserProfile => userProfile.sink; StreamSink<User> get sinkUserProfile => userProfile.sink;
Stream<User> get streamUserProfile => userProfile.stream; Stream<User> get streamUserProfile => userProfile.stream;
// Profile Screen // Profile Screen
final isChangeProfileInfomation = StreamController<bool>.broadcast(); final isChangeProfileInfomation = StreamController<bool>.broadcast();
StreamSink<bool> get sinkIsChangeProfileInfomation => StreamSink<bool> get sinkIsChangeProfileInfomation =>
@@ -17,9 +19,13 @@ class SettingsBloc extends BlocBase {
Stream<bool> get streamIsChangeProfileInfomation => Stream<bool> get streamIsChangeProfileInfomation =>
isChangeProfileInfomation.stream; isChangeProfileInfomation.stream;
void getUserProfile(BuildContext context) async {
await apiServices.execute(context, () async {
User user = await apiServices.getUserDetail();
sinkUserProfile.add(user);
});
}
@override @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 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../product/extension/context_extension.dart';
import '../../product/services/language_services.dart'; import '../../product/services/language_services.dart';
import '../../bloc/bell_bloc.dart'; import '../../bloc/bell_bloc.dart';
import '../../product/base/bloc/base_bloc.dart'; import '../../product/base/bloc/base_bloc.dart';
import '../../product/services/api_services.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'; import 'bell_model.dart';
class BellScreen extends StatefulWidget { class BellScreen extends StatefulWidget {
@@ -56,11 +57,7 @@ class _BellScreenState extends State<BellScreen> {
initialData: items, initialData: items,
builder: (context, bellSnapshot) { builder: (context, bellSnapshot) {
return check return check
? Center( ? const SharedLoadingAnimation()
child: CircularProgressIndicator(
value: context.highValue,
),
)
: bellSnapshot.data?.isEmpty ?? true : bellSnapshot.data?.isEmpty ?? true
? Center( ? Center(
child: Text( child: Text(
@@ -78,16 +75,8 @@ class _BellScreenState extends State<BellScreen> {
if (index < bellSnapshot.data!.length) { if (index < bellSnapshot.data!.length) {
return GestureDetector( return GestureDetector(
onTap: () async { onTap: () async {
List<String> read = []; readNotification(
read.add(bellSnapshot.data![index].id!); bellSnapshot.data![index].id!);
int code = await apiServices
.updateStatusOfNotification(read);
if (code == 200) {
read.clear();
} else {
read.clear();
}
refresh();
}, },
child: Column( child: Column(
children: [ children: [
@@ -143,7 +132,7 @@ class _BellScreenState extends State<BellScreen> {
builder: (context, hasMoreSnapshot) { builder: (context, hasMoreSnapshot) {
return Center( return Center(
child: hasMoreSnapshot.data ?? hasMore child: hasMoreSnapshot.data ?? hasMore
? const CircularProgressIndicator() ? const SharedComponentLoadingAnimation()
: Text( : Text(
appLocalization(context) appLocalization(context)
.bell_read_all, .bell_read_all,
@@ -173,10 +162,20 @@ class _BellScreenState extends State<BellScreen> {
getBellNotification(offset); 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 { Future<void> getBellNotification(int offset) async {
apiServices.execute(context, () async {
bell = await apiServices.getBellNotifications( bell = await apiServices.getBellNotifications(
offset.toString(), (offset + 20).toString()); offset.toString(), (offset + 20).toString());
if (bell.items!.isEmpty) { if (bell.items!.isEmpty) {
hasMore = false; hasMore = false;
bellBloc.sinkHasMore.add(hasMore); bellBloc.sinkHasMore.add(hasMore);
@@ -187,6 +186,7 @@ class _BellScreenState extends State<BellScreen> {
} }
bellBloc.bellItems.add(items); bellBloc.bellItems.add(items);
check = false; check = false;
});
} }
String timeAgo(BuildContext context, DateTime dateTime) { String timeAgo(BuildContext context, DateTime dateTime) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.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 { class NotFoundScreen extends StatelessWidget {
const NotFoundScreen({super.key}); const NotFoundScreen({super.key});

View File

@@ -1,11 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:alarm/alarm.dart';
import 'package:flutter/material.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/alert_card.dart';
import 'shared/warning_card.dart'; import 'shared/warning_card.dart';
import '../../product/utils/device_utils.dart';
import 'device_alias_model.dart'; import 'device_alias_model.dart';
import 'shared/overview_card.dart'; import 'shared/overview_card.dart';
import '../settings/device_notification_settings/device_notification_settings_model.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 { class HomeScreen extends StatefulWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@override @override
State<HomeScreen> createState() => _HomeScreenState(); State<HomeScreen> createState() => _HomeScreenState();
} }
@@ -25,33 +25,54 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> {
late HomeBloc homeBloc; late HomeBloc homeBloc;
APIServices apiServices = APIServices(); APIServices apiServices = APIServices();
Map<String, List<DeviceWithAlias>> allDevicesAliasMap = {};
Map<String, List<DeviceWithAlias>> allDevicesAliasJoinedMap = {};
List<DeviceWithAlias> devices = [];
bool isFunctionCall = false; bool isFunctionCall = false;
Timer? getAllDevicesTimer; Timer? getAllDevicesTimer;
int notificationCount = 0;
Map<String, List<DeviceWithAlias>> ownerDevicesStatus = {};
List<String> ownerDevicesState = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
homeBloc = BlocProvider.of(context); homeBloc = BlocProvider.of(context);
getOwnerAndJoinedDevices();
const duration = Duration(seconds: 10); 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 @override
void dispose() { void dispose() {
getAllDevicesTimer?.cancel(); getAllDevicesTimer?.cancel();
homeBloc.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { 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( return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
loadAlarms();
},
child: const Icon(Icons.alarm),
),
body: Padding( body: Padding(
padding: context.paddingLow, padding: context.paddingLow,
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -67,10 +88,16 @@ class _HomeScreenState extends State<HomeScreen> {
StreamBuilder<int>( StreamBuilder<int>(
stream: homeBloc.streamCountNotification, stream: homeBloc.streamCountNotification,
builder: (context, countSnapshot) { builder: (context, countSnapshot) {
if (countSnapshot.data == null) {
homeBloc.getOwnerDeviceState(
context, aliasDevicesSnapshot.data ?? []);
return const Text("0");
} else {
return Text( return Text(
"(${countSnapshot.data ?? 0})", "(${countSnapshot.data ?? 0})",
style: context.titleMediumTextStyle, style: context.titleMediumTextStyle,
); );
}
}, },
) )
], ],
@@ -78,46 +105,109 @@ class _HomeScreenState extends State<HomeScreen> {
SizedBox( SizedBox(
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: StreamBuilder<Map<String, List<DeviceWithAlias>>>( child:
StreamBuilder<Map<String, List<DeviceWithAlias>>>(
stream: homeBloc.streamOwnerDevicesStatus, stream: homeBloc.streamOwnerDevicesStatus,
builder: (context, snapshot) { builder: (context, ownerDevicesStatusSnapshot) {
if (snapshot.data?['state'] != null || snapshot.data?['battery'] != null) { if (ownerDevicesStatusSnapshot.data == null) {
return ConstrainedBox( homeBloc.getOwnerDeviceState(
constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width), 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( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment:
MainAxisAlignment.start,
children: [ children: [
if (snapshot.data?['state'] != null) if (ownerDevicesStatusSnapshot
...snapshot.data!['state']! .data?['state'] !=
null)
...ownerDevicesStatusSnapshot
.data!['state']!
.map( .map(
(item) => SizedBox( (item) => SizedBox(
width: context.dynamicWidth(0.95), width: context
child: FutureBuilder<Widget>( .dynamicWidth(0.95),
future: warningCard(context, apiServices, item), child: FutureBuilder<
builder: (context, warningCardSnapshot) { Widget>(
if (warningCardSnapshot.hasData) { future: warningCard(
return warningCardSnapshot.data!; context,
apiServices,
item),
builder: (context,
warningCardSnapshot) {
if (warningCardSnapshot
.hasData) {
return warningCardSnapshot
.data!;
} else { } else {
return const SizedBox.shrink(); return const SizedBox
.shrink();
} }
}, },
), ),
), ),
) )
.toList(), .toList(),
if (snapshot.data?['battery'] != null) if (ownerDevicesStatusSnapshot
...snapshot.data!['battery']! .data?['battery'] !=
null)
...ownerDevicesStatusSnapshot
.data!['battery']!
.map( .map(
(batteryItem) => SizedBox( (batteryItem) => SizedBox(
width: context.dynamicWidth(0.95), width: context
child: FutureBuilder<Widget>( .dynamicWidth(0.95),
future: notificationCard( child: FutureBuilder<
context, "lowBattery", appLocalization(context).low_battery_message, batteryItem), Widget>(
builder: (context, warningCardSnapshot) { future:
if (warningCardSnapshot.hasData) { notificationCard(
return warningCardSnapshot.data!; context,
"lowBattery",
appLocalization(
context)
.low_battery_message,
batteryItem,
),
builder: (context,
warningCardSnapshot) {
if (warningCardSnapshot
.hasData) {
return warningCardSnapshot
.data!;
} else { } else {
return const SizedBox.shrink(); return const SizedBox
.shrink();
} }
}, },
), ),
@@ -126,48 +216,49 @@ class _HomeScreenState extends State<HomeScreen> {
.toList(), .toList(),
], ],
), ),
); )
} else { : Padding(
return Padding( key: const ValueKey('no_data'),
padding: context.paddingMedium, padding: context.paddingMedium,
child: Center( child: Center(
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon( const Icon(
Icons.check_circle_outline_rounded, Icons
.check_circle_outline_rounded,
size: 40, size: 40,
color: Colors.green, color: Colors.green,
), ),
SizedBox(width: context.lowValue), SizedBox(
width: context.lowValue),
Text( Text(
appLocalization(context).notification_description, appLocalization(context)
.notification_description,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow:
TextOverflow.ellipsis,
softWrap: true, softWrap: true,
textAlign: TextAlign.start, textAlign: TextAlign.start,
), ),
], ],
), ),
), ),
),
); );
} }
}, },
), ),
), ),
), ),
StreamBuilder<Map<String, List<DeviceWithAlias>>?>(
StreamBuilder<Map<String, List<DeviceWithAlias>>>(
stream: homeBloc.streamAllDevicesAliasMap, stream: homeBloc.streamAllDevicesAliasMap,
builder: (context, allDevicesAliasMapSnapshot) { builder: (context, allDevicesAliasMapSnapshot) {
if (!allDevicesAliasMapSnapshot.hasData || if (allDevicesAliasMapSnapshot.data == null) {
allDevicesAliasMapSnapshot.data == null) { homeBloc.getDeviceStatusAliasMap(
return const Center(child: CircularProgressIndicator()); aliasDevicesSnapshot.data ?? []);
} return const SharedComponentLoadingAnimation();
} else {
final data = allDevicesAliasMapSnapshot.data!; final data = allDevicesAliasMapSnapshot.data!;
return OverviewCard( return OverviewCard(
isOwner: true, isOwner: true,
@@ -175,34 +266,10 @@ class _HomeScreenState extends State<HomeScreen> {
active: data['online']?.length ?? 0, active: data['online']?.length ?? 0,
inactive: data['offline']?.length ?? 0, inactive: data['offline']?.length ?? 0,
warning: data['warn']?.length ?? 0, warning: data['warn']?.length ?? 0,
unused: data['not-use']?.length ?? 0); unused: data['not-use']?.length ?? 0,
}), showUnused: false,
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,
); );
}
}, },
), ),
], ],
@@ -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 { void checkSettingDevice(List<DeviceWithAlias> devices) async {
if (isFunctionCall) { if (isFunctionCall) {
log("Ham check setting da duoc goi"); log("Ham check setting da duoc goi");
} else { } else {
String? response = await apiServices.getAllSettingsNotificationOfDevices(); await apiServices.execute(context, () async {
if (response != "") {
final data = jsonDecode(response);
final result = data['data'];
// log("Data ${DeviceNotificationSettings.mapFromJson(jsonDecode(data)).values.toList()}");
List<DeviceNotificationSettings> list = List<DeviceNotificationSettings> list =
DeviceNotificationSettings.mapFromJson(result).values.toList(); await apiServices.getAllSettingsNotificationOfDevices();
// log("List: $list"); // 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) { for (var device in devices) {
if (!thingIdsInList.contains(device.thingId)) { if (!thingIdsInList.contains(device.thingId)) {
log("Device with Thing ID ${device.thingId} is not in the notification settings list."); 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 { } else {
log("All devices are in the notification settings list."); log("All devices are in the notification settings list.");
} }
} }
} else { });
log("apiServices: getAllSettingsNotificationofDevices error!");
}
} }
isFunctionCall = true; isFunctionCall = true;
} }

View File

@@ -3,24 +3,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:intl/intl.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/enums/app_route_enums.dart';
import '../../../product/constant/image/image_constants.dart'; import '../../../product/constant/image/image_constants.dart';
import '../../../product/extension/context_extension.dart'; import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart'; import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart'; import '../../../product/utils/device_utils.dart';
import '../../../product/constant/icon/icon_constants.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 notificationTitle, DeviceWithAlias device) async {
String location = ""; String location = "";
if (device.areaPath != "") { if (device.areaPath != "") {
location = await DeviceUtils.instance location = await DeviceUtils.instance
.getFullDeviceLocation(context, device.areaPath!); .getFullDeviceLocation(context, device.areaPath!, "");
} }
String path = ""; String path = "";
// DateTime time = DateTime.now();
String time = ""; String time = "";
for (var sensor in device.status!.sensors!) { for (var sensor in device.status!.sensors!) {
if (sensor.name! == "7") { 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); time = DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
} }
} }
if (notiticationType == "lowBattery") { if (notificationType == "lowBattery") {
path = ImageConstants.instance.getImage("low_battery"); path = ImageConstants.instance.getImage("low_battery");
} }
return Card( return Card(
@@ -62,10 +62,7 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
SizedBox( SizedBox(
child: Text( child: Text(
"${appLocalization(context).device_title} ${device.isOwner! ? device.name : device.alias}", "${appLocalization(context).device_title} ${device.isOwner! ? device.name : device.alias}",
style: const TextStyle( style: context.responsiveBodyLargeWithBold,
fontWeight: FontWeight.bold,
fontSize: 18,
),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: true, softWrap: true,
@@ -92,7 +89,7 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
Expanded( Expanded(
child: Text( child: Text(
location, location,
style: const TextStyle(fontSize: 15), style: context.responsiveBodySmall,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: true, softWrap: true,
@@ -111,7 +108,7 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
Expanded( Expanded(
child: Text( child: Text(
time.toString(), time.toString(),
style: const TextStyle(fontSize: 15), style: context.responsiveBodySmall,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: true, softWrap: true,
@@ -120,11 +117,16 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
), ),
], ],
), ),
Align( SizedBox(
height: context.lowValue,
),
device.isOwner!
? Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: OutlinedButton( child: OutlinedButton(
style: const ButtonStyle( style: const ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.blueAccent), backgroundColor:
WidgetStatePropertyAll(Colors.blueAccent),
), ),
onPressed: () { onPressed: () {
context.pushNamed(AppRoutes.DEVICE_DETAIL.name, context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
@@ -132,11 +134,28 @@ Future<Widget> notificationCard(BuildContext context, String notiticationType,
}, },
child: Text( child: Text(
appLocalization(context).detail_message, appLocalization(context).detail_message,
style: const TextStyle( style: const TextStyle(color: Colors.white),
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 'package:flutter/material.dart';
import 'status_card.dart'; import 'status_card.dart';
import '../../../product/extension/context_extension.dart'; import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart'; import '../../../product/services/language_services.dart';
class OverviewCard extends StatelessWidget { class OverviewCard extends StatefulWidget {
final bool isOwner; final bool isOwner;
final int total; final int total;
final int active; final int active;
final int inactive; final int inactive;
final int warning; final int warning;
final int unused; final int unused;
final bool showTotal;
final bool showActive;
final bool showInactive;
final bool showWarning;
final bool showUnused;
const OverviewCard( const OverviewCard({
{super.key, super.key,
required this.isOwner, required this.isOwner,
required this.total, required this.total,
required this.active, required this.active,
required this.inactive, required this.inactive,
required this.warning, 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return FittedBox(
elevation: 8, alignment: Alignment.topCenter,
child: SizedBox(
width: context.width,
child: Card(
// elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
child: Padding( child: Padding(
padding: context.paddingNormal, padding: context.paddingNormal,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
isOwner widget.isOwner
? appLocalization(context).overview_message ? appLocalization(context).overview_message
: appLocalization(context).interfamily_page_name, : appLocalization(context).interfamily_page_name,
style: const TextStyle( style: context.h2,
fontSize: 20,
fontWeight: FontWeight.bold,
),
), ),
SizedBox(height: context.normalValue), SizedBox(height: context.normalValue),
Column( Column(
children: [ children: [
if (widget.showTotal)
StatusCard( StatusCard(
label: appLocalization(context).total_nof_devices_message, label: appLocalization(context).total_nof_devices_message,
count: total, count: widget.total,
color: Colors.blue, color: Colors.blue,
), ),
if (widget.showActive)
StatusCard( StatusCard(
label: appLocalization(context).active_devices_message, label: appLocalization(context).active_devices_message,
count: active, count: widget.active,
color: Colors.green, color: Colors.green,
), ),
if (widget.showInactive)
StatusCard( StatusCard(
label: appLocalization(context).inactive_devices_message, label: appLocalization(context).inactive_devices_message,
count: inactive, count: widget.inactive,
color: Colors.grey, color: Colors.grey,
), ),
if (widget.showWarning)
StatusCard( StatusCard(
label: appLocalization(context).warning_devices_message, label: appLocalization(context).warning_devices_message,
count: warning, count: widget.warning,
color: Colors.orange, color: Colors.orange,
), ),
if (widget.showUnused)
StatusCard( StatusCard(
label: appLocalization(context).unused_devices_message, label: appLocalization(context).unused_devices_message,
count: unused, count: widget.unused,
color: Colors.yellow, color: Colors.yellow,
), ),
], ],
@@ -71,6 +95,8 @@ class OverviewCard extends StatelessWidget {
], ],
), ),
), ),
),
),
); );
} }
} }

View File

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

View File

@@ -3,7 +3,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:maps_launcher/maps_launcher.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 '../device_alias_model.dart';
import '../../../product/constant/icon/icon_constants.dart'; import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/constant/image/image_constants.dart'; import '../../../product/constant/image/image_constants.dart';
@@ -21,7 +22,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
String fullLocation = ""; String fullLocation = "";
if (device.areaPath != "") { if (device.areaPath != "") {
fullLocation = await DeviceUtils.instance fullLocation = await DeviceUtils.instance
.getFullDeviceLocation(context, device.areaPath!); .getFullDeviceLocation(context, device.areaPath!, "");
} }
String time = ""; String time = "";
for (var sensor in device.status!.sensors!) { for (var sensor in device.status!.sensors!) {
@@ -33,7 +34,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
} }
if (device.state! == 3) { if (device.state! == 3) {
backgroundColor = Colors.grey; backgroundColor = Colors.grey;
textColor = Colors.black; textColor = Colors.white;
message = appLocalization(context).in_progress_message; message = appLocalization(context).in_progress_message;
} else if (device.state! == 2) { } else if (device.state! == 2) {
backgroundColor = const Color.fromARGB(255, 6, 138, 72); backgroundColor = const Color.fromARGB(255, 6, 138, 72);
@@ -42,32 +43,18 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
} else if (device.state! == 1) { } else if (device.state! == 1) {
backgroundColor = const Color.fromARGB(255, 250, 63, 63); backgroundColor = const Color.fromARGB(255, 250, 63, 63);
textColor = Colors.white; textColor = Colors.white;
if(device.isOwner == true){
message = appLocalization(context).button_fake_fire_message; message = appLocalization(context).button_fake_fire_message;
}else{
message = appLocalization(context).smoke_detecting_message_lowercase;
}
} else { } else {
backgroundColor = Colors.black; backgroundColor = Colors.black;
textColor = Colors.white; textColor = Colors.white;
message = appLocalization(context).disconnect_message_uppercase; message = appLocalization(context).disconnect_message_uppercase;
} }
return badges.Badge( return Card(
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(
child: Padding( child: Padding(
padding: context.paddingLow, padding: context.paddingLow,
child: Column( child: Column(
@@ -97,10 +84,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
SizedBox( SizedBox(
child: Text( child: Text(
"${appLocalization(context).device_title}: ${device.isOwner! ? device.name : device.alias}", "${appLocalization(context).device_title}: ${device.isOwner! ? device.name : device.alias}",
style: const TextStyle( style: context.responsiveBodyLargeWithBold,
fontWeight: FontWeight.bold,
fontSize: 18,
),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: true, softWrap: true,
@@ -128,7 +112,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
Expanded( Expanded(
child: Text( child: Text(
fullLocation, fullLocation,
style: const TextStyle(fontSize: 15), style: context.responsiveBodySmall,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: true, softWrap: true,
@@ -147,7 +131,7 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
Expanded( Expanded(
child: Text( child: Text(
time, time,
style: const TextStyle(fontSize: 15), style: context.responsiveBodySmall,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
softWrap: true, softWrap: true,
@@ -159,14 +143,16 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
SizedBox( SizedBox(
height: context.lowValue, height: context.lowValue,
), ),
Row( device.isOwner == true
? Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
IconButton.outlined( IconButton.outlined(
onPressed: () async => {}, onPressed: () async => {},
// displayListOfFireStationPhoneNumbers(testDevice), // displayListOfFireStationPhoneNumbers(testDevice),
icon: IconConstants.instance.getMaterialIcon(Icons.call), icon:
IconConstants.instance.getMaterialIcon(Icons.call),
iconSize: 25, iconSize: 25,
style: ButtonStyle( style: ButtonStyle(
backgroundColor: backgroundColor:
@@ -199,7 +185,8 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
WidgetStatePropertyAll(backgroundColor)), WidgetStatePropertyAll(backgroundColor)),
onPressed: () async { onPressed: () async {
if (message == if (message ==
appLocalization(context).button_fake_fire_message) { appLocalization(context)
.button_fake_fire_message) {
await showDialog( await showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
@@ -212,8 +199,11 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await apiServices.execute(context,
() async {
int statusCode = await apiServices int statusCode = await apiServices
.confirmFakeFireByUser(device.thingId!); .confirmFakeFireByUser(
device.thingId!);
if (statusCode == 200) { if (statusCode == 200) {
showNoIconTopSnackBar( showNoIconTopSnackBar(
context, context,
@@ -229,13 +219,15 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
Colors.red, Colors.red,
Colors.red); Colors.red);
} }
});
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
appLocalization(context) appLocalization(context)
.confirm_fake_fire_sure_message, .confirm_fake_fire_sure_message,
style: style: const TextStyle(
const TextStyle(color: Colors.red)), color: Colors.red)),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
@@ -250,7 +242,8 @@ Future<Widget> warningCard(BuildContext context, APIServices apiServices,
} else { } else {
showNoIconTopSnackBar( showNoIconTopSnackBar(
context, context,
appLocalization(context).let_PCCC_handle_message, appLocalization(context)
.let_PCCC_handle_message,
Colors.orange, Colors.orange,
Colors.white); 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:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../product/extension/context_extension.dart';
import '../../../bloc/group_detail_bloc.dart'; import '../../../bloc/group_detail_bloc.dart';
import '../../../product/constant/icon/icon_constants.dart'; import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/services/language_services.dart'; import '../../../product/services/language_services.dart';
import '../../devices/device_model.dart'; import '../../devices/device_model.dart';
import 'group_detail_model.dart'; import 'group_detail_model.dart';
addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc, addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
String groupID, List<DeviceOfGroup> devices) async { String groupID, List<DeviceOfGroup> devices) async {
List<Device> ownerDevices = await detailGroupBloc.getOwnerDevices(); List<Device> ownerDevices = await detailGroupBloc.getOwnerDevices(context);
List<String> selectedItems = []; List<String> selectedItems = [];
List<String> selectedDevices = []; List<String> selectedDevices = [];
if (devices.isNotEmpty) { if (devices.isNotEmpty) {
@@ -36,7 +37,7 @@ addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
), ),
hint: Text( hint: Text(
appLocalization(context).choose_device_dropdownButton, appLocalization(context).choose_device_dropdownButton,
style: const TextStyle(fontSize: 14), style: context.responsiveBodySmall,
), ),
items: ownerDevices items: ownerDevices
.map( .map(
@@ -73,7 +74,7 @@ addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
Expanded( Expanded(
child: Text( child: Text(
item.name!, item.name!,
style: const TextStyle(fontSize: 14), style: context.responsiveBodySmall,
), ),
), ),
], ],
@@ -130,7 +131,7 @@ addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc,
for (var device in selectedItems) { for (var device in selectedItems) {
await detailGroupBloc.addDeviceToGroup( await detailGroupBloc.addDeviceToGroup(
context, groupID, device); context, groupID, device);
await detailGroupBloc.getGroupDetail(groupID); await detailGroupBloc.getGroupDetail(context,groupID);
} }
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'groups/groups_screen.dart'; import 'groups/groups_screen.dart';
import 'groups/groups_model.dart';
import '../../bloc/inter_family_bloc.dart'; import '../../bloc/inter_family_bloc.dart';
import 'inter_family_widget.dart'; import 'inter_family_widget.dart';
import '../../product/base/bloc/base_bloc.dart'; import '../../product/base/bloc/base_bloc.dart';
@@ -25,20 +26,29 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
void initState() { void initState() {
super.initState(); super.initState();
interFamilyBloc = BlocProvider.of(context); 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>[ List<Group> ownerGroups = [];
BlocProvider( List<Group> participantGroups = [];
blocBuilder: () => InterFamilyBloc(),
child: const GroupsScreen( List<Widget> get _widgetOptions => [
GroupsScreen(
role: ApplicationConstants.OWNER_GROUP, role: ApplicationConstants.OWNER_GROUP,
groups: ownerGroups,
interFamilyBloc: interFamilyBloc,
), ),
), GroupsScreen(
BlocProvider(
blocBuilder: () => InterFamilyBloc(),
child: const GroupsScreen(
role: ApplicationConstants.PARTICIPANT_GROUP, role: ApplicationConstants.PARTICIPANT_GROUP,
), groups: participantGroups,
interFamilyBloc: interFamilyBloc,
), ),
]; ];
@@ -49,6 +59,20 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
stream: interFamilyBloc.streamSelectedScreen, stream: interFamilyBloc.streamSelectedScreen,
initialData: _selectedIndex, initialData: _selectedIndex,
builder: (context, selectSnapshot) { 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( return Scaffold(
appBar: AppBar( appBar: AppBar(
actions: [ actions: [
@@ -58,35 +82,32 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
createOrJoinGroupDialog( createOrJoinGroupDialog(
context, context,
interFamilyBloc, interFamilyBloc,
selectSnapshot.data! == 0 ApplicationConstants.OWNER_GROUP,
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP,
appLocalization(context).add_new_group, appLocalization(context).add_new_group,
appLocalization(context).group_name_title, appLocalization(context).group_name_title,
"", "",
false, false,
"", "",
"", "",
""); "",
);
} else { } else {
createOrJoinGroupDialog( createOrJoinGroupDialog(
context, context,
interFamilyBloc, interFamilyBloc,
selectSnapshot.data! == 0 ApplicationConstants.PARTICIPANT_GROUP,
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP,
appLocalization(context).join_group, appLocalization(context).join_group,
appLocalization(context).group_id_title, appLocalization(context).group_id_title,
'', '',
true, true,
"", "",
appLocalization(context).group_name_title, appLocalization(context).group_name_title,
""); "",
);
} }
}, },
style: ElevatedButton.styleFrom( style:
shape: const CircleBorder(), ElevatedButton.styleFrom(shape: const CircleBorder()),
),
child: IconConstants.instance.getMaterialIcon(Icons.add), child: IconConstants.instance.getMaterialIcon(Icons.add),
), ),
], ],
@@ -111,7 +132,7 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
), ),
), ),
drawer: Drawer( drawer: Drawer(
width: context.dynamicWidth(0.4), width: context.dynamicWidth(0.6),
child: ListView( child: ListView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
children: [ children: [
@@ -142,6 +163,8 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
); );
}, },
); );
},
);
} }
void checkTitle(int index) { void checkTitle(int index) {
@@ -159,5 +182,11 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
interFamilyBloc.sinkSelectedScreen.add(_selectedIndex); interFamilyBloc.sinkSelectedScreen.add(_selectedIndex);
isLoading = false; isLoading = false;
interFamilyBloc.sinkIsLoading.add(isLoading); 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 { try {
await interFamilyBloc.createGroup( await interFamilyBloc.createGroup(
context, groupName, description); context, groupName, description);
interFamilyBloc.getAllGroup(role); interFamilyBloc.getAllGroup(context, role);
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
} catch (e) { } catch (e) {
// log("Lỗi khi tạo nhóm: $e"); // log("Lỗi khi tạo nhóm: $e");
@@ -131,9 +131,9 @@ createOrJoinGroupDialog(
appLocalization(context) appLocalization(context)
.change_group_infomation_content) { .change_group_infomation_content) {
try { try {
await interFamilyBloc.changeGroupInfomation( await interFamilyBloc.changeGroupInformation(
context, groupID, groupName, description); context, groupID, groupName, description);
interFamilyBloc.getAllGroup(role); interFamilyBloc.getAllGroup(context, role);
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
} catch (e) { } catch (e) {
// log("Lỗi khi sửa nhóm: $e"); // log("Lỗi khi sửa nhóm: $e");

View File

@@ -1,24 +1,22 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:developer'; import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:badges/badges.dart' as badges; import 'package:badges/badges.dart' as badges;
import 'package:persistent_bottom_nav_bar/persistent_bottom_nav_bar.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:sfm_app/product/utils/permission_handler.dart';
import '../../product/permission/notification_permission.dart'; import '../../product/shared/shared_snack_bar.dart';
import '../../product/services/notification_services.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 '../settings/profile/profile_model.dart';
import '../../product/extension/context_extension.dart'; import '../../product/extension/context_extension.dart';
import '../../bloc/home_bloc.dart'; import '../../bloc/home_bloc.dart';
import '../../product/constant/app/app_constants.dart'; import '../../product/constant/app/app_constants.dart';
import '../../product/constant/enums/app_route_enums.dart'; import '../../product/constant/enums/app_route_enums.dart';
import '../../product/permission/location_permission.dart';
import '../../product/services/theme_services.dart'; import '../../product/services/theme_services.dart';
import '../../bloc/devices_manager_bloc.dart'; import '../../bloc/devices_manager_bloc.dart';
import '../devices/devices_manager_screen.dart'; import '../devices/devices_manager_screen.dart';
@@ -46,22 +44,10 @@ class MainScreen extends StatefulWidget {
State<MainScreen> createState() => _MainScreenState(); 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 { class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
APIServices apiServices = APIServices(); APIServices apiServices = APIServices();
// final NotificationServices notificationServices = NotificationServices(); final NotificationServices notificationServices = NotificationServices();
late MainBloc mainBloc; late MainBloc mainBloc;
bool isVN = true; bool isVN = true;
bool isLight = true; bool isLight = true;
@@ -89,7 +75,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
mainBloc.sinkIsVNIcon.add(isVN); mainBloc.sinkIsVNIcon.add(isVN);
mainBloc.sinkThemeMode.add(isLight); mainBloc.sinkThemeMode.add(isLight);
checkAndRequestPermission(); 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 @override
@@ -100,7 +91,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
initialCheck(); initialCheck();
getBellNotification(); getBellNotification();
mainBloc.getUserProfile(); mainBloc.getUserProfile(context);
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) { FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
log("New FCM Token: $newToken"); log("New FCM Token: $newToken");
@@ -108,129 +99,86 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
mainBloc.sendNotificationToken(newToken); 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.initLocalNotifications(controller);
// notificationServices.firebaseInit(context); // notificationServices.firebaseInit(context);
// NotificationServices().setupInteractMessage(controller); // 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 @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
} }
@override
List<PersistentBottomNavBarItem> _navBarsItems() { Widget build(BuildContext context) {
return [ List<PersistentTabConfig> tabs = [
PersistentBottomNavBarItem( PersistentTabConfig(
icon: IconConstants.instance.getMaterialIcon(Icons.home), screen: BlocProvider(
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(
child: const HomeScreen(), child: const HomeScreen(),
blocBuilder: () => HomeBloc(), 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(), child: const DevicesManagerScreen(),
blocBuilder: () => DevicesManagerBloc()), blocBuilder: () => DevicesManagerBloc(),
BlocProvider( ),
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(), child: const MapScreen(),
blocBuilder: () => MapBloc(), 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(), child: const DeviceLogsScreen(),
blocBuilder: () => DeviceLogsBloc(), 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(), child: const InterFamilyScreen(),
blocBuilder: () => InterFamilyBloc(), 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>( return StreamBuilder<bool>(
stream: mainBloc.streamThemeMode, stream: mainBloc.streamThemeMode,
initialData: isLight, initialData: isLight,
@@ -379,41 +327,38 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
], ],
), ),
body: PersistentTabView( body: PersistentTabView(
context, stateManagement: false,
controller: controller, controller: controller,
screens: _buildScreens(), tabs: tabs,
items: _navBarsItems(), navBarBuilder: (navBarConfig) => Style6BottomNavBar(
handleAndroidBackButtonPress: true, navBarConfig: navBarConfig,
resizeToAvoidBottomInset: true, navBarDecoration: NavBarDecoration(
stateManagement: true, color: themeModeSnapshot.data! ? Colors.white : Colors.black,
borderRadius: BorderRadius.circular(context.mediumValue),
padding: const EdgeInsets.all(10)),
),
backgroundColor: backgroundColor:
themeModeSnapshot.data! ? Colors.white : Colors.black, themeModeSnapshot.data! ? Colors.white : Colors.black,
decoration: NavBarDecoration( navBarOverlap: const NavBarOverlap.none(),
borderRadius: BorderRadius.circular(30.0), // margin: EdgeInsets.only(
colorBehindNavBar: // left: context.lowValue,
themeModeSnapshot.data! ? Colors.white : Colors.black, // bottom: context.dynamicHeight(0.02),
), // right: context.lowValue),
animationSettings: const NavBarAnimationSettings( screenTransitionAnimation: const ScreenTransitionAnimation(
navBarItemAnimation: ItemAnimationSettings(
duration: Duration(milliseconds: 200),
curve: Curves.bounceInOut,
),
screenTransitionAnimation: ScreenTransitionAnimationSettings(
animateTabTransition: true,
curve: Curves.bounceInOut, curve: Curves.bounceInOut,
duration: Duration(milliseconds: 200), duration: Duration(milliseconds: 200),
), ),
), ),
navBarStyle: NavBarStyle.style13,
),
); );
}, },
); );
} }
Future<void> getBellNotification() async { Future<void> getBellNotification() async {
await apiServices.execute(context, () async {
bell = await apiServices.getBellNotifications("0", "20"); bell = await apiServices.getBellNotifications("0", "20");
mainBloc.bellBloc.add(bell); mainBloc.bellBloc.add(bell);
});
} }
bool checkStatus(List<BellItems> bells) { bool checkStatus(List<BellItems> bells) {

View File

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

View File

@@ -1,12 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:maps_launcher/maps_launcher.dart'; import 'package:maps_launcher/maps_launcher.dart';
import '../../../product/constant/icon/icon_constants.dart'; import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/extension/context_extension.dart'; import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart'; import '../../../product/services/language_services.dart';
import '../../../bloc/map_bloc.dart'; import '../../../bloc/map_bloc.dart';
showDirections( showDirections(
@@ -16,16 +15,14 @@ showDirections(
MapBloc mapBloc, MapBloc mapBloc,
String originalName, String originalName,
String destinationLocation, String destinationLocation,
double devicelat, double deviceLat,
double devicelng, double deviceLng,
) { ) {
TextEditingController originController = TextEditingController originController = TextEditingController(text: originalName);
TextEditingController(text: originalName); TextEditingController destinationController = TextEditingController(text: destinationLocation);
TextEditingController destinationController =
TextEditingController(text: destinationLocation);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
backgroundColor: Colors.transparent, backgroundColor: Theme.of(context).colorScheme.surface,
// dismissDirection: DismissDirection.none, // dismissDirection: DismissDirection.none,
duration: const Duration(minutes: 5), duration: const Duration(minutes: 5),
content: Column( content: Column(
@@ -38,21 +35,20 @@ showDirections(
children: [ children: [
Text( Text(
appLocalization(context).map_show_direction, appLocalization(context).map_show_direction,
style: context.titleLargeTextStyle, style: context.responsiveBodyLargeWithBold,
), ),
Container( Container(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: IconButton.outlined( child: IconButton.outlined(
onPressed: () async { onPressed: () async {
mapBloc.sinkPolylines.add([]);
markers.clear();
await mapBloc.updateCameraPosition( await mapBloc.updateCameraPosition(
controller, controller,
devicelat, deviceLat,
devicelng, deviceLng,
13.0, 13.0,
); );
List<LatLng> polylineCoordinates = [];
mapBloc.sinkPolylines.add(polylineCoordinates);
markers.clear();
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
} }
@@ -103,17 +99,12 @@ showDirections(
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
List<LatLng> polylineCoordinates = []; List<LatLng> polylineCoordinates = [];
mapBloc.sinkPolylines.add(polylineCoordinates); mapBloc.sinkPolylines.add(polylineCoordinates);
MapsLauncher.launchCoordinates(devicelat, devicelng); MapsLauncher.launchCoordinates(deviceLat, deviceLng);
}, },
icon: IconConstants.instance icon: IconConstants.instance.getMaterialIcon(Icons.near_me_rounded),
.getMaterialIcon(Icons.near_me_rounded),
label: Text( label: Text(
appLocalization(context).map_stream, 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 // ignore_for_file: use_build_context_synchronously
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../bloc/map_bloc.dart'; import '../../../bloc/map_bloc.dart';
import 'show_direction_widget.dart'; import 'show_direction_widget.dart';
import '../../../product/constant/icon/icon_constants.dart'; import '../../../product/constant/icon/icon_constants.dart';
@@ -52,7 +52,6 @@ showNearPlacesSideSheet(
padding: context.paddingLow, padding: context.paddingLow,
width: screenWidth, width: screenWidth,
height: screenHeight / 3, height: screenHeight / 3,
color: Colors.white,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -62,10 +61,7 @@ showNearPlacesSideSheet(
Center( Center(
child: Text( child: Text(
'${appLocalization(modalBottomSheetContext).map_result}: ', '${appLocalization(modalBottomSheetContext).map_result}: ',
style: const TextStyle( style: context.h3
fontSize: 20,
fontWeight: FontWeight.bold,
),
), ),
), ),
Container( Container(
@@ -119,10 +115,7 @@ showNearPlacesSideSheet(
children: [ children: [
Text( Text(
place.result!.name!, place.result!.name!,
style: const TextStyle( style: context.responsiveBodyMediumWithBold,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
SizedBox(height: listViewContext.lowValue), SizedBox(height: listViewContext.lowValue),
Text( Text(

View File

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

View File

@@ -1,6 +1,7 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../product/shared/shared_snack_bar.dart'; import '../../../product/shared/shared_snack_bar.dart';
import '../../../product/constant/icon/icon_constants.dart'; import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/services/api_services.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/shared/shared_input_decoration.dart';
import '../../../product/extension/context_extension.dart'; import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart'; import '../../../product/services/language_services.dart';
import 'profile_model.dart'; import 'profile_model.dart';
changeUserInfomation( changeUserInfomation(
@@ -38,6 +38,7 @@ changeUserInfomation(
? IconButton( ? IconButton(
onPressed: () async { onPressed: () async {
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
await apiServices.execute(context,() async {
formKey.currentState!.save(); formKey.currentState!.save();
String latitude = user.latitude ?? ""; String latitude = user.latitude ?? "";
String longitude = user.longitude ?? ""; String longitude = user.longitude ?? "";
@@ -66,7 +67,44 @@ changeUserInfomation(
Colors.redAccent, Colors.redAccent,
Colors.white); Colors.white);
} }
settingsBloc.getUserProfile(context);
Navigator.pop(modalBottomSheetContext); 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: icon:
@@ -205,6 +243,7 @@ changeUserInfomation(
child: TextButton( child: TextButton(
onPressed: () async { onPressed: () async {
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
await apiServices.execute(context,() async {
formKey.currentState!.save(); formKey.currentState!.save();
String latitude = user.latitude ?? ""; String latitude = user.latitude ?? "";
String longitude = user.longitude ?? ""; String longitude = user.longitude ?? "";
@@ -233,7 +272,9 @@ changeUserInfomation(
Colors.redAccent, Colors.redAccent,
Colors.white); Colors.white);
} }
settingsBloc.getUserProfile(context);
Navigator.pop(modalBottomSheetContext); Navigator.pop(modalBottomSheetContext);
});
} }
}, },
style: const ButtonStyle( style: const ButtonStyle(
@@ -283,6 +324,7 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
? IconButton( ? IconButton(
onPressed: () async { onPressed: () async {
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
await apiServices.execute(context,() async {
formKey.currentState!.save(); formKey.currentState!.save();
Map<String, dynamic> body = { Map<String, dynamic> body = {
"password_old": oldPass, "password_old": oldPass,
@@ -306,6 +348,7 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
Colors.white); Colors.white);
} }
Navigator.pop(modalBottomSheetContext); Navigator.pop(modalBottomSheetContext);
});
} }
}, },
icon: icon:
@@ -390,6 +433,7 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
? Center( ? Center(
child: TextButton( child: TextButton(
onPressed: () async { onPressed: () async {
await apiServices.execute(context,() async {
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
formKey.currentState!.save(); formKey.currentState!.save();
Map<String, dynamic> body = { Map<String, dynamic> body = {
@@ -415,6 +459,38 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
} }
Navigator.pop(modalBottomSheetContext); 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( style: const ButtonStyle(
backgroundColor: backgroundColor:

View File

@@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../product/constant/app/app_constants.dart'; import '../../product/constant/app/app_constants.dart';
import '../../product/shared/shared_loading_animation.dart';
import 'profile/profile_screen.dart'; import 'profile/profile_screen.dart';
import '../../product/constant/icon/icon_constants.dart'; import '../../product/constant/icon/icon_constants.dart';
import '../../product/extension/context_extension.dart'; import '../../product/extension/context_extension.dart';
@@ -27,7 +27,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
void initState() { void initState() {
super.initState(); super.initState();
settingsBloc = BlocProvider.of(context); settingsBloc = BlocProvider.of(context);
getUserProfile();
} }
@override @override
@@ -39,15 +38,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
body: StreamBuilder<User>( body: StreamBuilder<User>(
stream: settingsBloc.streamUserProfile, stream: settingsBloc.streamUserProfile,
initialData: user,
builder: (context, userSnapshot) { builder: (context, userSnapshot) {
return userSnapshot.data?.id == "" || user.id == "" if (userSnapshot.data == null) {
? Center( settingsBloc.getUserProfile(context);
child: CircularProgressIndicator( return const SharedLoadingAnimation();
value: context.highValue, } else {
), return Column(
)
: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@@ -60,12 +56,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: CircleAvatar( child: CircleAvatar(
radius: 50, radius: 50,
child: Text( child: Text(
getAvatarContent( getAvatarContent(userSnapshot.data?.username ?? ""),
userSnapshot.data?.username ?? ""), style: context.dynamicResponsiveSize(36),
style: const TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
),
), ),
), ),
), ),
@@ -76,8 +68,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [ children: [
Text( Text(
userSnapshot.data?.name ?? "User Name", userSnapshot.data?.name ?? "User Name",
style: const TextStyle( style: context.h2,
fontWeight: FontWeight.w900, fontSize: 26),
) )
], ],
), ),
@@ -89,29 +80,36 @@ class _SettingsScreenState extends State<SettingsScreen> {
cardContent( cardContent(
Icons.account_circle_rounded, Icons.account_circle_rounded,
appLocalization(context).profile_change_info, appLocalization(context).profile_change_info,
), userSnapshot.data ?? user),
SizedBox(height: context.lowValue), SizedBox(height: context.lowValue),
cardContent( cardContent(
Icons.lock_outline, Icons.lock_outline,
appLocalization(context).profile_change_pass, appLocalization(context).profile_change_pass,
), userSnapshot.data ?? user),
SizedBox(height: context.lowValue), SizedBox(height: context.lowValue),
cardContent( cardContent(
Icons.settings_outlined, Icons.settings_outlined,
appLocalization(context).profile_setting, 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), SizedBox(height: context.lowValue),
cardContent( cardContent(
Icons.logout_outlined, Icons.logout_outlined,
appLocalization(context).log_out, appLocalization(context).log_out,
), userSnapshot.data ?? user),
], ],
); );
}), }
},
),
); );
} }
cardContent(IconData icon, String content) { cardContent(IconData icon, String content, User user) {
return GestureDetector( return GestureDetector(
onTap: () async { onTap: () async {
if (icon == Icons.account_circle_rounded) { if (icon == Icons.account_circle_rounded) {
@@ -120,7 +118,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
changeUserPassword(context, settingsBloc); changeUserPassword(context, settingsBloc);
} else if (icon == Icons.settings_outlined) { } else if (icon == Icons.settings_outlined) {
context.push(ApplicationConstants.DEVICE_NOTIFICATIONS_SETTINGS); context.push(ApplicationConstants.DEVICE_NOTIFICATIONS_SETTINGS);
} else { } else if(icon == Icons.sim_card){
context.push(ApplicationConstants.SIM_DATA_SETTINGS);
}
else {
await apiServices.logOut(context); await apiServices.logOut(context);
} }
}, },
@@ -132,7 +133,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
leading: IconConstants.instance.getMaterialIcon(icon), leading: IconConstants.instance.getMaterialIcon(icon),
title: Text( title: Text(
content, content,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: context.responsiveBodyMediumWithBold,
), ),
trailing: const Icon( trailing: const Icon(
Icons.arrow_forward_ios_outlined, 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 getAvatarContent(String username) {
String name = ""; String name = "";
if (username.isNotEmpty) { 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_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; show PersistentTabController;
import 'package:sfm_app/firebase_options.dart';
import 'firebase_options.dart';
import 'product/lang/l10n/app_localizations.dart';
import 'product/services/api_services.dart'; import 'product/services/api_services.dart';
import 'product/services/notification_services.dart'; import 'product/services/notification_services.dart';
import 'product/services/theme_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/base/bloc/base_bloc.dart';
import 'product/constant/navigation/navigation_router.dart'; import 'product/constant/navigation/navigation_router.dart';
@pragma('vm:entry-point') PersistentTabController controller = PersistentTabController(initialIndex: 0);
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;
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); await Firebase.initializeApp(
// Set the background messaging handler early on, as a named top-level function options: DefaultFirebaseOptions.currentPlatform,
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); name: "sfm-notification");
FirebaseMessaging.onBackgroundMessage(
if (!kIsWeb) { NotificationServices.firebaseMessagingBackgroundHandler);
await setupFlutterNotifications(); await Alarm.init();
} await Alarm.stopAll();
runApp( runApp(
BlocProvider( BlocProvider(
child: const MyApp(), child: const MyApp(),
@@ -107,7 +34,6 @@ void main() async {
); );
} }
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); const MyApp({super.key});
@@ -130,7 +56,7 @@ class _MyAppState extends State<MyApp> {
late MainBloc mainBloc; late MainBloc mainBloc;
LanguageServices languageServices = LanguageServices(); LanguageServices languageServices = LanguageServices();
ThemeServices themeServices = ThemeServices(); ThemeServices themeServices = ThemeServices();
// final NotificationServices notificationServices = NotificationServices(); final NotificationServices notificationServices = NotificationServices();
APIServices apiServices = APIServices(); APIServices apiServices = APIServices();
setLocale(Locale locale) { setLocale(Locale locale) {
_locale = locale; _locale = locale;
@@ -146,20 +72,11 @@ class _MyAppState extends State<MyApp> {
void initState() { void initState() {
super.initState(); super.initState();
mainBloc = BlocProvider.of(context); mainBloc = BlocProvider.of(context);
// // notificationServices.initLocalNotifications(); notificationServices.initialize();
// // notificationServices.firebaseInit(context); notificationServices.firebaseInit(context);
// // notificationServices.setupInteractMessage(); notificationServices.setupInteractMessage(controller);
// notificationServices.getDeviceToken().then((token){
// print("Firebase Token: $token");
// sendNotificationToken(token);
// });
} }
// void sendNotificationToken (String token) async {
// int statusCode = await apiServices.sendNotificationToken(token);
// log("Notification Send StatusCode : $statusCode");
// }
@override @override
void didChangeDependencies() { void didChangeDependencies() {
languageServices.getLocale().then((locale) => {setLocale(locale)}); languageServices.getLocale().then((locale) => {setLocale(locale)});
@@ -186,8 +103,7 @@ class _MyAppState extends State<MyApp> {
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
locale: languageSnapshot.data, locale: languageSnapshot.data,
); );
} });
);
}, },
); );
} }

View File

@@ -30,8 +30,7 @@ class RequestPermissionDialog {
child: Text( child: Text(
"Alow app to use $content permission", "Alow app to use $content permission",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: context.responsiveBodyLargeWithBold,
fontWeight: FontWeight.bold, fontSize: 18),
), ),
), ),
Divider(height: dialogContext.lowValue), 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 { class ApplicationConstants {
static const APP_NAME = "Smatec SFM"; static const APP_NAME = "Smatec SFM";
@@ -21,8 +21,10 @@ class ApplicationConstants {
static const DEVICE_LOGS_PATH = "/device-logs"; static const DEVICE_LOGS_PATH = "/device-logs";
static const GROUP_PATH = "/groups"; static const GROUP_PATH = "/groups";
static const DEVICE_NOTIFICATIONS_SETTINGS = "/device-notifications-settings"; static const DEVICE_NOTIFICATIONS_SETTINGS = "/device-notifications-settings";
static const SIM_DATA_SETTINGS = "/sim-data-settings";
static const OWNER_GROUP = "owner"; static const OWNER_GROUP = "owner";
static const PARTICIPANT_GROUP = "participant"; static const PARTICIPANT_GROUP = "participant";
static const NO_DATA = "no_data"; static const NO_DATA = "no_data";
static const LOADING = "loading"; static const LOADING = "loading";
static int CALL_API_TIMEOUT = 30;
} }

View File

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

View File

@@ -1,4 +1,7 @@
import 'package:go_router/go_router.dart'; 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 '../../../bloc/device_detail_bloc.dart';
import '../../../feature/devices/device_detail/device_detail_screen.dart'; import '../../../feature/devices/device_detail/device_detail_screen.dart';
import '../../../bloc/device_notification_settings_bloc.dart'; import '../../../bloc/device_notification_settings_bloc.dart';
@@ -151,6 +154,16 @@ GoRouter goRouter() {
), ),
transitionsBuilder: transitionsRightToLeft), 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 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../utils/app_logger_utils.dart';
import '../utils/responsive_text_utils.dart';
import '../theme/app_theme_light.dart'; import '../theme/app_theme_light.dart';
// MEDIA // MEDIA
@@ -9,33 +10,77 @@ extension ContextExtension on BuildContext {
MediaQueryData get mediaQuery => MediaQuery.of(this); 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 // VALUES
extension MediaQueryExtension on BuildContext { extension MediaQueryExtension on BuildContext {
double get height => mediaQuery.size.height; double get height => mediaQuery.size.height;
double get width => mediaQuery.size.width; double get width => mediaQuery.size.width;
double get lowValue => height * 0.01; double get lowValue => height * 0.01;
double get normalValue => height * 0.02; double get normalValue => height * 0.02;
double get mediumValue => height * 0.04; double get mediumValue => height * 0.04;
double get highValue => height * 0.1; double get highValue => height * 0.1;
double dynamicWidth(double val) => width * val; double dynamicWidth(double val) => width * val;
double dynamicHeight(double val) => height * val; double dynamicHeight(double val) => height * val;
} }
// THEME // THEME
extension ThemeExtension on BuildContext { extension ThemeExtension on BuildContext {
ThemeData get theme => Theme.of(this); ThemeData get theme => Theme.of(this);
TextTheme get textTheme => theme.textTheme; TextTheme get textTheme => theme.textTheme;
ColorScheme get colors => AppThemeLight.instance.theme.colorScheme; ColorScheme get colors => AppThemeLight.instance.theme.colorScheme;
} }
// PADDING ALLL // PADDING ALLL
extension PaddingExtensionAll on BuildContext { extension PaddingExtensionAll on BuildContext {
EdgeInsets get paddingLow => EdgeInsets.all(lowValue); EdgeInsets get paddingLow => EdgeInsets.all(lowValue);
EdgeInsets get paddingNormal => EdgeInsets.all(normalValue); EdgeInsets get paddingNormal => EdgeInsets.all(normalValue);
EdgeInsets get paddingMedium => EdgeInsets.all(mediumValue); EdgeInsets get paddingMedium => EdgeInsets.all(mediumValue);
EdgeInsets get paddingHigh => EdgeInsets.all(highValue); EdgeInsets get paddingHigh => EdgeInsets.all(highValue);
EdgeInsets dynamicPadding(double val) => EdgeInsets.all(val); EdgeInsets dynamicPadding(double val) => EdgeInsets.all(val);
// double dynamicPadding(double val) => height * val; // double dynamicPadding(double val) => height * val;
} }
@@ -44,20 +89,26 @@ extension PaddingExtensionAll on BuildContext {
extension PaddingExtensionSymetric on BuildContext { extension PaddingExtensionSymetric on BuildContext {
// VERTICAL PADDİNG // VERTICAL PADDİNG
EdgeInsets get paddingLowVertical => EdgeInsets.symmetric(vertical: lowValue); EdgeInsets get paddingLowVertical => EdgeInsets.symmetric(vertical: lowValue);
EdgeInsets get paddingNormalVertical => EdgeInsets get paddingNormalVertical =>
EdgeInsets.symmetric(vertical: normalValue); EdgeInsets.symmetric(vertical: normalValue);
EdgeInsets get paddingMediumVertical => EdgeInsets get paddingMediumVertical =>
EdgeInsets.symmetric(vertical: mediumValue); EdgeInsets.symmetric(vertical: mediumValue);
EdgeInsets get paddingHighVertical => EdgeInsets get paddingHighVertical =>
EdgeInsets.symmetric(vertical: highValue); EdgeInsets.symmetric(vertical: highValue);
// HORIZONTAL PADDİNG // HORIZONTAL PADDİNG
EdgeInsets get paddingLowHorizontal => EdgeInsets get paddingLowHorizontal =>
EdgeInsets.symmetric(horizontal: lowValue); EdgeInsets.symmetric(horizontal: lowValue);
EdgeInsets get paddingNormalHorizontal => EdgeInsets get paddingNormalHorizontal =>
EdgeInsets.symmetric(horizontal: normalValue); EdgeInsets.symmetric(horizontal: normalValue);
EdgeInsets get paddingMediumHorizontal => EdgeInsets get paddingMediumHorizontal =>
EdgeInsets.symmetric(horizontal: mediumValue); EdgeInsets.symmetric(horizontal: mediumValue);
EdgeInsets get paddingHighHorizontal => EdgeInsets get paddingHighHorizontal =>
EdgeInsets.symmetric(horizontal: highValue); EdgeInsets.symmetric(horizontal: highValue);
} }
@@ -70,34 +121,63 @@ extension PageExtension on BuildContext {
// DURATION // DURATION
extension DurationExtension on BuildContext { extension DurationExtension on BuildContext {
Duration get lowDuration => const Duration(milliseconds: 150); Duration get lowDuration => const Duration(milliseconds: 150);
Duration get normalDuration => const Duration(milliseconds: 500); Duration get normalDuration => const Duration(milliseconds: 500);
Duration dynamicMilliSecondDuration(int milliseconds) => Duration dynamicMilliSecondDuration(int milliseconds) =>
Duration(milliseconds: milliseconds); Duration(milliseconds: milliseconds);
Duration dynamicMinutesDuration(int minutes) => Duration(minutes: minutes); Duration dynamicMinutesDuration(int minutes) => Duration(minutes: minutes);
} }
// RADIUS // RADIUS
extension RadiusExtension on BuildContext { extension RadiusExtension on BuildContext {
Radius get lowRadius => Radius.circular(width * 0.02); Radius get lowRadius => Radius.circular(width * 0.02);
Radius get normalRadius => Radius.circular(width * 0.05); Radius get normalRadius => Radius.circular(width * 0.05);
Radius get highRadius => Radius.circular(width * 0.1); Radius get highRadius => Radius.circular(width * 0.1);
Radius dynamicRadius(double radius) => Radius.circular(radius); Radius dynamicRadius(double radius) => Radius.circular(radius);
} }
extension TextStyleExtention on BuildContext { extension TextStyleExtention on BuildContext {
TextStyle get labelSmallTextStyle => Theme.of(this).textTheme.labelSmall!; TextStyle get labelSmallTextStyle => Theme.of(this).textTheme.labelSmall!;
TextStyle get labelMediumTextStyle => Theme.of(this).textTheme.labelMedium!; TextStyle get labelMediumTextStyle => Theme.of(this).textTheme.labelMedium!;
TextStyle get labelLargeTextStyle => Theme.of(this).textTheme.labelLarge!; TextStyle get labelLargeTextStyle => Theme.of(this).textTheme.labelLarge!;
TextStyle get bodySmallTextStyle => Theme.of(this).textTheme.bodySmall!; TextStyle get bodySmallTextStyle => Theme.of(this).textTheme.bodySmall!;
TextStyle get bodyMediumTextStyle => Theme.of(this).textTheme.bodyMedium!; TextStyle get bodyMediumTextStyle => Theme.of(this).textTheme.bodyMedium!;
TextStyle get bodyLargeTextStyle => Theme.of(this).textTheme.bodyLarge!; TextStyle get bodyLargeTextStyle => Theme.of(this).textTheme.bodyLarge!;
TextStyle get titleSmallTextStyle => Theme.of(this).textTheme.titleSmall!; TextStyle get titleSmallTextStyle => Theme.of(this).textTheme.titleSmall!;
TextStyle get titleMediumTextStyle => Theme.of(this).textTheme.titleMedium!; TextStyle get titleMediumTextStyle => Theme.of(this).textTheme.titleMedium!;
TextStyle get titleLargeTextStyle => Theme.of(this).textTheme.titleLarge!; TextStyle get titleLargeTextStyle => Theme.of(this).textTheme.titleLarge!;
TextStyle get headlineSmallTextStyle => TextStyle get headlineSmallTextStyle =>
Theme.of(this).textTheme.headlineSmall!; Theme.of(this).textTheme.headlineSmall!;
TextStyle get headlineMediumTextStyle => TextStyle get headlineMediumTextStyle =>
Theme.of(this).textTheme.headlineMedium!; Theme.of(this).textTheme.headlineMedium!;
TextStyle get headlineLargeTextStyle => TextStyle get headlineLargeTextStyle =>
Theme.of(this).textTheme.headlineLarge!; 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!", "let_PCCC_handle_message": "Let the Fire Prevention and Fighting Team handle it!",
"overview_message": "Overview", "overview_message": "Overview",
"total_nof_devices_message": "Total number of devices", "total_nof_devices_message": "Total number of devices",
"over_view_owner_devices":"Owner Devices",
"over_view_joined_devices":"Joined Devices",
"active_devices_message": "Active", "active_devices_message": "Active",
"inactive_devices_message": "Inactive", "inactive_devices_message": "Inactive",
"warning_devices_message": "Warning", "warning_devices_message": "Warning",
@@ -109,7 +111,10 @@
"profile_page_title": "Settings Page", "profile_page_title": "Settings Page",
"profile_change_info": "Change information", "profile_change_info": "Change information",
"profile_change_pass": "Change password", "profile_change_pass": "Change password",
"profile_sim_data": "Device SIM information",
"profile_setting": "Notification Setting", "profile_setting": "Notification Setting",
"sim_data_month_left_message": "months left",
"time_title": "Time",
"change_profile_title": "Personal information", "change_profile_title": "Personal information",
"change_profile_username": "Username: ", "change_profile_username": "Username: ",
"change_profile_username_hint": "Enter 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", "confirm_fake_fire_sure_message": "Tôi chắc chắn",
"let_PCCC_handle_message": "Hãy để Đội PCCC xử lý!", "let_PCCC_handle_message": "Hãy để Đội PCCC xử lý!",
"overview_message": "Tổng quan", "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ố", "total_nof_devices_message": "Tổng số",
"active_devices_message": "Bình thường", "active_devices_message": "Bình thường",
"inactive_devices_message": "Đang tắt", "inactive_devices_message": "Đang tắt",
@@ -110,6 +112,9 @@
"profile_change_info": "Đổi thông tin cá nhân", "profile_change_info": "Đổi thông tin cá nhân",
"profile_change_pass": "Đổi mật khẩu", "profile_change_pass": "Đổi mật khẩu",
"profile_setting": "Cài đặt thông báo", "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_title": "Thông tin người dùng",
"change_profile_username": "Tên người dùng: ", "change_profile_username": "Tên người dùng: ",
"change_profile_username_hint": "Nhập tên ", "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 '../constant/icon/icon_constants.dart';
import 'package:sfm_app/product/constant/lang/language_constants.dart'; import '../constant/lang/language_constants.dart';
class Language { class Language {
final int id; final int id;

View File

@@ -1,6 +1,9 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import '../utils/app_logger_utils.dart';
import '../constant/status_code/status_code_constants.dart'; import '../constant/status_code/status_code_constants.dart';
import '../cache/local_manager.dart'; import '../cache/local_manager.dart';
@@ -13,6 +16,7 @@ class NetworkManager {
static NetworkManager? _instance; static NetworkManager? _instance;
static NetworkManager? get instance => _instance ??= NetworkManager._init(); static NetworkManager? get instance => _instance ??= NetworkManager._init();
Future<Map<String, String>> getHeaders() async { Future<Map<String, String>> getHeaders() async {
String? token = String? token =
LocaleManager.instance.getStringValue(PreferencesKeys.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 is successful (status code 200), or an empty
/// string if the request fails /// string if the request fails
Future<String> getDataFromServer(String path) async { Future<String> getDataFromServer(String path) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path); 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 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 || if (response.statusCode == StatusCodeConstants.OK ||
response.statusCode == StatusCodeConstants.CREATED) { response.statusCode == StatusCodeConstants.CREATED) {
return response.body; return response.body;
} else { } 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. /// Returns a [Future<String>] containing the server response body.
Future<String> getDataFromServerWithParams( Future<String> getDataFromServerWithParams(
String path, Map<String, dynamic> params) async { String path, Map<String, dynamic> params) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path, params); 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 headers = await getHeaders();
final response = await http.get(url, headers: headers); final response = await http.get(url, headers: headers).timeout(
if (response.statusCode == StatusCodeConstants.CREATED || Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT),
response.statusCode == StatusCodeConstants.OK) { 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; return response.body;
} else { } 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. /// 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 /// [path] is the endpoint for the request, and [body] contains the data
/// to be sent. Returns the HTTP status code of the response. /// to be sent. Returns the HTTP status code of the response.
Future<int> createDataInServer(String path, Map<String, dynamic> body) async { Future<int> createDataInServer(String path, Map<String, dynamic> body) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path); 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 headers = await getHeaders();
final response = final response = await http
await http.post(url, headers: headers, body: jsonEncode(body)); .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; 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. /// 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 /// [path] is the endpoint for the request, and [body] contains the data
/// to be updated. Returns the HTTP status code of the response. /// to be updated. Returns the HTTP status code of the response.
Future<int> updateDataInServer(String path, Map<String, dynamic> body) async { Future<int> updateDataInServer(String path, Map<String, dynamic> body) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path); 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 headers = await getHeaders();
final response = 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; 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. /// 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 /// A status code of 200 indicates success, while other codes indicate
/// failure or an error. /// failure or an error.
Future<int> deleteDataInServer(String path) async { Future<int> deleteDataInServer(String path) async {
try {
final url = Uri.https(ApplicationConstants.DOMAIN, path); 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 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; 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:developer';
import 'dart:io'; import 'dart:io';
import 'package:app_settings/app_settings.dart'; import 'package:app_settings/app_settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.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 { class LocationPermissionRequest {
LocationPermissionRequest._init(); LocationPermissionRequest._init();

View File

@@ -1,8 +1,10 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:io';
import 'package:app_settings/app_settings.dart'; import 'package:app_settings/app_settings.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import '../base/widget/dialog/request_permission_dialog.dart'; import '../base/widget/dialog/request_permission_dialog.dart';
@@ -11,7 +13,7 @@ class NotificationPermission {
static NotificationPermission? _instance; static NotificationPermission? _instance;
static NotificationPermission get instance => static NotificationPermission get instance =>
_instance ??= NotificationPermission._init(); _instance ??= NotificationPermission._init();
static final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
Future<bool> checkNotificationPermission(context) async { Future<bool> checkNotificationPermission(context) async {
var status = await Permission.notification.status; var status = await Permission.notification.status;
log("Status: $status"); log("Status: $status");
@@ -44,4 +46,21 @@ class NotificationPermission {
Icons.location_on_outlined, "ABCDE", AppSettingsType.notification); 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 // ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http; 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 '../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 '../shared/shared_snack_bar.dart';
import '../constant/enums/app_route_enums.dart'; import '../constant/enums/app_route_enums.dart';
import 'language_services.dart'; import 'language_services.dart';
@@ -41,6 +55,86 @@ class APIServices {
return headers; 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 { Future<String> login(String path, Map<String, dynamic> loginRequest) async {
final url = Uri.https(ApplicationConstants.DOMAIN, path); final url = Uri.https(ApplicationConstants.DOMAIN, path);
final headers = await getHeaders(); final headers = await getHeaders();
@@ -51,12 +145,9 @@ class APIServices {
Future<int> sendNotificationToken(String token) async { Future<int> sendNotificationToken(String token) async {
String uid = await getUID(); String uid = await getUID();
Map<String,dynamic> body = { Map<String, dynamic> body = {"user_id": uid, "app_token": token};
"user_id": uid, int statusCode = await NetworkManager.instance!
"app_token": token .updateDataInServer(APIPathConstants.NOTIFICATION_TOKEN_PATH, body);
};
int statusCode = await NetworkManager.instance!.updateDataInServer(
APIPathConstants.NOTIFICATION_TOKEN_PATH, body);
return statusCode; return statusCode;
} }
@@ -69,7 +160,7 @@ class APIServices {
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
var url = Uri.http(ApplicationConstants.DOMAIN, var url = Uri.https(ApplicationConstants.DOMAIN,
APIPathConstants.LOGOUT_PATH); APIPathConstants.LOGOUT_PATH);
final headers = await NetworkManager.instance!.getHeaders(); final headers = await NetworkManager.instance!.getHeaders();
final response = await http.post(url, headers: headers); final response = await http.post(url, headers: headers);
@@ -122,67 +213,89 @@ class APIServices {
return language; return language;
} }
Future<Bell> getBellNotifications(String offset, String pagesize) async { Future<Bell> getBellNotifications(String offset, String pageSize) async {
Bell bell = Bell(); final params = {"offset": offset, "page_size": pageSize};
final params = {"offset": offset, "page_size": pagesize}; return executeApiCall(
final data = await NetworkManager.instance!.getDataFromServerWithParams( () => NetworkManager.instance!.getDataFromServerWithParams(
APIPathConstants.BELL_NOTIFICATIONS_PATH, params); APIPathConstants.BELL_NOTIFICATIONS_PATH, params),
if (data != "") { parser: (json) => Bell.fromJson(json),
bell = Bell.fromJson(jsonDecode(data)); errorMessage: 'Lỗi khi GET /${APIPathConstants.BELL_NOTIFICATIONS_PATH}',
return bell; );
} else {
return bell;
}
} }
Future<int> updateStatusOfNotification(List<String> notificationID) async { Future<int> updateStatusOfNotification(List<String> notificationID) async {
Map<String, dynamic> body = { Map<String, dynamic> body = {
"event_ids": notificationID, "event_ids": notificationID,
}; };
int statusCode = await NetworkManager.instance!.updateDataInServer( return executeApiCall(
APIPathConstants.BELL_UPDATE_READ_NOTIFICATIONS_PATH, body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; 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 uid = await getUID();
String? response = await NetworkManager.instance! return executeApiCall(
.getDataFromServer('${APIPathConstants.USER_PATH}/$uid'); () => NetworkManager.instance!
return response; .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 { Future<int> updateUserProfile(Map<String, dynamic> body) async {
String uid = await getUID(); String uid = await getUID();
int statusCode = await NetworkManager.instance! return executeApiCall(
.updateDataInServer("${APIPathConstants.USER_PROFILE_PATH}/$uid", body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; "${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 { Future<int> updateUserPassword(Map<String, dynamic> body) async {
String uid = await getUID(); String uid = await getUID();
int statusCode = await NetworkManager.instance!.updateDataInServer( // int statusCode = await NetworkManager.instance!.updateDataInServer(
"${APIPathConstants.USER_PATH}/$uid/password", body); // "${APIPathConstants.USER_PATH}/$uid/password", body);
return statusCode; 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 { Future<List<DeviceNotificationSettings>>
String? data = await NetworkManager.instance! getAllSettingsNotificationOfDevices() async {
.getDataFromServer(APIPathConstants.DEVICE_NOTIFICATION_SETTINGS); return executeApiCall(
return data; () => 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( Future<int> updateDeviceNotificationSettings(
String thingID, Map<String, int> data) async { String thingID, Map<String, int> data) async {
Map<String, dynamic> body = {"thing_id": thingID, "notifi_settings": data}; Map<String, dynamic> body = {"thing_id": thingID, "notifi_settings": data};
int statusCode = await NetworkManager.instance!.updateDataInServer( return executeApiCall(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}');
} }
Future<String> getDashBoardDevices() async { Future<List<DeviceWithAlias>> getDashBoardDevices() async {
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServer(APIPathConstants.DASHBOARD_DEVICES); () => NetworkManager.instance!
return data; .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 { Future<int> setupDeviceNotification(String thingID, String deviceName) async {
@@ -202,68 +315,103 @@ class APIServices {
"104": 1, "104": 1,
} }
}; };
int statusCode = await NetworkManager.instance!.updateDataInServer( return executeApiCall(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}');
} }
Future<String> getAllProvinces() async { Future<List<Province>> getAllProvinces() async {
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServer(APIPathConstants.PROVINCES_PATH); () => NetworkManager.instance!
return data; .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}; final params = {'name': name};
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServerWithParams(APIPathConstants.PROVINCES_PATH, params); () => NetworkManager.instance!
return data; .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 { Future<Province> getProvinceByID(String provinceID) async {
String data = await NetworkManager.instance! return executeApiCall(
.getDataFromServer("${APIPathConstants.PROVINCES_PATH}/$provinceID"); () => NetworkManager.instance!
return data; .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}; final params = {"parent": provinceID};
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params); () => NetworkManager.instance!
return data; .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}; final params = {"name": districtName};
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params); () => NetworkManager.instance!
return data; .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 { Future<District> getDistrictByID(String districtID) async {
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServer("${APIPathConstants.DISTRICTS_PATH}/$districtID"); () => NetworkManager.instance!
return data; .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}; final params = {'parent': districtID};
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params); () => NetworkManager.instance!
return data; .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}; final params = {"name": wardName};
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params); () => NetworkManager.instance!
return data; .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 { Future<Ward> getWardByID(String wardID) async {
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServer("${APIPathConstants.WARDS_PATH}/$wardID"); () => NetworkManager.instance!
return data; .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 { Future<int> confirmFakeFireByUser(String thingID) async {
@@ -271,131 +419,187 @@ class APIServices {
"state": 3, "state": 3,
"note": "Người dùng xác nhận cháy giả!" "note": "Người dùng xác nhận cháy giả!"
}; };
int statusCode = await NetworkManager.instance! return executeApiCall(
.updateDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID", body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; "${APIPathConstants.DEVICE_PATH}/$thingID", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_PATH}/$thingID');
} }
Future<String> getOwnerDevices() async { Future<List<Device>> getOwnerDevices() async {
String? data = await NetworkManager.instance! return executeApiCall(
.getDataFromServer(APIPathConstants.DEVICE_PATH); () => NetworkManager.instance!
return data; .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 { Future<List<Device>> getOwnerDeviceByState(
String? data = await NetworkManager.instance! Map<String, dynamic> params) async {
.getDataFromServerWithParams(APIPathConstants.DEVICE_PATH, params); return executeApiCall(
return data; () => 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 { Future<int> createDeviceByAdmin(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance! return executeApiCall(
.createDataInServer(APIPathConstants.DEVICE_PATH, body); () => NetworkManager.instance!
return statusCode; .createDataInServer(APIPathConstants.DEVICE_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_PATH}');
} }
Future<int> registerDevice(Map<String, dynamic> body) async { Future<int> registerDevice(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance! return executeApiCall(
.createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body); () => NetworkManager.instance!
return statusCode; .createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_REGISTER_PATH}');
} }
Future<int> deleteDeviceByAdmin(String thingID) async { Future<int> deleteDeviceByAdmin(String thingID) async {
int statusCode = await NetworkManager.instance! return executeApiCall(
.deleteDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID"); () => NetworkManager.instance!
return statusCode; .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 { Future<int> unregisterDevice(Map<String, dynamic> body) async {
int statusCode = await NetworkManager.instance! return executeApiCall(
.createDataInServer(APIPathConstants.DEVICE_UNREGISTER_PATH, body); () => NetworkManager.instance!
return statusCode; .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 { Future<Device> getDeviceInformation(String thingID) async {
String? response = await NetworkManager.instance! return executeApiCall(
.getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID"); () => NetworkManager.instance!
return response; .getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID"),
parser: (json) => Device.fromJson(json),
errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}/$thingID',
);
} }
Future<String> getAllGroups() async { Future<List<Group>> getAllGroups() async {
String? body = await NetworkManager.instance! return executeApiCall(
.getDataFromServer(APIPathConstants.ALL_GROUPS_PATH); () => NetworkManager.instance!
return body; .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 { Future<int> createGroup(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance! return executeApiCall(
.createDataInServer(APIPathConstants.GROUPS_PATH, body); () => NetworkManager.instance!
return statusCode; .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 { Future<int> updateGroup(Map<String, dynamic> body, String groupID) async {
int? statusCode = await NetworkManager.instance! return executeApiCall(
.updateDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID", body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; "${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 { Future<int> joinGroup(String groupID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance! return executeApiCall(
.createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body); () => NetworkManager.instance!
return statusCode; .createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage: 'Lỗi khi POST /${APIPathConstants.JOIN_GROUP_PATH}');
} }
Future<int> deleteGroup(String groupID) async { Future<int> deleteGroup(String groupID) async {
int? statusCode = await NetworkManager.instance! return executeApiCall(
.deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID"); () => NetworkManager.instance!
return statusCode; .deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID"),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID');
} }
Future<String> getGroupDetail(String groupID) async { Future<GroupDetail> getGroupDetail(String groupID) async {
String? body = await NetworkManager.instance! return executeApiCall(
.getDataFromServer("${APIPathConstants.GROUPS_PATH}/$groupID"); () => NetworkManager.instance!
return body; .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 { Future<int> approveGroup(Map<String, dynamic> body) async {
int statusCode = await NetworkManager.instance! return executeApiCall(
.createDataInServer(APIPathConstants.APPROVE_GROUP_PATH, body); () => NetworkManager.instance!
return statusCode; .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 { Future<int> deleteUserInGroup(String groupID, String userID) async {
int? statusCode = await NetworkManager.instance!.deleteDataInServer( return executeApiCall(
"${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID"); () => NetworkManager.instance!.deleteDataInServer(
return statusCode; "${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 { Future<int> deleteDeviceInGroup(String groupID, String thingID) async {
int? statusCode = await NetworkManager.instance!.deleteDataInServer( return executeApiCall(
"${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID"); () => NetworkManager.instance!.deleteDataInServer(
return statusCode; "${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 { Future<int> updateDeviceAlias(Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!.updateDataInServer( return executeApiCall(
APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}');
} }
Future<int> addDeviceToGroup( Future<int> addDeviceToGroup(
String groupID, Map<String, dynamic> body) async { String groupID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance!.createDataInServer( return executeApiCall(
"${APIPathConstants.GROUPS_PATH}/$groupID/things", body); () => NetworkManager.instance!.createDataInServer(
return statusCode; "${APIPathConstants.GROUPS_PATH}/$groupID/things", body),
statusCodeHandler: (statusCode) => statusCode,
errorMessage:
'Lỗi khi PUT /${APIPathConstants.GROUPS_PATH}/$groupID/things');
} }
Future<int> updateOwnerDevice( Future<int> updateOwnerDevice(
String thingID, Map<String, dynamic> body) async { String thingID, Map<String, dynamic> body) async {
int? statusCode = await NetworkManager.instance! return executeApiCall(
.updateDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID", body); () => NetworkManager.instance!.updateDataInServer(
return statusCode; "${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 thingID, Map<String, dynamic> params) async {
String? body = await NetworkManager.instance! return executeApiCall(
.getDataFromServerWithParams(APIPathConstants.DEVICE_LOGS_PATH, params); () => NetworkManager.instance!.getDataFromServerWithParams(
return body; 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 '../cache/local_manager.dart';
import '../constant/enums/local_keys_enums.dart'; import '../constant/enums/local_keys_enums.dart';
import '../constant/lang/language_constants.dart'; import '../constant/lang/language_constants.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../lang/l10n/app_localizations.dart';
class LanguageServices { class LanguageServices {
Future<Locale> setLocale(String languageCode) async { 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'; import '../shared/model/near_by_search_model.dart';
class MapServices { class MapServices {
Future<List<PlaceDetails>> getNearbyPlaces(
Future<List<PlaceDetails>> getNearbyPlaces(double latitude, double longitude, double latitude, double longitude, String searchKey, int radius, String type) async {
String searchKey, int radius, String type) async {
List<PlaceDetails> result = []; List<PlaceDetails> result = [];
var url = Uri.parse( 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}'); '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 = []; List<LatLng> polylineCoordinates = [];
PolylinePoints polylinePoints = PolylinePoints(); PolylinePoints polylinePoints = PolylinePoints();
// PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
// ApplicationConstants.MAP_KEY, googleApiKey: ApplicationConstants.MAP_KEY,
// PointLatLng(origin.latitude, origin.longitude), request: PolylineRequest(
// PointLatLng(destination.latitude, destination.longitude),
// travelMode: TravelMode.driving, origin: PointLatLng(origin.latitude, origin.longitude),
// optimizeWaypoints: true); destination: PointLatLng(destination.latitude, destination.longitude),
// if (result.points.isNotEmpty) { mode: TravelMode.driving,
// for (var point in result.points) { optimizeWaypoints: true));
// polylineCoordinates.add(LatLng(point.latitude, point.longitude)); if (result.points.isNotEmpty) {
// } for (var point in result.points) {
// return polylineCoordinates; polylineCoordinates.add(LatLng(point.latitude, point.longitude));
// } else { }
// log("Lỗi khi tìm đường"); return polylineCoordinates;
// return []; } else {
// } log("Lỗi khi tìm đường");
return []; return [];
} }
}
} }

View File

@@ -1,121 +1,262 @@
import 'dart:developer' as dev; import 'dart:developer' as dev;
import 'dart:io';
import 'dart:math' as math; 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: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: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 { class NotificationServices {
FirebaseMessaging messaging = FirebaseMessaging.instance; static final FlutterLocalNotificationsPlugin _notificationsPlugin =
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin();
final firebase_messaging.FirebaseMessaging _messaging =
firebase_messaging.FirebaseMessaging.instance;
AlarmServices alarmServices = AlarmServices();
Future<void> initLocalNotifications(PersistentTabController controller) async { Future<void> initialize() async {
const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); await initializeLocalNotifications();
const DarwinInitializationSettings iosInitializationSettings = DarwinInitializationSettings(); dev.log("NotificationService initialized");
const InitializationSettings initializationSettings = InitializationSettings( }
android: androidInitializationSettings,
iOS: iosInitializationSettings, @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( await _notificationsPlugin.initialize(
initializationSettings, initSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) { onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
dev.log("Người dùng click thông báo ở foreground với payload: ${response.payload}");
handleMessage(response.payload,controller);
},
); );
dev.log("Local notifications initialized"); 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) { void firebaseInit(BuildContext context) {
FirebaseMessaging.onMessage.listen((message) { firebase_messaging.FirebaseMessaging.onMessage.listen((message) {
dev.log("Foreground message payload: ${message.toMap()}"); _handleForegroundMessage(message);
if (WidgetsBinding.instance != null) {
showNotification(message);
} else {
dev.log("App is in background, skipping foreground notification");
}
}); });
} }
Future<String> getDeviceToken() async {
print("GET FB TOKEN");
String? token = await messaging.getAPNSToken();
print("GET FB: ${token}");
return token!;
}
void isTokenRefresh() { void isTokenRefresh() {
messaging.onTokenRefresh.listen((newToken) { _messaging.onTokenRefresh.listen((newToken) {
dev.log("Refresh Firebase Messaging Token: $newToken"); dev.log("Refresh Firebase Messaging Token: $newToken");
}); });
} }
Future<void> showNotification(RemoteMessage message) async { static Future<bool?> requestNotificationPermission() async {
dev.log(message.toString()); try {
dev.log(message.data.toString()); final messaging = firebase_messaging.FirebaseMessaging.instance;
dev.log(message.data["notification"].toString()); final settings = await messaging.requestPermission(
String? title = message.data['title']; alert: true,
String? body = message.data['body']; announcement: false,
String type = message.data['type'] ?? "normal"; badge: true,
provisional: false,
if (title == null || body == null) { sound: true,
dev.log("Skipping notification due to missing title or body"); );
return; return settings.authorizationStatus ==
firebase_messaging.AuthorizationStatus.authorized;
} catch (e) {
AppLoggerUtils.error("Failed to request notification permission", e);
return null;
}
} }
AndroidNotificationChannel androidNotificationChannel = AndroidNotificationChannel( /// Xử lý message khi app foreground
math.Random.secure().nextInt(1000000).toString(), Future<void> _handleForegroundMessage(
'High Importance Notification', firebase_messaging.RemoteMessage message) async {
importance: Importance.max, 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 Alarm.set(alarmSettings: alarmSettings);
await androidPlugin?.deleteNotificationChannel(androidNotificationChannel.id); dev.log('Alarm set successfully: $title');
} catch (e, stackTrace) {
AppLoggerUtils.error('Failed to set alarm', e, stackTrace);
}
}
AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( /// Hiển thị notification local khi app foreground
androidNotificationChannel.id, Future<void> _showForegroundNotification(
androidNotificationChannel.name, String title, String body, String type) async {
channelDescription: "Channel description", try {
final androidDetails = AndroidNotificationDetails(
'sfm_channel_${math.Random.secure().nextInt(1000000)}',
'SFM Notifications',
channelDescription: 'Notifications from SFM app',
sound: getSound(type), sound: getSound(type),
importance: androidNotificationChannel.importance, importance: Importance.max,
priority: Priority.high, priority: Priority.high,
ticker: 'ticker', 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, presentAlert: true,
presentBadge: true, presentBadge: true,
presentBanner: true, presentBanner: true,
presentSound: true, presentSound: true,
); );
NotificationDetails notificationDetails = NotificationDetails( final notificationDetails = NotificationDetails(
android: androidNotificationDetails, android: androidDetails,
iOS: darwinNotificationDetails, iOS: iosDetails,
); );
// Truyền payload vào thông báo await _notificationsPlugin.show(
String payload = message.data['type'] ?? "default";
await _flutterLocalNotificationsPlugin.show(
math.Random.secure().nextInt(1000000), math.Random.secure().nextInt(1000000),
title, title,
body, body,
notificationDetails, 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) { AndroidNotificationSound getSound(String type) {
@@ -128,35 +269,110 @@ class NotificationServices {
} else if (type == "battery_warning") { } else if (type == "battery_warning") {
return const RawResourceAndroidNotificationSound("new_alarm"); return const RawResourceAndroidNotificationSound("new_alarm");
} else { } 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) { 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); 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 { Future<void> setupInteractMessage(PersistentTabController controller) async {
// Khi app terminated // Khi app terminated, được mở bằng notification
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); firebase_messaging.RemoteMessage? initialMessage =
await firebase_messaging.FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) { if (initialMessage != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { 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 { try {
handleMessage(initialMessage.data['type'],controller); final type = message.data['type'] as String?;
} catch (e, stack) { handleMessage(type, controller);
dev.log("Error handling initial message: $e\n$stack"); } 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 { try {
handleMessage(message.data['type'],controller); // Log to device storage for debugging background isolate
} catch (e, stack) { dev.log('═══ BACKGROUND HANDLER STARTED ═══');
dev.log("Error in onMessageOpenedApp: $e\n$stack"); 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:flutter/material.dart';
import 'package:sfm_app/product/constant/image/image_constants.dart';
import '../constant/image/image_constants.dart';
class SharedBackground extends StatelessWidget { class SharedBackground extends StatelessWidget {
final Widget child; 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:flutter/material.dart';
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
import '../constant/icon/icon_constants.dart';
const int _kDuration = 300; const int _kDuration = 300;
const double _kWidth = 60; const double _kWidth = 60;

View File

@@ -1,7 +1,7 @@
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart'; import '../../feature/device_log/device_logs_model.dart';
import 'package:sfm_app/product/utils/date_time_utils.dart'; import '../utils/date_time_utils.dart';
Widget sharedLineChart(String chartName, List<SensorLogs> sensors) { Widget sharedLineChart(String chartName, List<SensorLogs> sensors) {
return LineChart( 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) { switch (originalIndex) {
case 0: // OFFLINE_STATE case 0: // OFFLINE_STATE
log("Touched Index device state = -1"); log("Touched Index device state = -1");
widget.devicesManagerBloc.getDeviceByState(-1); widget.devicesManagerBloc.getDeviceByState(context,-1);
break; break;
case 1: // NORMAL_STATE case 1: // NORMAL_STATE
log("Touched Index Get device state = 0"); log("Touched Index Get device state = 0");
widget.devicesManagerBloc.getDeviceByState(0); widget.devicesManagerBloc.getDeviceByState(context,0);
break; break;
case 2: // WARNING_STATE case 2: // WARNING_STATE
log("Touched Index Get device state = 1"); log("Touched Index Get device state = 1");
widget.devicesManagerBloc.getDeviceByState(1); widget.devicesManagerBloc.getDeviceByState(context,1);
break; break;
case 3: // INPROGRESS_STATE case 3: // INPROGRESS_STATE
log("Touched Index Get device state = 2"); log("Touched Index Get device state = 2");
widget.devicesManagerBloc.getDeviceByState(2); widget.devicesManagerBloc.getDeviceByState(context,2);
break; break;
case 4: // ERROR_STATE case 4: // ERROR_STATE
log("Touched Index Get device state = 3"); log("Touched Index Get device state = 3");
widget.devicesManagerBloc.getDeviceByState(3); widget.devicesManagerBloc.getDeviceByState(context,3);
break; 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: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/custom_snack_bar.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart';
import '../extension/context_extension.dart';
void showNoIconTopSnackBar(BuildContext context, String message, void showNoIconTopSnackBar(BuildContext context, String message,
Color backgroundColor, Color textColor) { Color backgroundColor, Color textColor) {
if (!context.mounted) return; if (!context.mounted) return;

View File

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

View File

@@ -13,6 +13,7 @@ class AppThemeLight extends AppTheme {
@override @override
ThemeData get theme => FlexThemeData.light( ThemeData get theme => FlexThemeData.light(
scaffoldBackground: Colors.white,
useMaterial3: true, useMaterial3: true,
scheme: FlexScheme.flutterDash, scheme: FlexScheme.flutterDash,
bottomAppBarElevation: 20.0, 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: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 '../../feature/devices/device_model.dart';
import '../constant/icon/icon_constants.dart'; import '../constant/icon/icon_constants.dart';
import '../shared/model/ward_model.dart'; import '../shared/model/ward_model.dart';
@@ -93,30 +91,34 @@ class DeviceUtils {
return map; return map;
} }
Future<String> getFullDeviceLocation( Future<String> getFullDeviceLocation(
BuildContext context, String areaPath) async { BuildContext context, String areaPath, String? deviceName) async {
if (areaPath != "") { if (areaPath.isNotEmpty) {
List<String> parts = areaPath.split('_'); 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 provinceID = parts[0];
String districtID = parts[1]; String districtID = parts[1];
String wardID = parts[2]; String wardID = parts[2];
String provinceBody = await apiServices.getProvinceByID(provinceID); Province province = await apiServices.getProvinceByID(provinceID);
final provinceItem = jsonDecode(provinceBody); District district = await apiServices.getDistrictByID(districtID);
Province province = Province.fromJson(provinceItem['data']); Ward ward = await apiServices.getWardByID(wardID);
String districtBody = await apiServices.getDistrictByID(districtID);
final districtItem = jsonDecode(districtBody);
District district = District.fromJson(districtItem['data']);
String wardBody = await apiServices.getWardByID(wardID);
final wardItem = jsonDecode(wardBody);
Ward ward = Ward.fromJson(wardItem['data']);
return "${ward.fullName}, ${district.fullName}, ${province.fullName}"; return "${ward.fullName}, ${district.fullName}, ${province.fullName}";
} }
return appLocalization(context).no_data_message; return appLocalization(context).no_data_message;
} }
String checkStateDevice(BuildContext context, int state) { String checkStateDevice(BuildContext context, int state) {
String message = appLocalization(context).no_data_message; String message = appLocalization(context).no_data_message;
if (state == 1) { if (state == 1) {
@@ -157,11 +159,11 @@ class DeviceUtils {
} else if (state == 0) { } else if (state == 0) {
return const Color(0xFF9EF16D); return const Color(0xFF9EF16D);
} else if (state == 2) { } else if (state == 2) {
return const Color(0xFFF5EF44);; return const Color(0xFFF5EF44);
} else if (state == -1) { } else if (state == -1) {
return const Color(0xFFBBBAC2);; return const Color(0xFFBBBAC2);
} else { } else {
return const Color(0xFFF5EF44);; return const Color(0xFFF5EF44);
} }
} }

View File

@@ -23,7 +23,8 @@ Future<LocationPermission> checkAndRequestPermission() async {
} }
if (permission == LocationPermission.deniedForever) { 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; return permission;
} }
@@ -56,7 +57,8 @@ Future<void> requestLocationPermission() async {
LocationPermission permission = await checkAndRequestPermission(); LocationPermission permission = await checkAndRequestPermission();
// Bước 3: Nếu quyền được cấp, lấy vị trí // 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(); await getCurrentPosition();
} }
} }

View File

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

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