Complete refactoring SFM App Source Code
44
.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
45
.metadata
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
base_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
- platform: android
|
||||||
|
create_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
base_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
- platform: ios
|
||||||
|
create_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
base_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
- platform: linux
|
||||||
|
create_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
base_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
- platform: macos
|
||||||
|
create_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
base_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
- platform: web
|
||||||
|
create_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
base_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
- platform: windows
|
||||||
|
create_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
base_revision: d3d8effc686d73e0114d71abdcccef63fa1f25d2
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
29
analysis_options.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at
|
||||||
|
# https://dart-lang.github.io/linter/lints/index.html.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
13
android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
84
android/app/build.gradle
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
// throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
// START: FlutterFire Configuration
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
// END: FlutterFire Configuration
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "com.example.sfm_app"
|
||||||
|
compileSdkVersion 34
|
||||||
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
// Flag to enable support for the new language APIs
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// START: Flutter Local Notifications
|
||||||
|
multiDexEnabled true
|
||||||
|
// END: Flutter Local Notifications
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.sfm_app"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
|
minSdkVersion 23
|
||||||
|
targetSdkVersion 33
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'com.google.firebase:firebase-messaging-directboot:20.2.0'
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
|
||||||
|
implementation 'androidx.window:window:1.0.0'
|
||||||
|
implementation 'androidx.window:window-java:1.0.0'
|
||||||
|
}
|
||||||
29
android/app/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "910110439150",
|
||||||
|
"project_id": "sfm-notification",
|
||||||
|
"storage_bucket": "sfm-notification.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:910110439150:android:c3dbc3b4a85d7cb75b65ff",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.example.sfm_app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyADdZ66_q4HALlb0-yEGgeyGnsbwmlvDsA"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
76
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
|
<!-- NOTE: the example app requests USE_EXACT_ALARM to make it easier to run the app.
|
||||||
|
Developers will need to check if their own app needs to use SCHEDULE_EXACT_ALARM instead -->
|
||||||
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<!-- NOTE: Special use was selected as it's the closest match for this example app.
|
||||||
|
apps should specify the appropriate permission for their use cases. -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<!-- START Permissions Package -->
|
||||||
|
<!-- Permissions options for the `camera` group -->
|
||||||
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
|
<!-- Permissions options for the `location` group -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
<!-- END Permissions Package -->
|
||||||
|
<application
|
||||||
|
android:label="sfm_app"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<meta-data android:name="com.google.android.geo.API_KEY"
|
||||||
|
android:value="AIzaSyA9C7Pmxw6Gw3H2mM4WA_XGngRIIr2VS7k"/>
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:showWhenLocked="true"
|
||||||
|
android:turnScreenOn="true"
|
||||||
|
android:exported="true">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
|
android:value="high_importance_channel" />
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
<service
|
||||||
|
android:name="com.dexterous.flutterlocalnotifications.ForegroundService"
|
||||||
|
android:exported="false"
|
||||||
|
android:stopWithTask="false"/>
|
||||||
|
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" />
|
||||||
|
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
|
||||||
|
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
|
||||||
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
</application>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example.sfm_app
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
}
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/raw/warning_alarm.mp3
Normal file
18
android/app/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
18
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
34
android/build.gradle
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.8.20'
|
||||||
|
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:7.3.1'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
3
android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||||
11
android/settings.gradle
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||||
BIN
assets/icons/en_icon.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/icons/fire_station_marker.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/icons/flame_icon.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
assets/icons/hospital_marker.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
assets/icons/normal_icon.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/icons/offline_icon.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/icons/vi_icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/images/background_bottom.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/images/background_top.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
assets/images/error_page.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
assets/images/fire_warning.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/images/low_battery.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
1
firebase.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"flutter":{"platforms":{"android":{"default":{"projectId":"sfm-notification","appId":"1:910110439150:android:c3dbc3b4a85d7cb75b65ff","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"sfm-notification","configurations":{"android":"1:910110439150:android:c3dbc3b4a85d7cb75b65ff","ios":"1:910110439150:ios:1d111f0cdd0240a35b65ff","macos":"1:910110439150:ios:1d111f0cdd0240a35b65ff","web":"1:910110439150:web:b3dc22fa9ecb953b5b65ff","windows":"1:910110439150:web:7c413e6bc22555575b65ff"}}}}}}
|
||||||
34
ios/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>11.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
1
ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
68
ios/Podfile
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
# You can remove unused permissions here
|
||||||
|
# for more information: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h
|
||||||
|
# e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
|
||||||
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
|
'$(inherited)',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.calendar
|
||||||
|
# 'PERMISSION_EVENTS=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.calendarFullAccess
|
||||||
|
# 'PERMISSION_EVENTS_FULL_ACCESS=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.reminders
|
||||||
|
# 'PERMISSION_REMINDERS=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.contacts
|
||||||
|
# 'PERMISSION_CONTACTS=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.camera
|
||||||
|
'PERMISSION_CAMERA=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.microphone
|
||||||
|
# 'PERMISSION_MICROPHONE=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.speech
|
||||||
|
# 'PERMISSION_SPEECH_RECOGNIZER=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.photos
|
||||||
|
# 'PERMISSION_PHOTOS=1',
|
||||||
|
|
||||||
|
## The 'PERMISSION_LOCATION' macro enables the `locationWhenInUse` and `locationAlways` permission. If
|
||||||
|
## the application only requires `locationWhenInUse`, only specify the `PERMISSION_LOCATION_WHENINUSE`
|
||||||
|
## macro.
|
||||||
|
##
|
||||||
|
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
|
||||||
|
'PERMISSION_LOCATION=1',
|
||||||
|
'PERMISSION_LOCATION_WHENINUSE=0',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.notification
|
||||||
|
'PERMISSION_NOTIFICATIONS=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.mediaLibrary
|
||||||
|
# 'PERMISSION_MEDIA_LIBRARY=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.sensors
|
||||||
|
# 'PERMISSION_SENSORS=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.bluetooth
|
||||||
|
# 'PERMISSION_BLUETOOTH=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.appTrackingTransparency
|
||||||
|
# 'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.criticalAlerts
|
||||||
|
'PERMISSION_CRITICAL_ALERTS=1',
|
||||||
|
|
||||||
|
## dart: PermissionGroup.criticalAlerts
|
||||||
|
'PERMISSION_ASSISTANT=1',
|
||||||
|
]
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
613
ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,613 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; 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>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807E294A63A400263BE5 /* Frameworks */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 1300;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.sfmApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.sfmApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.sfmApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.sfmApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.sfmApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.sfmApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
98
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1300"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
25
ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import UIKit
|
||||||
|
import Flutter
|
||||||
|
import flutter_local_notifications
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
// This is required to make any communication available in the action isolate.
|
||||||
|
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
|
||||||
|
GeneratedPluginRegistrant.register(with: registry)
|
||||||
|
}
|
||||||
|
GMSServices.provideAPIKey("AIzaSyDI8b-PUgKUgj5rHdtgEHCwWjUXYJrqYhE")
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
70
ios/Runner/Info.plist
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Sfm App</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>sfm_app</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<false/>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<!-- START Permissions Package -->
|
||||||
|
<!-- Permission options for the `location` group -->
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>Need location when in use</string>
|
||||||
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||||
|
<string>Always and when in use!</string>
|
||||||
|
<key>NSLocationUsageDescription</key>
|
||||||
|
<string>Older devices need location.</string>
|
||||||
|
<key>NSLocationAlwaysUsageDescription</key>
|
||||||
|
<string>Can I have location always?</string>
|
||||||
|
<!-- Permission options for the `camera` group -->
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>camera</string>
|
||||||
|
<!-- END Permissions Package -->
|
||||||
|
<!-- START BARCODE SCANNER -->
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Camera permission is required for barcode scanning.</string>
|
||||||
|
<!-- END BARCODE SCANNER -->
|
||||||
|
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
4
l10n.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
arb-dir: lib/product/lang/l10n
|
||||||
|
template-arb-file: app_en.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
|
untranslated-messages-file: sfmTranslateErrors.txt
|
||||||
18
lib/feature/auth/login/bloc/login_bloc.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import '../../../../product/base/bloc/base_bloc.dart';
|
||||||
|
|
||||||
|
class LoginBloc extends BlocBase{
|
||||||
|
|
||||||
|
final loginRequest = StreamController<Map<String,dynamic>>.broadcast();
|
||||||
|
StreamSink<Map<String,dynamic>> get sinkLoginRequest => loginRequest.sink;
|
||||||
|
Stream<Map<String,dynamic>> get streamLoginRequest => loginRequest.stream;
|
||||||
|
|
||||||
|
final isShowPassword = StreamController<bool>.broadcast();
|
||||||
|
StreamSink<bool> get sinkIsShowPassword => isShowPassword.sink;
|
||||||
|
Stream<bool> get streamIsShowPassword => isShowPassword.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
lib/feature/auth/login/model/login_model.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
class LoginModel {
|
||||||
|
int? exp;
|
||||||
|
int? iat;
|
||||||
|
String? id;
|
||||||
|
String? iss;
|
||||||
|
String? role;
|
||||||
|
|
||||||
|
LoginModel({this.exp, this.iat, this.id, this.iss, this.role});
|
||||||
|
|
||||||
|
LoginModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
exp = json['exp'];
|
||||||
|
iat = json['iat'];
|
||||||
|
id = json['id'];
|
||||||
|
iss = json['iss'];
|
||||||
|
role = json['role'];
|
||||||
|
}
|
||||||
|
}
|
||||||
234
lib/feature/auth/login/screen/login_screen.dart
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../../../product/constant/app/api_path_constant.dart';
|
||||||
|
import '../../../../product/constant/enums/app_route_enums.dart';
|
||||||
|
import '../model/login_model.dart';
|
||||||
|
import '../../../../product/cache/local_manager.dart';
|
||||||
|
import '../../../../product/constant/enums/local_keys_enums.dart';
|
||||||
|
import '../../../../product/services/api_services.dart';
|
||||||
|
import '../../../../product/shared/shared_snack_bar.dart';
|
||||||
|
import '../../../../product/constant/icon/icon_constants.dart';
|
||||||
|
import '../../../../product/extention/context_extention.dart';
|
||||||
|
import '../../../../product/constant/image/image_constants.dart';
|
||||||
|
import '../../../../product/services/language_services.dart';
|
||||||
|
import '../bloc/login_bloc.dart';
|
||||||
|
import '../../../../product/base/bloc/base_bloc.dart';
|
||||||
|
import '../../../../product/shared/shared_background.dart';
|
||||||
|
|
||||||
|
class LoginScreen extends StatefulWidget {
|
||||||
|
const LoginScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginScreen> createState() => _LoginScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginScreenState extends State<LoginScreen> {
|
||||||
|
late LoginBloc loginBloc;
|
||||||
|
Map<String, dynamic> loginRequest = {"username": "", "password": ""};
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
bool isShowPassword = true;
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loginBloc = BlocProvider.of(context);
|
||||||
|
checkLogin(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SharedBackground(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: Image.asset(
|
||||||
|
ImageConstants.instance.getImage("logo"),
|
||||||
|
height: context.dynamicHeight(0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: context.mediumValue,
|
||||||
|
),
|
||||||
|
StreamBuilder<Map<String, dynamic>>(
|
||||||
|
stream: loginBloc.streamLoginRequest,
|
||||||
|
builder: (context, loginResquestSnapshot) {
|
||||||
|
return Padding(
|
||||||
|
padding: context.paddingLow,
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == "null" || value!.isEmpty) {
|
||||||
|
return appLocalization(context)
|
||||||
|
.login_account_not_empty;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (username) {
|
||||||
|
loginRequest["username"] = username!;
|
||||||
|
loginBloc.sinkLoginRequest.add(loginRequest);
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText:
|
||||||
|
appLocalization(context).login_account_hint,
|
||||||
|
prefixIcon: Padding(
|
||||||
|
padding: context.dynamicPadding(16),
|
||||||
|
child: IconConstants.instance
|
||||||
|
.getMaterialIcon(Icons.person),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: context.lowValue),
|
||||||
|
StreamBuilder<bool>(
|
||||||
|
stream: loginBloc.streamIsShowPassword,
|
||||||
|
builder: (context, isShowPassSnapshot) {
|
||||||
|
return TextFormField(
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
obscureText:
|
||||||
|
isShowPassSnapshot.data ?? isShowPassword,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return appLocalization(context)
|
||||||
|
.login_password_not_empty;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (password) {
|
||||||
|
loginRequest["password"] = password!;
|
||||||
|
loginBloc.sinkLoginRequest.add(loginRequest);
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: appLocalization(context)
|
||||||
|
.login_password_hint,
|
||||||
|
prefixIcon: Padding(
|
||||||
|
padding: context.dynamicPadding(16),
|
||||||
|
child: IconConstants.instance
|
||||||
|
.getMaterialIcon(Icons.lock),
|
||||||
|
),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
isShowPassword = !isShowPassword;
|
||||||
|
loginBloc.sinkIsShowPassword
|
||||||
|
.add(isShowPassword);
|
||||||
|
},
|
||||||
|
icon: isShowPassword
|
||||||
|
? IconConstants.instance
|
||||||
|
.getMaterialIcon(
|
||||||
|
Icons.visibility,
|
||||||
|
)
|
||||||
|
: IconConstants.instance
|
||||||
|
.getMaterialIcon(
|
||||||
|
Icons.visibility_off,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: context.lowValue,
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: const ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStatePropertyAll(Colors.blue),
|
||||||
|
foregroundColor:
|
||||||
|
MaterialStatePropertyAll(Colors.white),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
validate();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
appLocalization(context).login_button_content),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate() async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
var data =
|
||||||
|
await apiServices.login(APIPathConstants.LOGIN_PATH, loginRequest);
|
||||||
|
if (data != "") {
|
||||||
|
Map<String, dynamic> tokenData = jsonDecode(data);
|
||||||
|
String token = tokenData['token'];
|
||||||
|
LocaleManager.instance.setString(PreferencesKeys.TOKEN, token);
|
||||||
|
String userToken = getBaseToken(token);
|
||||||
|
var decode = decodeBase64Token(userToken);
|
||||||
|
LoginModel loginModel =
|
||||||
|
LoginModel.fromJson(jsonDecode(decode) as Map<String, dynamic>);
|
||||||
|
LocaleManager.instance.setString(PreferencesKeys.UID, loginModel.id!);
|
||||||
|
LocaleManager.instance.setInt(PreferencesKeys.EXP, loginModel.exp!);
|
||||||
|
LocaleManager.instance
|
||||||
|
.setString(PreferencesKeys.ROLE, loginModel.role!);
|
||||||
|
context.goNamed(AppRoutes.HOME.name);
|
||||||
|
} else {
|
||||||
|
showErrorTopSnackBarCustom(
|
||||||
|
context, appLocalization(context).login_incorrect_usernameOrPass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkLogin(BuildContext context) async {
|
||||||
|
// ThemeNotifier themeNotifier = context.watch<ThemeNotifier>();
|
||||||
|
await LocaleManager.prefrencesInit();
|
||||||
|
// String theme = LocaleManager.instance.getStringValue(PreferencesKeys.THEME);
|
||||||
|
String token = LocaleManager.instance.getStringValue(PreferencesKeys.TOKEN);
|
||||||
|
int exp = LocaleManager.instance.getIntValue(PreferencesKeys.EXP);
|
||||||
|
log("Token cu: ${LocaleManager.instance.getStringValue(PreferencesKeys.TOKEN)}");
|
||||||
|
log("UID: ${LocaleManager.instance.getStringValue(PreferencesKeys.UID)}");
|
||||||
|
log("EXP: ${LocaleManager.instance.getIntValue(PreferencesKeys.EXP)}");
|
||||||
|
log("Role: ${LocaleManager.instance.getStringValue(PreferencesKeys.ROLE)}");
|
||||||
|
log("Theme: ${LocaleManager.instance.getStringValue(PreferencesKeys.THEME)}");
|
||||||
|
log("Lang: ${LocaleManager.instance.getStringValue(PreferencesKeys.LANGUAGE_CODE)}");
|
||||||
|
// log("Theme: $theme");
|
||||||
|
// if (theme == AppThemes.DARK.name) {
|
||||||
|
// themeNotifier.changeValue(AppThemes.DARK);
|
||||||
|
// } else {
|
||||||
|
// themeNotifier.changeValue(AppThemes.LIGHT);
|
||||||
|
// }
|
||||||
|
int timeNow = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
if (token != "" && (exp - timeNow) > 7200) {
|
||||||
|
context.goNamed(AppRoutes.HOME.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseToken(String token) {
|
||||||
|
List<String> parts = token.split('.');
|
||||||
|
String userToken = parts[1];
|
||||||
|
return userToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeBase64Token(String value) {
|
||||||
|
List<int> res = base64.decode(base64.normalize(value));
|
||||||
|
return utf8.decode(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
lib/feature/bell/bell_bloc.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import '../../product/base/bloc/base_bloc.dart';
|
||||||
|
import 'bell_model.dart';
|
||||||
|
|
||||||
|
class BellBloc extends BlocBase {
|
||||||
|
|
||||||
|
final bellItems = StreamController<List<BellItems>>.broadcast();
|
||||||
|
StreamSink<List<BellItems>> get sinkBellItems => bellItems.sink;
|
||||||
|
Stream<List<BellItems>> get streamBellItems => bellItems.stream;
|
||||||
|
|
||||||
|
final isLoading = StreamController<bool>.broadcast();
|
||||||
|
StreamSink<bool> get sinkIsLoading => isLoading.sink;
|
||||||
|
Stream<bool> get streamIsLoading => isLoading.stream;
|
||||||
|
|
||||||
|
final hasMore = StreamController<bool>.broadcast();
|
||||||
|
StreamSink<bool> get sinkHasMore => hasMore.sink;
|
||||||
|
Stream<bool> get streamHasMore => hasMore.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {}
|
||||||
|
}
|
||||||
75
lib/feature/bell/bell_model.dart
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
class Bell {
|
||||||
|
int? offset;
|
||||||
|
String? result;
|
||||||
|
List<BellItems>? items;
|
||||||
|
|
||||||
|
Bell({
|
||||||
|
this.offset,
|
||||||
|
this.result,
|
||||||
|
this.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
Bell.fromJson(Map<String, dynamic> json) {
|
||||||
|
offset = json["offset"];
|
||||||
|
result = json["result"];
|
||||||
|
if (json['items'] != null) {
|
||||||
|
items = [];
|
||||||
|
json['items'].forEach((v) {
|
||||||
|
items!.add(BellItems.fromJson(v));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
items = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BellItems {
|
||||||
|
String? id;
|
||||||
|
String? notifiType;
|
||||||
|
String? userId;
|
||||||
|
String? eventType;
|
||||||
|
ItemDetail? itemDetail;
|
||||||
|
int? status;
|
||||||
|
DateTime? createdAt;
|
||||||
|
DateTime? updatedAt;
|
||||||
|
|
||||||
|
BellItems(
|
||||||
|
{this.id, this.eventType, this.status, this.createdAt, this.updatedAt});
|
||||||
|
|
||||||
|
BellItems.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json["id"];
|
||||||
|
notifiType = json["notifi_type"];
|
||||||
|
userId = json["user_id"];
|
||||||
|
eventType = json["event_type"];
|
||||||
|
status = json["status"];
|
||||||
|
itemDetail =
|
||||||
|
json['detail'] != null ? ItemDetail.fromJson(json['detail']) : null;
|
||||||
|
createdAt = DateTime.parse(json["created_at"]);
|
||||||
|
updatedAt = DateTime.parse(json["updated_at"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<BellItems> fromJsonDynamicList(List<dynamic> list) {
|
||||||
|
return list.map((e) => BellItems.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemDetail {
|
||||||
|
String? sourceId;
|
||||||
|
String? sourceName;
|
||||||
|
String? targetId;
|
||||||
|
String? targetName;
|
||||||
|
|
||||||
|
ItemDetail({
|
||||||
|
this.sourceId,
|
||||||
|
this.sourceName,
|
||||||
|
this.targetId,
|
||||||
|
this.targetName,
|
||||||
|
});
|
||||||
|
|
||||||
|
ItemDetail.fromJson(Map<String, dynamic> json) {
|
||||||
|
sourceId = json["source_id"];
|
||||||
|
sourceName = json["source_name"];
|
||||||
|
targetId = json["target_id"];
|
||||||
|
targetName = json["target_name"];
|
||||||
|
}
|
||||||
|
}
|
||||||
253
lib/feature/bell/bell_screen.dart
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../product/extention/context_extention.dart';
|
||||||
|
import '../../product/services/language_services.dart';
|
||||||
|
import '../../product/constant/enums/app_theme_enums.dart';
|
||||||
|
import 'bell_bloc.dart';
|
||||||
|
import '../../product/base/bloc/base_bloc.dart';
|
||||||
|
import '../../product/services/api_services.dart';
|
||||||
|
import 'bell_model.dart';
|
||||||
|
|
||||||
|
class BellScreen extends StatefulWidget {
|
||||||
|
const BellScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BellScreen> createState() => _BellScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BellScreenState extends State<BellScreen> {
|
||||||
|
late BellBloc bellBloc;
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
Timer? getBellTimer;
|
||||||
|
int offset = 0;
|
||||||
|
Bell bell = Bell();
|
||||||
|
List<BellItems> items = [];
|
||||||
|
bool check = true;
|
||||||
|
bool hasMore = true;
|
||||||
|
final controller = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
bellBloc = BlocProvider.of(context);
|
||||||
|
getBellNotification(offset);
|
||||||
|
controller.addListener(
|
||||||
|
() {
|
||||||
|
if (controller.position.maxScrollExtent == controller.offset) {
|
||||||
|
offset += 20;
|
||||||
|
getBellNotification(offset);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<bool>(
|
||||||
|
stream: bellBloc.streamIsLoading,
|
||||||
|
builder: (context, isLoadingSnapshot) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(appLocalization(context).bell_page_title),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: StreamBuilder<List<BellItems>>(
|
||||||
|
stream: bellBloc.streamBellItems,
|
||||||
|
initialData: items,
|
||||||
|
builder: (context, bellSnapshot) {
|
||||||
|
return check
|
||||||
|
? Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: context.highValue,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: bellSnapshot.data?.isEmpty ?? true
|
||||||
|
? Center(
|
||||||
|
child: Text(
|
||||||
|
appLocalization(context).bell_page_no_items_body),
|
||||||
|
)
|
||||||
|
: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: refresh,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: controller,
|
||||||
|
itemCount: (bellSnapshot.data!.length + 1),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index < bellSnapshot.data!.length) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
List<String> read = [];
|
||||||
|
read.add(bellSnapshot.data![index].id!);
|
||||||
|
int code = await apiServices
|
||||||
|
.updateStatusOfNotification(read);
|
||||||
|
if (code == 200) {
|
||||||
|
read.clear();
|
||||||
|
} else {
|
||||||
|
read.clear();
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
getBellEvent(
|
||||||
|
context,
|
||||||
|
bellSnapshot.data![index]
|
||||||
|
.itemDetail!.sourceName!,
|
||||||
|
bellSnapshot
|
||||||
|
.data![index].eventType!,
|
||||||
|
bellSnapshot.data![index]
|
||||||
|
.itemDetail!.targetName!,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 3,
|
||||||
|
style:
|
||||||
|
const TextStyle(fontSize: 15),
|
||||||
|
),
|
||||||
|
trailing: Text(
|
||||||
|
timeAgo(
|
||||||
|
context,
|
||||||
|
bellSnapshot
|
||||||
|
.data![index].createdAt!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: StreamBuilder<bool>(
|
||||||
|
stream: bellBloc.streamHasMore,
|
||||||
|
builder: (context, hasMoreSnapshot) {
|
||||||
|
return Center(
|
||||||
|
child: hasMoreSnapshot.data ?? hasMore
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: Text(
|
||||||
|
appLocalization(context)
|
||||||
|
.bell_read_all,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
check = true;
|
||||||
|
offset = 0;
|
||||||
|
items.clear();
|
||||||
|
bellBloc.sinkBellItems.add(items);
|
||||||
|
hasMore = true;
|
||||||
|
getBellNotification(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getBellNotification(int offset) async {
|
||||||
|
bell = await apiServices.getBellNotifications(
|
||||||
|
offset.toString(), (offset + 20).toString());
|
||||||
|
|
||||||
|
if (bell.items!.isEmpty) {
|
||||||
|
hasMore = false;
|
||||||
|
bellBloc.sinkHasMore.add(hasMore);
|
||||||
|
} else {
|
||||||
|
for (var item in bell.items!) {
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bellBloc.bellItems.add(items);
|
||||||
|
check = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkStatus(List<BellItems> bells) {
|
||||||
|
for (var bell in bells) {
|
||||||
|
if (bell.status == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Color> colorByTheme(int status) async {
|
||||||
|
String theme = await apiServices.checkTheme();
|
||||||
|
if (theme == AppThemes.LIGHT.name && status == 1) {
|
||||||
|
return Colors.white;
|
||||||
|
} else if (theme == AppThemes.DARK.name && status == 1) {
|
||||||
|
return Colors.black;
|
||||||
|
} else {
|
||||||
|
return const Color.fromARGB(255, 90, 175, 214);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String timeAgo(BuildContext context, DateTime dateTime) {
|
||||||
|
final duration = DateTime.now().difference(dateTime);
|
||||||
|
|
||||||
|
if (duration.inDays > 0) {
|
||||||
|
return '${duration.inDays} ${appLocalization(context).bell_days_ago}';
|
||||||
|
} else if (duration.inHours > 0) {
|
||||||
|
return '${duration.inHours} ${appLocalization(context).bell_hours_ago}';
|
||||||
|
} else if (duration.inMinutes > 0) {
|
||||||
|
return '${duration.inMinutes} ${appLocalization(context).bell_minutes_ago}';
|
||||||
|
} else {
|
||||||
|
return appLocalization(context).bell_just_now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getBellEvent(BuildContext context, String deviceName, String eventCode,
|
||||||
|
String targetName) {
|
||||||
|
String message = "";
|
||||||
|
|
||||||
|
if (eventCode == "001") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).device_title} $deviceName ${appLocalization(context).disconnect_message_lowercase}";
|
||||||
|
} else if (eventCode == "002") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).device_title} $deviceName ${appLocalization(context).gf_connected_lowercase}";
|
||||||
|
} else if (eventCode == "003") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).device_title} $deviceName ${appLocalization(context).smoke_detecting_message_lowercase}";
|
||||||
|
} else if (eventCode == "004") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).device_title} $deviceName ${appLocalization(context).error_message_lowercase}";
|
||||||
|
} else if (eventCode == "005") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).device_title} $deviceName ${appLocalization(context).bell_operate_normal}";
|
||||||
|
} else if (eventCode == "006") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).bell_battery_device} $deviceName ${appLocalization(context).low_message_lowercase}";
|
||||||
|
} else if (eventCode == "101") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).bell_user_uppercase} ${appLocalization(context).bell_user_joined_group}";
|
||||||
|
} else if (eventCode == "102") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).bell_user_uppercase} $deviceName ${appLocalization(context).bell_leave_group} $targetName ";
|
||||||
|
} else if (eventCode == "103") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).device_title} $deviceName ${appLocalization(context).bell_user_added_group} $targetName";
|
||||||
|
} else if (eventCode == "104") {
|
||||||
|
message =
|
||||||
|
"${appLocalization(context).device_title} $deviceName ${appLocalization(context).bell_user_kick_group} $targetName";
|
||||||
|
} else {
|
||||||
|
message = appLocalization(context).bell_invalid_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
lib/feature/devices/add_new_device_widget.dart
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../product/utils/response_status_utils.dart';
|
||||||
|
import '../../product/constant/enums/role_enums.dart';
|
||||||
|
import '../../product/services/api_services.dart';
|
||||||
|
import '../../product/utils/qr_utils.dart';
|
||||||
|
import '../../product/constant/icon/icon_constants.dart';
|
||||||
|
import '../../product/extention/context_extention.dart';
|
||||||
|
import '../../product/services/language_services.dart';
|
||||||
|
|
||||||
|
addNewDevice(BuildContext context, String role) async {
|
||||||
|
TextEditingController extIDController = TextEditingController(text: "");
|
||||||
|
TextEditingController deviceNameController = TextEditingController(text: "");
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
dismissDirection: DismissDirection.none,
|
||||||
|
duration: context.dynamicMinutesDuration(1),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('${appLocalization(context).add_device_title}: ',
|
||||||
|
style: context.titleMediumTextStyle),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||||
|
},
|
||||||
|
icon: IconConstants.instance.getMaterialIcon(Icons.close)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
controller: extIDController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: appLocalization(context).input_extID_device_hintText,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
String result =
|
||||||
|
await QRScanUtils.instance.scanQR(context);
|
||||||
|
extIDController.text = result;
|
||||||
|
},
|
||||||
|
icon: IconConstants.instance
|
||||||
|
.getMaterialIcon(Icons.qr_code_scanner_outlined))),
|
||||||
|
),
|
||||||
|
role == RoleEnums.ADMIN.name
|
||||||
|
? TextField(
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
controller: deviceNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText:
|
||||||
|
appLocalization(context).input_name_device_hintText),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
Center(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
String extID = extIDController.text;
|
||||||
|
String deviceName = deviceNameController.text;
|
||||||
|
addDevices(context, role, extID, deviceName);
|
||||||
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||||
|
},
|
||||||
|
child: Text(appLocalization(context).add_button_content)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addDevices(
|
||||||
|
BuildContext context, String role, String extID, String deviceName) async {
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
Map<String, dynamic> body = {};
|
||||||
|
if (role == RoleEnums.ADMIN.name) {
|
||||||
|
body = {"ext_id": extID, "name": deviceName};
|
||||||
|
int statusCode = await apiServices.createDeviceByAdmin(body);
|
||||||
|
showSnackBarResponseByStatusCode(
|
||||||
|
context,
|
||||||
|
statusCode,
|
||||||
|
appLocalization(context).notification_create_device_success,
|
||||||
|
appLocalization(context).notification_create_device_failed);
|
||||||
|
} else {
|
||||||
|
body = {"ext_id": extID};
|
||||||
|
int statusCode = await apiServices.registerDevice(body);
|
||||||
|
showSnackBarResponseByStatusCode(
|
||||||
|
context,
|
||||||
|
statusCode,
|
||||||
|
appLocalization(context).notification_add_device_success,
|
||||||
|
appLocalization(context).notification_device_not_exist);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
lib/feature/devices/delete_device_widget.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../product/constant/enums/role_enums.dart';
|
||||||
|
import '../../product/services/api_services.dart';
|
||||||
|
import '../../product/services/language_services.dart';
|
||||||
|
import '../../product/utils/response_status_utils.dart';
|
||||||
|
|
||||||
|
handleDeleteDevice(BuildContext context, String thingID, String role) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(appLocalization(dialogContext).delete_device_dialog_title),
|
||||||
|
content:
|
||||||
|
Text(appLocalization(dialogContext).delete_device_dialog_content),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: Text(appLocalization(dialogContext).cancel_button_content),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text(
|
||||||
|
appLocalization(dialogContext).delete_button_content,
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
deleteOrUnregisterDevice(context, thingID, role);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOrUnregisterDevice(
|
||||||
|
BuildContext context, String thingID, String role) async {
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
if (role == RoleEnums.USER.name) {
|
||||||
|
Map<String, dynamic> body = {
|
||||||
|
"thing_id": thingID,
|
||||||
|
};
|
||||||
|
int statusCode = await apiServices.unregisterDevice(body);
|
||||||
|
showSnackBarResponseByStatusCode(
|
||||||
|
context,
|
||||||
|
statusCode,
|
||||||
|
appLocalization(context).notification_delete_device_success,
|
||||||
|
appLocalization(context).notification_delete_device_failed);
|
||||||
|
} else {
|
||||||
|
int statusCode = await apiServices.deleteDeviceByAdmin(thingID);
|
||||||
|
showSnackBarResponseByStatusCode(
|
||||||
|
context,
|
||||||
|
statusCode,
|
||||||
|
appLocalization(context).notification_delete_device_success,
|
||||||
|
appLocalization(context).notification_delete_device_failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
lib/feature/devices/device_detail/device_detail_bloc.dart
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:sfm_app/product/services/api_services.dart';
|
||||||
|
import 'package:sfm_app/product/utils/device_utils.dart';
|
||||||
|
|
||||||
|
import '../device_model.dart';
|
||||||
|
|
||||||
|
import '../../../product/base/bloc/base_bloc.dart';
|
||||||
|
|
||||||
|
class DetailDeviceBloc extends BlocBase {
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
|
||||||
|
final deviceInfo = StreamController<Device>.broadcast();
|
||||||
|
StreamSink<Device> get sinkDeviceInfo => deviceInfo.sink;
|
||||||
|
Stream<Device> get streamDeviceInfo => deviceInfo.stream;
|
||||||
|
|
||||||
|
final deviceSensor = StreamController<Map<String, dynamic>>.broadcast();
|
||||||
|
StreamSink<Map<String, dynamic>> get sinkDeviceSensor => deviceSensor.sink;
|
||||||
|
Stream<Map<String, dynamic>> get streamDeviceSensor => deviceSensor.stream;
|
||||||
|
|
||||||
|
final deviceLocation = StreamController<String>.broadcast();
|
||||||
|
StreamSink<String> get sinkDeviceLocation => deviceLocation.sink;
|
||||||
|
Stream<String> get streamDeviceLocation => deviceLocation.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {}
|
||||||
|
|
||||||
|
void getDeviceDetail(
|
||||||
|
BuildContext context,
|
||||||
|
String thingID,
|
||||||
|
Completer<GoogleMapController> controller,
|
||||||
|
) async {
|
||||||
|
String body = await apiServices.getDeviceInfomation(thingID);
|
||||||
|
if (body != "") {
|
||||||
|
final data = jsonDecode(body);
|
||||||
|
Device device = Device.fromJson(data);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void findLocation(BuildContext context, String areaPath) async {
|
||||||
|
String fullLocation =
|
||||||
|
await DeviceUtils.instance.getFullDeviceLocation(context, areaPath);
|
||||||
|
sinkDeviceLocation.add(fullLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
361
lib/feature/devices/device_detail/device_detail_screen.dart
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:simple_ripple_animation/simple_ripple_animation.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import '../device_model.dart';
|
||||||
|
import '../../../product/base/bloc/base_bloc.dart';
|
||||||
|
import '../../../product/extention/context_extention.dart';
|
||||||
|
import '../../../product/services/language_services.dart';
|
||||||
|
import '../../../product/utils/device_utils.dart';
|
||||||
|
|
||||||
|
import '../../../product/constant/icon/icon_constants.dart';
|
||||||
|
import 'device_detail_bloc.dart';
|
||||||
|
|
||||||
|
class DetailDeviceScreen extends StatefulWidget {
|
||||||
|
const DetailDeviceScreen({super.key, required this.thingID});
|
||||||
|
final String thingID;
|
||||||
|
@override
|
||||||
|
State<DetailDeviceScreen> createState() => _DetailDeviceScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
|
||||||
|
List<String> imageAssets = [
|
||||||
|
IconConstants.instance.getIcon("normal_icon"),
|
||||||
|
IconConstants.instance.getIcon("offline_icon"),
|
||||||
|
IconConstants.instance.getIcon("flame_icon"),
|
||||||
|
];
|
||||||
|
String stateImgAssets(int state) {
|
||||||
|
String imgStringAsset;
|
||||||
|
if (state == 0) {
|
||||||
|
imgStringAsset = imageAssets[0];
|
||||||
|
} else if (state == 1) {
|
||||||
|
imgStringAsset = imageAssets[1];
|
||||||
|
} else {
|
||||||
|
imgStringAsset = imageAssets[2];
|
||||||
|
}
|
||||||
|
return imgStringAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
late DetailDeviceBloc detailDeviceBloc;
|
||||||
|
Completer<GoogleMapController> controller = Completer();
|
||||||
|
CameraPosition initialCamera = const CameraPosition(
|
||||||
|
target: LatLng(20.966048511844402, 105.74977710843086), zoom: 15);
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
detailDeviceBloc = BlocProvider.of(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
return StreamBuilder<Device>(
|
||||||
|
stream: detailDeviceBloc.streamDeviceInfo,
|
||||||
|
builder: (context, deviceSnapshot) {
|
||||||
|
if (deviceSnapshot.data?.extId == null) {
|
||||||
|
detailDeviceBloc.getDeviceDetail(context, widget.thingID, controller);
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return StreamBuilder<Map<String, dynamic>>(
|
||||||
|
stream: detailDeviceBloc.streamDeviceSensor,
|
||||||
|
builder: (context, sensorSnapshot) {
|
||||||
|
if (sensorSnapshot.data != null) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(appLocalization(context).detail_message),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// device Name
|
||||||
|
Card(
|
||||||
|
child: Container(
|
||||||
|
width: context.dynamicWidth(1),
|
||||||
|
height: context.highValue,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'${appLocalization(context).device_title}: ${deviceSnapshot.data!.name}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Tinh trang va nhiet do
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: Container(
|
||||||
|
width: (screenWidth - 20) / 2,
|
||||||
|
height: context.highValue,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.fromLTRB(5, 5, 0, 5),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 25,
|
||||||
|
width: 25,
|
||||||
|
child: RippleAnimation(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getColorRiple(
|
||||||
|
deviceSnapshot.data!.state!),
|
||||||
|
delay:
|
||||||
|
const Duration(milliseconds: 800),
|
||||||
|
repeat: true,
|
||||||
|
minRadius: 40,
|
||||||
|
ripplesCount: 6,
|
||||||
|
duration: const Duration(
|
||||||
|
milliseconds: 6 * 300),
|
||||||
|
child: CircleAvatar(
|
||||||
|
minRadius: 20,
|
||||||
|
maxRadius: 20,
|
||||||
|
backgroundImage: AssetImage(
|
||||||
|
stateImgAssets(
|
||||||
|
deviceSnapshot.data!.state!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: context.lowValue,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
DeviceUtils.instance.checkStateDevice(
|
||||||
|
context,
|
||||||
|
deviceSnapshot.data!.state!,
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: SizedBox(
|
||||||
|
width: (screenWidth - 20) / 2,
|
||||||
|
height: context.highValue,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.fromLTRB(5, 5, 0, 5),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.thermostat,
|
||||||
|
color: Colors.blue,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).paginated_data_table_column_deviceTemperature}: ${sensorSnapshot.data?['sensorTemp'] ?? 100}",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
child: Container(
|
||||||
|
width: (screenWidth - 20) / 2,
|
||||||
|
height: context.highValue,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.fromLTRB(10, 5, 0, 5),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Transform.rotate(
|
||||||
|
angle: 90 * math.pi / 180,
|
||||||
|
child: Icon(
|
||||||
|
DeviceUtils.instance.getBatteryIcon(
|
||||||
|
int.parse(
|
||||||
|
sensorSnapshot
|
||||||
|
.data!['sensorBattery'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Colors.blue,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: context.lowValue,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).paginated_data_table_column_deviceBaterry}: ${sensorSnapshot.data!['sensorBattery']}%",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: Container(
|
||||||
|
width: (screenWidth - 20) / 2,
|
||||||
|
height: context.highValue,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.fromLTRB(10, 5, 0, 5),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
DeviceUtils.instance.getSignalIcon(
|
||||||
|
context,
|
||||||
|
sensorSnapshot.data!['sensorCsq'],
|
||||||
|
),
|
||||||
|
color: Colors.blue,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: context.lowValue,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).paginated_data_table_column_deviceSignal}: ${sensorSnapshot.data!['sensorCsq']}",
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
height: context.highValue,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
color: Colors.blue,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: context.lowValue,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: StreamBuilder<String>(
|
||||||
|
stream:
|
||||||
|
detailDeviceBloc.streamDeviceLocation,
|
||||||
|
builder: (context, locationSnapshot) {
|
||||||
|
if (locationSnapshot.data != null) {
|
||||||
|
return Text(
|
||||||
|
locationSnapshot.data ?? "",
|
||||||
|
style:
|
||||||
|
const TextStyle(fontSize: 13),
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
detailDeviceBloc.findLocation(context,
|
||||||
|
deviceSnapshot.data!.areaPath!);
|
||||||
|
return Text(appLocalization(context)
|
||||||
|
.undefine_message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
child: Container(
|
||||||
|
height: 300,
|
||||||
|
padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: deviceSnapshot.data!.settings!.latitude !=
|
||||||
|
""
|
||||||
|
? GoogleMap(
|
||||||
|
initialCameraPosition: initialCamera,
|
||||||
|
mapType: MapType.normal,
|
||||||
|
markers: {
|
||||||
|
Marker(
|
||||||
|
markerId: MarkerId(
|
||||||
|
deviceSnapshot.data!.thingId!),
|
||||||
|
position: LatLng(
|
||||||
|
double.parse(deviceSnapshot
|
||||||
|
.data!.settings!.latitude!),
|
||||||
|
double.parse(deviceSnapshot
|
||||||
|
.data!.settings!.longitude!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
onMapCreated: (mapcontroller) {
|
||||||
|
controller.complete(mapcontroller);
|
||||||
|
},
|
||||||
|
mapToolbarEnabled: false,
|
||||||
|
zoomControlsEnabled: false,
|
||||||
|
liteModeEnabled: true,
|
||||||
|
)
|
||||||
|
: Center(
|
||||||
|
child: Text(
|
||||||
|
appLocalization(context)
|
||||||
|
.detail_device_dont_has_location_message,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
171
lib/feature/devices/device_model.dart
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
|
||||||
|
// class Device {
|
||||||
|
// int? offset;
|
||||||
|
// List<Item>? items;
|
||||||
|
|
||||||
|
// Device({
|
||||||
|
// this.offset,
|
||||||
|
// this.items,
|
||||||
|
// });
|
||||||
|
// Device.fromJson(Map<String, dynamic> json) {
|
||||||
|
// offset = json['offset'];
|
||||||
|
// if (json['items'] != null) {
|
||||||
|
// items = [];
|
||||||
|
// json['items'].forEach((v) {
|
||||||
|
// items!.add(Item.fromJson(v));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
class Device with ClusterItem {
|
||||||
|
String? extId;
|
||||||
|
String? thingId;
|
||||||
|
String? thingKey;
|
||||||
|
List<String?>? channels;
|
||||||
|
String? areaPath;
|
||||||
|
String? fvers;
|
||||||
|
String? name;
|
||||||
|
HardwareInfo? hardwareInfo;
|
||||||
|
Settings? settings;
|
||||||
|
Status? status;
|
||||||
|
DateTime? connectionTime;
|
||||||
|
int? state;
|
||||||
|
String? visibility;
|
||||||
|
DateTime? createdAt;
|
||||||
|
DateTime? updatedAt;
|
||||||
|
|
||||||
|
Device({
|
||||||
|
this.extId,
|
||||||
|
this.thingId,
|
||||||
|
this.thingKey,
|
||||||
|
this.channels,
|
||||||
|
this.areaPath,
|
||||||
|
this.fvers,
|
||||||
|
this.name,
|
||||||
|
this.hardwareInfo,
|
||||||
|
this.settings,
|
||||||
|
this.status,
|
||||||
|
this.connectionTime,
|
||||||
|
this.state,
|
||||||
|
this.visibility,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
Device.fromJson(Map<String, dynamic> json) {
|
||||||
|
extId = json['ext_id'];
|
||||||
|
thingId = json['thing_id'];
|
||||||
|
thingKey = json['thing_key'];
|
||||||
|
if (json['channels'] != null) {
|
||||||
|
channels = [];
|
||||||
|
json['channels'].forEach((v) {
|
||||||
|
channels!.add(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
areaPath = json['area_path'];
|
||||||
|
fvers = json['fvers'];
|
||||||
|
name = json['name'];
|
||||||
|
hardwareInfo = json['hardware_info'] != null
|
||||||
|
? HardwareInfo.fromJson(json['hardware_info'])
|
||||||
|
: null;
|
||||||
|
settings =
|
||||||
|
json['settings'] != null ? Settings.fromJson(json['settings']) : null;
|
||||||
|
status = json['status'] != null ? Status.fromJson(json['status']) : null;
|
||||||
|
connectionTime = DateTime.parse(json['connection_time']);
|
||||||
|
state = json['state'];
|
||||||
|
visibility = json['visibility'];
|
||||||
|
createdAt = DateTime.parse(json['created_at']);
|
||||||
|
updatedAt = DateTime.parse(json['updated_at']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Device> fromJsonDynamicList(List<dynamic> list) {
|
||||||
|
return list.map((e) => Device.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
LatLng get location {
|
||||||
|
double latitude = double.tryParse(settings?.latitude ?? '') ?? 34.639971;
|
||||||
|
double longitude = double.tryParse(settings?.longitude ?? '') ?? -39.664027;
|
||||||
|
return LatLng(latitude, longitude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HardwareInfo {
|
||||||
|
String? gsm;
|
||||||
|
String? imei;
|
||||||
|
String? imsi;
|
||||||
|
String? phone;
|
||||||
|
DateTime? updatedAt;
|
||||||
|
|
||||||
|
HardwareInfo({
|
||||||
|
this.gsm,
|
||||||
|
this.imei,
|
||||||
|
this.imsi,
|
||||||
|
this.phone,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
HardwareInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
gsm = json['gsm'];
|
||||||
|
imei = json['imei'];
|
||||||
|
imsi = json['imsi'];
|
||||||
|
phone = json['phone'];
|
||||||
|
updatedAt = DateTime.parse(json['updated_at']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Settings {
|
||||||
|
String? latitude;
|
||||||
|
String? longitude;
|
||||||
|
DateTime? updatedAt;
|
||||||
|
|
||||||
|
Settings({
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
Settings.fromJson(Map<String, dynamic> json) {
|
||||||
|
latitude = json['latitude'];
|
||||||
|
longitude = json['longitude'];
|
||||||
|
updatedAt = DateTime.parse(json['updated_at']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Status {
|
||||||
|
int? readOffset;
|
||||||
|
List<Sensor>? sensors;
|
||||||
|
DateTime? updatedAt;
|
||||||
|
|
||||||
|
Status({
|
||||||
|
this.readOffset,
|
||||||
|
this.sensors,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
Status.fromJson(Map<String, dynamic> json) {
|
||||||
|
readOffset = json['read_offset'];
|
||||||
|
if (json['sensors'] != null) {
|
||||||
|
sensors = [];
|
||||||
|
json['sensors'].forEach((v) {
|
||||||
|
sensors!.add(Sensor.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updatedAt = DateTime.parse(json['updated_at']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sensor {
|
||||||
|
String? name;
|
||||||
|
int? value;
|
||||||
|
int? time;
|
||||||
|
dynamic x;
|
||||||
|
|
||||||
|
Sensor({this.name, this.value, this.time, this.x});
|
||||||
|
Sensor.fromJson(Map<String, dynamic> json) {
|
||||||
|
name = json['n'];
|
||||||
|
value = json['v'];
|
||||||
|
time = json['t'];
|
||||||
|
x = json['x'];
|
||||||
|
}
|
||||||
|
}
|
||||||
255
lib/feature/devices/device_update/device_update_bloc.dart
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import '../../../product/services/api_services.dart';
|
||||||
|
import '../../../product/services/language_services.dart';
|
||||||
|
import '../../../product/shared/model/ward_model.dart';
|
||||||
|
import '../../../product/utils/response_status_utils.dart';
|
||||||
|
|
||||||
|
import '../../../product/shared/model/district_model.dart';
|
||||||
|
import '../../../product/shared/model/province_model.dart';
|
||||||
|
import '../device_model.dart';
|
||||||
|
import '../../../product/base/bloc/base_bloc.dart';
|
||||||
|
|
||||||
|
class DeviceUpdateBloc extends BlocBase {
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
|
||||||
|
final deviceInfo = StreamController<Device>.broadcast();
|
||||||
|
StreamSink<Device> get sinkDeviceInfo => deviceInfo.sink;
|
||||||
|
Stream<Device> get streamDeviceInfo => deviceInfo.stream;
|
||||||
|
|
||||||
|
// DeviceUpdateScreen
|
||||||
|
final isChanged = StreamController<bool>.broadcast();
|
||||||
|
StreamSink<bool> get sinkIsChanged => isChanged.sink;
|
||||||
|
Stream<bool> get streamIsChanged => isChanged.stream;
|
||||||
|
|
||||||
|
final provinceData = StreamController<Map<String, String>>.broadcast();
|
||||||
|
StreamSink<Map<String, String>> get sinkProvinceData => provinceData.sink;
|
||||||
|
Stream<Map<String, String>> get streamProvinceData => provinceData.stream;
|
||||||
|
|
||||||
|
final listProvinces =
|
||||||
|
StreamController<List<DropdownMenuItem<Province>>>.broadcast();
|
||||||
|
StreamSink<List<DropdownMenuItem<Province>>> get sinkListProvinces =>
|
||||||
|
listProvinces.sink;
|
||||||
|
Stream<List<DropdownMenuItem<Province>>> get streamListProvinces =>
|
||||||
|
listProvinces.stream;
|
||||||
|
|
||||||
|
final districtData = StreamController<Map<String, String>>.broadcast();
|
||||||
|
StreamSink<Map<String, String>> get sinkDistrictData => districtData.sink;
|
||||||
|
Stream<Map<String, String>> get streamDistrictData => districtData.stream;
|
||||||
|
|
||||||
|
final listDistricts =
|
||||||
|
StreamController<List<DropdownMenuItem<District>>>.broadcast();
|
||||||
|
StreamSink<List<DropdownMenuItem<District>>> get sinkListDistricts =>
|
||||||
|
listDistricts.sink;
|
||||||
|
Stream<List<DropdownMenuItem<District>>> get streamListDistricts =>
|
||||||
|
listDistricts.stream;
|
||||||
|
|
||||||
|
final wardData = StreamController<Map<String, String>>.broadcast();
|
||||||
|
StreamSink<Map<String, String>> get sinkWardData => wardData.sink;
|
||||||
|
Stream<Map<String, String>> get streamWardData => wardData.stream;
|
||||||
|
|
||||||
|
final listWards = StreamController<List<DropdownMenuItem<Ward>>>.broadcast();
|
||||||
|
StreamSink<List<DropdownMenuItem<Ward>>> get sinkListWards => listWards.sink;
|
||||||
|
Stream<List<DropdownMenuItem<Ward>>> get streamListWards => listWards.stream;
|
||||||
|
|
||||||
|
// Show Maps in DeviceUpdateScreen
|
||||||
|
|
||||||
|
final markers = StreamController<Set<Marker>>.broadcast();
|
||||||
|
StreamSink<Set<Marker>> get sinkMarkers => markers.sink;
|
||||||
|
Stream<Set<Marker>> get streamMarkers => markers.stream;
|
||||||
|
|
||||||
|
final searchLocation = StreamController<TextEditingController>.broadcast();
|
||||||
|
StreamSink<TextEditingController> get sinkSearchLocation =>
|
||||||
|
searchLocation.sink;
|
||||||
|
Stream<TextEditingController> get streamSearchLocation =>
|
||||||
|
searchLocation.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// deviceInfo.done;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAllProvinces() async {
|
||||||
|
List<DropdownMenuItem<Province>> provincesData = [];
|
||||||
|
provincesData.clear();
|
||||||
|
sinkListProvinces.add(provincesData);
|
||||||
|
final body = await apiServices.getAllProvinces();
|
||||||
|
final data = jsonDecode(body);
|
||||||
|
List<dynamic> items = data["items"];
|
||||||
|
|
||||||
|
final provinces = Province.fromJsonDynamicList(items);
|
||||||
|
for (var province in provinces) {
|
||||||
|
provincesData.add(
|
||||||
|
DropdownMenuItem(value: province, child: Text(province.fullName!)));
|
||||||
|
}
|
||||||
|
sinkListProvinces.add(provincesData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAllDistricts(String provinceID) async {
|
||||||
|
List<DropdownMenuItem<District>> districtsData = [];
|
||||||
|
districtsData.clear();
|
||||||
|
sinkListDistricts.add(districtsData);
|
||||||
|
final body = await apiServices.getAllDistricts(provinceID);
|
||||||
|
final data = jsonDecode(body);
|
||||||
|
List<dynamic> items = data["items"];
|
||||||
|
final districts = District.fromJsonDynamicList(items);
|
||||||
|
for (var district in districts) {
|
||||||
|
districtsData.add(
|
||||||
|
DropdownMenuItem(value: district, child: Text(district.fullName!)));
|
||||||
|
}
|
||||||
|
sinkListDistricts.add(districtsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAllWards(String districtID) async {
|
||||||
|
List<DropdownMenuItem<Ward>> wardsData = [];
|
||||||
|
wardsData.clear();
|
||||||
|
sinkListWards.add(wardsData);
|
||||||
|
final body = await apiServices.getAllWards(districtID);
|
||||||
|
final data = jsonDecode(body);
|
||||||
|
List<dynamic> items = data["items"];
|
||||||
|
final wards = Ward.fromJsonDynamicList(items);
|
||||||
|
for (var ward in wards) {
|
||||||
|
wardsData.add(DropdownMenuItem(value: ward, child: Text(ward.fullName!)));
|
||||||
|
}
|
||||||
|
sinkListWards.add(wardsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getDeviceInfomation(
|
||||||
|
String thingID,
|
||||||
|
List<DropdownMenuItem<District>> districtsData,
|
||||||
|
List<DropdownMenuItem<Ward>> wardsData,
|
||||||
|
TextEditingController deviceNameController,
|
||||||
|
TextEditingController latitudeController,
|
||||||
|
TextEditingController longitudeController) async {
|
||||||
|
String body = await apiServices.getDeviceInfomation(thingID);
|
||||||
|
final data = jsonDecode(body);
|
||||||
|
Device device = Device.fromJson(data);
|
||||||
|
sinkDeviceInfo.add(device);
|
||||||
|
deviceNameController.text = device.name ?? "";
|
||||||
|
latitudeController.text = device.settings!.latitude ?? "";
|
||||||
|
longitudeController.text = device.settings!.longitude ?? "";
|
||||||
|
if (device.areaPath != "") {
|
||||||
|
List<String> areaPath = device.areaPath!.split('_');
|
||||||
|
String provinceCode = areaPath[0];
|
||||||
|
String districtCode = areaPath[1];
|
||||||
|
String wardCode = areaPath[2];
|
||||||
|
getAllDistricts(provinceCode);
|
||||||
|
getAllWards(districtCode);
|
||||||
|
final provinceResponse = await apiServices.getProvinceByID(provinceCode);
|
||||||
|
final provincesData = jsonDecode(provinceResponse);
|
||||||
|
Province province = Province.fromJson(provincesData['data']);
|
||||||
|
final districtResponse = await apiServices.getDistrictByID(districtCode);
|
||||||
|
final districtData = jsonDecode(districtResponse);
|
||||||
|
District district = District.fromJson(districtData['data']);
|
||||||
|
final wardResponse = await apiServices.getWardByID(wardCode);
|
||||||
|
final wardData = jsonDecode(wardResponse);
|
||||||
|
Ward ward = Ward.fromJson(wardData['data']);
|
||||||
|
Map<String, String> provinceData = {
|
||||||
|
"name": province.fullName!,
|
||||||
|
"code": province.code!
|
||||||
|
};
|
||||||
|
sinkProvinceData.add(provinceData);
|
||||||
|
Map<String, String> districData = {
|
||||||
|
"name": district.fullName!,
|
||||||
|
"code": district.code!,
|
||||||
|
};
|
||||||
|
sinkDistrictData.add(districData);
|
||||||
|
Map<String, String> wardMap = {
|
||||||
|
"name": ward.fullName!,
|
||||||
|
"code": ward.code!,
|
||||||
|
};
|
||||||
|
sinkWardData.add(wardMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Province> getProvinceByName(String name) async {
|
||||||
|
final response = await apiServices.getProvincesByName(name);
|
||||||
|
final data = jsonDecode(response);
|
||||||
|
if (data != null &&
|
||||||
|
data.containsKey('items') &&
|
||||||
|
data['items'] != null &&
|
||||||
|
data['items'].isNotEmpty) {
|
||||||
|
List<dynamic> items = data['items'];
|
||||||
|
List<Province> provinces = Province.fromJsonDynamicList(items);
|
||||||
|
if (provinces.isNotEmpty) {
|
||||||
|
return provinces[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Province(name: "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<District> getDistrictByName(String name, String provinceCode) async {
|
||||||
|
final response = await apiServices.getDistrictsByName(name);
|
||||||
|
if (response != "") {
|
||||||
|
final data = jsonDecode(response);
|
||||||
|
List<dynamic> items = data['items'];
|
||||||
|
if (items.isNotEmpty) {
|
||||||
|
List<District> districts = District.fromJsonDynamicList(items);
|
||||||
|
if (districts.isNotEmpty) {
|
||||||
|
for (var district in districts) {
|
||||||
|
if (district.provinceCode == provinceCode) {
|
||||||
|
return district;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return District(name: "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Ward> getWardByName(String name, String districtCode) async {
|
||||||
|
final response = await apiServices.getWarsdByName(name);
|
||||||
|
final data = jsonDecode(response);
|
||||||
|
if (data != null && data['items'] != null) {
|
||||||
|
List<dynamic> items = data['items'];
|
||||||
|
if (items.isNotEmpty) {
|
||||||
|
List<Ward> wards = Ward.fromJsonDynamicList(items);
|
||||||
|
if (wards.isNotEmpty) {
|
||||||
|
for (var ward in wards) {
|
||||||
|
if (ward.districtCode == districtCode) {
|
||||||
|
return ward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ward(name: "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateDevice(
|
||||||
|
BuildContext context,
|
||||||
|
String thingID,
|
||||||
|
String name,
|
||||||
|
String latitude,
|
||||||
|
String longitude,
|
||||||
|
String provinceCode,
|
||||||
|
String districtCode,
|
||||||
|
String wardCode,
|
||||||
|
) async {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
515
lib/feature/devices/device_update/device_update_screen.dart
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:search_choices/search_choices.dart';
|
||||||
|
|
||||||
|
import '../device_model.dart';
|
||||||
|
import 'device_update_bloc.dart';
|
||||||
|
import 'map_dialog.dart';
|
||||||
|
import '../../../product/base/bloc/base_bloc.dart';
|
||||||
|
import '../../../product/extention/context_extention.dart';
|
||||||
|
import '../../../product/services/api_services.dart';
|
||||||
|
import '../../../product/services/language_services.dart';
|
||||||
|
|
||||||
|
import '../../../product/shared/model/district_model.dart';
|
||||||
|
import '../../../product/shared/model/province_model.dart';
|
||||||
|
import '../../../product/shared/model/ward_model.dart';
|
||||||
|
|
||||||
|
class DeviceUpdateScreen extends StatefulWidget {
|
||||||
|
const DeviceUpdateScreen({super.key, required this.thingID});
|
||||||
|
final String thingID;
|
||||||
|
@override
|
||||||
|
State<DeviceUpdateScreen> createState() => _DeviceUpdateScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeviceUpdateScreenState extends State<DeviceUpdateScreen> {
|
||||||
|
late DeviceUpdateBloc deviceUpdateBloc;
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
Device device = Device();
|
||||||
|
bool isChanged = false;
|
||||||
|
TextEditingController deviceNameController = TextEditingController();
|
||||||
|
TextEditingController deviceLatitudeController = TextEditingController();
|
||||||
|
TextEditingController deviceLongitudeController = TextEditingController();
|
||||||
|
List<DropdownMenuItem<Province>> provincesData = [];
|
||||||
|
String? selectedProvince = "";
|
||||||
|
List<DropdownMenuItem<District>> districtsData = [];
|
||||||
|
String? selectedDistrict = "";
|
||||||
|
List<DropdownMenuItem<Ward>> wardsData = [];
|
||||||
|
String? selectedWard = "";
|
||||||
|
|
||||||
|
// static String provinceCode = "";
|
||||||
|
// static String districtCode = "";
|
||||||
|
// static String wardCode = "";
|
||||||
|
|
||||||
|
Map<String, String> provinceData = {};
|
||||||
|
Map<String, String> districtData = {};
|
||||||
|
Map<String, String> wardData = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
deviceUpdateBloc = BlocProvider.of(context);
|
||||||
|
deviceUpdateBloc.getAllProvinces();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(appLocalization(context).device_update_title),
|
||||||
|
),
|
||||||
|
body: StreamBuilder<Map<String, String>>(
|
||||||
|
stream: deviceUpdateBloc.streamProvinceData,
|
||||||
|
builder: (context, provinceNameSnapshot) {
|
||||||
|
return StreamBuilder<Map<String, String>>(
|
||||||
|
stream: deviceUpdateBloc.streamDistrictData,
|
||||||
|
builder: (context, districtNameSnapshot) {
|
||||||
|
return StreamBuilder<Map<String, String>>(
|
||||||
|
stream: deviceUpdateBloc.streamWardData,
|
||||||
|
builder: (context, wardNameSnapshot) {
|
||||||
|
return SafeArea(
|
||||||
|
child: StreamBuilder<Device>(
|
||||||
|
stream: deviceUpdateBloc.streamDeviceInfo,
|
||||||
|
initialData: device,
|
||||||
|
builder: (context, deviceInfoSnapshot) {
|
||||||
|
if (deviceInfoSnapshot.data!.thingId == null) {
|
||||||
|
deviceUpdateBloc.getDeviceInfomation(
|
||||||
|
widget.thingID,
|
||||||
|
districtsData,
|
||||||
|
wardsData,
|
||||||
|
deviceNameController,
|
||||||
|
deviceLatitudeController,
|
||||||
|
deviceLongitudeController);
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return StreamBuilder<bool>(
|
||||||
|
stream: deviceUpdateBloc.streamIsChanged,
|
||||||
|
initialData: isChanged,
|
||||||
|
builder: (context, isChangedSnapshot) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: context.paddingLow,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).input_name_device_device}:",
|
||||||
|
style: context
|
||||||
|
.titleMediumTextStyle),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
context.paddingLowVertical,
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
isChangedListener();
|
||||||
|
},
|
||||||
|
textInputAction:
|
||||||
|
TextInputAction.next,
|
||||||
|
controller:
|
||||||
|
deviceNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: appLocalization(
|
||||||
|
context)
|
||||||
|
.input_name_device_hintText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).device_update_location}:",
|
||||||
|
style: context
|
||||||
|
.titleMediumTextStyle),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
context.paddingLowVertical,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment
|
||||||
|
.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: context
|
||||||
|
.dynamicWidth(0.6),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
isChangedListener();
|
||||||
|
},
|
||||||
|
textInputAction:
|
||||||
|
TextInputAction
|
||||||
|
.next,
|
||||||
|
controller:
|
||||||
|
deviceLatitudeController,
|
||||||
|
decoration:
|
||||||
|
InputDecoration(
|
||||||
|
label: Text(
|
||||||
|
"${appLocalization(context).update_device_dialog_location_longitude}:"),
|
||||||
|
hintText: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_longitude_hintText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: context
|
||||||
|
.paddingLowVertical,
|
||||||
|
child: SizedBox(
|
||||||
|
child: TextField(
|
||||||
|
onChanged:
|
||||||
|
(value) {
|
||||||
|
isChangedListener();
|
||||||
|
},
|
||||||
|
controller:
|
||||||
|
deviceLongitudeController,
|
||||||
|
textInputAction:
|
||||||
|
TextInputAction
|
||||||
|
.next,
|
||||||
|
decoration:
|
||||||
|
InputDecoration(
|
||||||
|
label: Text(
|
||||||
|
"${appLocalization(context).update_device_dialog_location_latitude}:"),
|
||||||
|
hintText: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_latitude_hintText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: IconButton.filled(
|
||||||
|
style: const ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStatePropertyAll(
|
||||||
|
Colors
|
||||||
|
.lightGreen)),
|
||||||
|
// iconSize: 24,
|
||||||
|
onPressed: () async {
|
||||||
|
showMapDialog(
|
||||||
|
context,
|
||||||
|
deviceUpdateBloc,
|
||||||
|
deviceLatitudeController,
|
||||||
|
deviceLongitudeController);
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.map_outlined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: context.lowValue),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).device_update_province}:",
|
||||||
|
style: context
|
||||||
|
.titleMediumTextStyle),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
context.paddingLowVertical,
|
||||||
|
child: StreamBuilder<
|
||||||
|
List<
|
||||||
|
DropdownMenuItem<
|
||||||
|
Province>>>(
|
||||||
|
stream: deviceUpdateBloc
|
||||||
|
.streamListProvinces,
|
||||||
|
builder: (dialogContext,
|
||||||
|
listProvinces) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius
|
||||||
|
.all(
|
||||||
|
Radius.circular(20),
|
||||||
|
),
|
||||||
|
border: Border.all(),
|
||||||
|
),
|
||||||
|
child: SearchChoices.single(
|
||||||
|
items:
|
||||||
|
listProvinces.data ??
|
||||||
|
provincesData,
|
||||||
|
hint: provinceNameSnapshot
|
||||||
|
.data !=
|
||||||
|
null
|
||||||
|
? Text(
|
||||||
|
provinceNameSnapshot
|
||||||
|
.data![
|
||||||
|
'name'] ??
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_province_hintText,
|
||||||
|
searchHint: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_province_searchHint,
|
||||||
|
displayClearIcon: false,
|
||||||
|
onChanged: (value) {
|
||||||
|
isChangedListener();
|
||||||
|
// provinceCode =
|
||||||
|
// value.code;
|
||||||
|
selectedProvince =
|
||||||
|
value.fullName;
|
||||||
|
provinceData['name'] =
|
||||||
|
value.fullName;
|
||||||
|
provinceData['code'] =
|
||||||
|
value.code;
|
||||||
|
deviceUpdateBloc
|
||||||
|
.sinkProvinceData
|
||||||
|
.add(provinceData);
|
||||||
|
deviceUpdateBloc
|
||||||
|
.getAllDistricts(
|
||||||
|
value.code);
|
||||||
|
selectedDistrict = "";
|
||||||
|
districtData['name'] =
|
||||||
|
selectedDistrict!;
|
||||||
|
// deviceUpdateBloc.sinkDistrictName
|
||||||
|
// .add(selectedDistrict);
|
||||||
|
deviceUpdateBloc
|
||||||
|
.sinkDistrictData
|
||||||
|
.add(districtData);
|
||||||
|
selectedWard = "";
|
||||||
|
wardData['name'] =
|
||||||
|
selectedWard!;
|
||||||
|
deviceUpdateBloc
|
||||||
|
.sinkWardData
|
||||||
|
.add(wardData);
|
||||||
|
},
|
||||||
|
isExpanded: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).device_update_district}:",
|
||||||
|
style: context
|
||||||
|
.titleMediumTextStyle),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
context.paddingLowVertical,
|
||||||
|
child: StreamBuilder<
|
||||||
|
List<
|
||||||
|
DropdownMenuItem<
|
||||||
|
District>>>(
|
||||||
|
stream: deviceUpdateBloc
|
||||||
|
.streamListDistricts,
|
||||||
|
builder: (dialogContext,
|
||||||
|
listDistricts) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius
|
||||||
|
.all(
|
||||||
|
Radius.circular(20),
|
||||||
|
),
|
||||||
|
border: Border.all(),
|
||||||
|
),
|
||||||
|
child: SearchChoices.single(
|
||||||
|
items:
|
||||||
|
listDistricts.data ??
|
||||||
|
districtsData,
|
||||||
|
hint: districtNameSnapshot
|
||||||
|
.data !=
|
||||||
|
null
|
||||||
|
? Text(
|
||||||
|
districtNameSnapshot
|
||||||
|
.data![
|
||||||
|
'name'] ??
|
||||||
|
selectedDistrict!,
|
||||||
|
)
|
||||||
|
: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_district_hintText,
|
||||||
|
searchHint: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_district_searchHint,
|
||||||
|
displayClearIcon: false,
|
||||||
|
onChanged: (value) {
|
||||||
|
isChangedListener();
|
||||||
|
// districtCode =
|
||||||
|
// value.code;
|
||||||
|
selectedDistrict =
|
||||||
|
value.fullName;
|
||||||
|
districtData['name'] =
|
||||||
|
value.fullName!;
|
||||||
|
districtData['code'] =
|
||||||
|
value.code;
|
||||||
|
deviceUpdateBloc
|
||||||
|
.sinkDistrictData
|
||||||
|
.add(districtData);
|
||||||
|
deviceUpdateBloc
|
||||||
|
.getAllWards(
|
||||||
|
value.code);
|
||||||
|
selectedWard = "";
|
||||||
|
wardData['name'] =
|
||||||
|
selectedWard!;
|
||||||
|
deviceUpdateBloc
|
||||||
|
.sinkWardData
|
||||||
|
.add(wardData);
|
||||||
|
},
|
||||||
|
isExpanded: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${appLocalization(context).device_update_ward}:",
|
||||||
|
style: context
|
||||||
|
.titleMediumTextStyle),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
context.paddingLowVertical,
|
||||||
|
child: StreamBuilder<
|
||||||
|
List<DropdownMenuItem<Ward>>>(
|
||||||
|
stream: deviceUpdateBloc
|
||||||
|
.streamListWards,
|
||||||
|
builder:
|
||||||
|
(dialogContext, listWards) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius
|
||||||
|
.all(
|
||||||
|
Radius.circular(
|
||||||
|
20)),
|
||||||
|
border: Border.all()),
|
||||||
|
child: SearchChoices.single(
|
||||||
|
items: listWards.data ??
|
||||||
|
wardsData,
|
||||||
|
hint: wardNameSnapshot
|
||||||
|
.data !=
|
||||||
|
null
|
||||||
|
? Text(
|
||||||
|
wardNameSnapshot
|
||||||
|
.data![
|
||||||
|
'name'] ??
|
||||||
|
selectedWard!,
|
||||||
|
)
|
||||||
|
: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_ward_hintText,
|
||||||
|
searchHint: appLocalization(
|
||||||
|
context)
|
||||||
|
.update_device_dialog_location_ward_searchHint,
|
||||||
|
displayClearIcon: false,
|
||||||
|
onChanged: (value) {
|
||||||
|
isChangedListener();
|
||||||
|
// wardCode = value.code;
|
||||||
|
selectedWard =
|
||||||
|
value.fullName;
|
||||||
|
wardData['name'] =
|
||||||
|
value.fullName!;
|
||||||
|
wardData['code'] =
|
||||||
|
value.code!;
|
||||||
|
deviceUpdateBloc
|
||||||
|
.sinkWardData
|
||||||
|
.add(wardData);
|
||||||
|
},
|
||||||
|
isExpanded: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isChangedSnapshot.data == true)
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width:
|
||||||
|
context.dynamicWidth(0.6),
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
foregroundColor:
|
||||||
|
MaterialStateProperty
|
||||||
|
.all(Colors
|
||||||
|
.white),
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty
|
||||||
|
.all(Colors
|
||||||
|
.blue)),
|
||||||
|
onPressed: () async {
|
||||||
|
String provinceCode =
|
||||||
|
provinceNameSnapshot
|
||||||
|
.data![
|
||||||
|
"code"] ??
|
||||||
|
"";
|
||||||
|
String districtCode =
|
||||||
|
districtNameSnapshot
|
||||||
|
.data![
|
||||||
|
"code"] ??
|
||||||
|
"";
|
||||||
|
String wardCode =
|
||||||
|
wardNameSnapshot
|
||||||
|
.data![
|
||||||
|
"code"] ??
|
||||||
|
"";
|
||||||
|
String latitude =
|
||||||
|
deviceLatitudeController
|
||||||
|
.value.text;
|
||||||
|
String longitude =
|
||||||
|
deviceLongitudeController
|
||||||
|
.value.text;
|
||||||
|
String deviceName =
|
||||||
|
deviceNameController
|
||||||
|
.value.text;
|
||||||
|
// log("ProvinceCode: $provinceCode");
|
||||||
|
// log("DistrictCode: $districtCode");
|
||||||
|
// log("WardCode: $wardCode");
|
||||||
|
// log("Latitude: $latitude");
|
||||||
|
// log("Longitude: $longitude");
|
||||||
|
// log("Device Name: $deviceName");
|
||||||
|
await deviceUpdateBloc
|
||||||
|
.updateDevice(
|
||||||
|
context,
|
||||||
|
deviceInfoSnapshot
|
||||||
|
.data!.thingId!,
|
||||||
|
deviceName,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
provinceCode,
|
||||||
|
districtCode,
|
||||||
|
wardCode,
|
||||||
|
);
|
||||||
|
Future.delayed(
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
context.lowDuration,
|
||||||
|
() {
|
||||||
|
Navigator.pop(
|
||||||
|
context);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(appLocalization(
|
||||||
|
context)
|
||||||
|
.update_button_content)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void isChangedListener() {
|
||||||
|
isChanged = true;
|
||||||
|
deviceUpdateBloc.sinkIsChanged.add(isChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
lib/feature/devices/device_update/geocode_model.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
class Geocode {
|
||||||
|
List<AddressComponent>? addressComponents;
|
||||||
|
String? formattedAddress;
|
||||||
|
Geocode({
|
||||||
|
this.addressComponents,
|
||||||
|
this.formattedAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
Geocode.fromJson(Map<String, dynamic> json) {
|
||||||
|
formattedAddress = json['formatted_address'];
|
||||||
|
if (json['address_components'] != null) {
|
||||||
|
addressComponents = [];
|
||||||
|
json['address_components'].forEach((v) {
|
||||||
|
addressComponents!.add(AddressComponent.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['formatted_address'] = formattedAddress;
|
||||||
|
if (addressComponents != null) {
|
||||||
|
data['address_components'] =
|
||||||
|
addressComponents!.map((e) => e.toJson()).toList();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddressComponent {
|
||||||
|
String? longName;
|
||||||
|
String? shortName;
|
||||||
|
List<String>? types;
|
||||||
|
AddressComponent({this.longName, this.shortName, this.types});
|
||||||
|
|
||||||
|
AddressComponent.fromJson(Map<String, dynamic> json) {
|
||||||
|
longName = json['long_name'];
|
||||||
|
shortName = json['short_name'];
|
||||||
|
types = json['types'].cast<String>();
|
||||||
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['long_name'] = longName;
|
||||||
|
data['short_name'] = shortName;
|
||||||
|
data['type'] = types;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
298
lib/feature/devices/device_update/map_dialog.dart
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'device_update_bloc.dart';
|
||||||
|
import '../../../product/constant/app/app_constants.dart';
|
||||||
|
import '../../../product/extention/context_extention.dart';
|
||||||
|
import '../../../product/services/language_services.dart';
|
||||||
|
import '../../../product/shared/find_location_maps/shared_map_search_location.dart';
|
||||||
|
|
||||||
|
import '../../../product/shared/find_location_maps/model/prediction_model.dart';
|
||||||
|
import '../../../product/shared/shared_transition.dart';
|
||||||
|
import 'geocode_model.dart';
|
||||||
|
|
||||||
|
showMapDialog(
|
||||||
|
BuildContext context,
|
||||||
|
DeviceUpdateBloc deviceUpdateBloc,
|
||||||
|
TextEditingController latitudeController,
|
||||||
|
TextEditingController longitudeController) async {
|
||||||
|
const CameraPosition defaultPosition = CameraPosition(
|
||||||
|
target: LatLng(20.985424, 105.738354),
|
||||||
|
zoom: 12,
|
||||||
|
);
|
||||||
|
TextEditingController searchLocationController = TextEditingController();
|
||||||
|
TextEditingController mapDialogLatitudeController = TextEditingController();
|
||||||
|
TextEditingController mapDialogLongitudeController = TextEditingController();
|
||||||
|
Completer<GoogleMapController> ggmapController = Completer();
|
||||||
|
final streamController = StreamController<GoogleMapController>.broadcast();
|
||||||
|
showGeneralDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
transitionDuration: context.normalDuration,
|
||||||
|
transitionBuilder: transitionsLeftToRight,
|
||||||
|
context: context,
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) {
|
||||||
|
return StreamBuilder<Set<Marker>>(
|
||||||
|
stream: deviceUpdateBloc.streamMarkers,
|
||||||
|
builder: (context, markerSnapshot) {
|
||||||
|
if (!markerSnapshot.hasData) {
|
||||||
|
if (latitudeController.value.text != "" &&
|
||||||
|
longitudeController.value.text != "") {
|
||||||
|
double latitude = double.parse(latitudeController.text);
|
||||||
|
double longitude = double.parse(longitudeController.text);
|
||||||
|
addMarker(
|
||||||
|
LatLng(latitude, longitude),
|
||||||
|
ggmapController,
|
||||||
|
deviceUpdateBloc,
|
||||||
|
mapDialogLatitudeController,
|
||||||
|
mapDialogLongitudeController,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(appLocalization(context)
|
||||||
|
.update_device_dialog_maps_dialog_title),
|
||||||
|
centerTitle: true,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
String latitude = mapDialogLatitudeController.text;
|
||||||
|
String longitude = mapDialogLongitudeController.text;
|
||||||
|
log("Finish -- Latitude: $latitude, longitude: $longitude --");
|
||||||
|
getDataFromApi(latitude, longitude, deviceUpdateBloc);
|
||||||
|
latitudeController.text =
|
||||||
|
mapDialogLatitudeController.text;
|
||||||
|
longitudeController.text =
|
||||||
|
mapDialogLongitudeController.text;
|
||||||
|
bool isChange = true;
|
||||||
|
deviceUpdateBloc.sinkIsChanged.add(isChange);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.check))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
GoogleMap(
|
||||||
|
onTap: (location) async {
|
||||||
|
addMarker(
|
||||||
|
location,
|
||||||
|
ggmapController,
|
||||||
|
deviceUpdateBloc,
|
||||||
|
mapDialogLatitudeController,
|
||||||
|
mapDialogLongitudeController,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
markers: markerSnapshot.data ?? {},
|
||||||
|
onMapCreated: (GoogleMapController mapController) {
|
||||||
|
ggmapController.complete(mapController);
|
||||||
|
streamController.add(mapController);
|
||||||
|
},
|
||||||
|
initialCameraPosition: defaultPosition,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
// color: Colors.white,
|
||||||
|
height: 80,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: StreamBuilder<TextEditingController>(
|
||||||
|
stream: deviceUpdateBloc.streamSearchLocation,
|
||||||
|
builder: (context, searchLocation) {
|
||||||
|
return NearBySearchSFM(
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
textEditingController:
|
||||||
|
searchLocation.data ?? searchLocationController,
|
||||||
|
googleAPIKey: ApplicationConstants.MAP_KEY,
|
||||||
|
locationLatitude: 20.985424,
|
||||||
|
locationLongitude: 105.738354,
|
||||||
|
radius: 50000,
|
||||||
|
inputDecoration: InputDecoration(
|
||||||
|
hintText: appLocalization(context)
|
||||||
|
.update_device_dialog_search_location_hint,
|
||||||
|
border: InputBorder.none),
|
||||||
|
debounceTime: 600,
|
||||||
|
isLatLngRequired: true,
|
||||||
|
getPlaceDetailWithLatLng: (Prediction prediction) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
addMarker(
|
||||||
|
LatLng(double.parse(prediction.lat!),
|
||||||
|
double.parse(prediction.lng!)),
|
||||||
|
ggmapController,
|
||||||
|
deviceUpdateBloc,
|
||||||
|
mapDialogLatitudeController,
|
||||||
|
mapDialogLongitudeController);
|
||||||
|
},
|
||||||
|
itemClick: (Prediction prediction) {
|
||||||
|
searchLocationController.text =
|
||||||
|
prediction.structuredFormatting!.mainText!;
|
||||||
|
deviceUpdateBloc.sinkSearchLocation
|
||||||
|
.add(searchLocationController);
|
||||||
|
searchLocationController.selection =
|
||||||
|
TextSelection.fromPosition(TextPosition(
|
||||||
|
offset: prediction.structuredFormatting!
|
||||||
|
.mainText!.length));
|
||||||
|
},
|
||||||
|
boxDecoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(20))),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMarker(
|
||||||
|
LatLng position,
|
||||||
|
Completer<GoogleMapController> mapController,
|
||||||
|
DeviceUpdateBloc deviceUpdateBloc,
|
||||||
|
TextEditingController mapDialogLatitudeController,
|
||||||
|
TextEditingController mapDialogLongitudeController,
|
||||||
|
) async {
|
||||||
|
log("AddMarker -- Latitude: ${position.latitude}, longitude: ${position.longitude} --");
|
||||||
|
|
||||||
|
Set<Marker> marker = {};
|
||||||
|
deviceUpdateBloc.sinkMarkers.add(marker);
|
||||||
|
Marker newMarker = Marker(
|
||||||
|
markerId: const MarkerId('value'),
|
||||||
|
position: LatLng(position.latitude, position.longitude),
|
||||||
|
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
|
||||||
|
draggable: true,
|
||||||
|
onDragEnd: (position) {
|
||||||
|
mapDialogLatitudeController.text = position.latitude.toString();
|
||||||
|
mapDialogLongitudeController.text = position.longitude.toString();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
marker.add(newMarker);
|
||||||
|
deviceUpdateBloc.sinkMarkers.add(marker);
|
||||||
|
mapDialogLatitudeController.text = position.latitude.toString();
|
||||||
|
mapDialogLongitudeController.text = position.longitude.toString();
|
||||||
|
updateCameraPosition(position, 14, mapController);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getDataFromApi(String latitude, String longitude,
|
||||||
|
DeviceUpdateBloc deviceUpdateBloc) async {
|
||||||
|
String path =
|
||||||
|
"maps/api/geocode/json?latlng=$latitude,$longitude&language=vi&result_type=political&key=${ApplicationConstants.MAP_KEY}";
|
||||||
|
var url = Uri.parse('https://maps.googleapis.com/$path');
|
||||||
|
|
||||||
|
final response = await http.get(url);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
log("Loi: ${response.statusCode}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> data = jsonDecode(response.body);
|
||||||
|
if (!data.containsKey('results') || data['results'].isEmpty) {
|
||||||
|
log("Khong co result");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> results = data['results'];
|
||||||
|
List<Geocode> geocodes =
|
||||||
|
results.map((result) => Geocode.fromJson(result)).toList();
|
||||||
|
|
||||||
|
Map<String, String> locations =
|
||||||
|
_extractLocationComponents(geocodes[0].addressComponents!);
|
||||||
|
|
||||||
|
// In ra thông tin của các location
|
||||||
|
locations.forEach((key, value) {
|
||||||
|
log("$key: $value");
|
||||||
|
});
|
||||||
|
|
||||||
|
await _processLocations(locations, deviceUpdateBloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> _extractLocationComponents(
|
||||||
|
List<AddressComponent> addressComponents) {
|
||||||
|
Map<String, String> locations = {};
|
||||||
|
|
||||||
|
for (var addressComponent in addressComponents) {
|
||||||
|
String longName = addressComponent.longName ?? "";
|
||||||
|
if (addressComponent.types!.contains('administrative_area_level_3') ||
|
||||||
|
addressComponent.types!.contains('sublocality_level_1')) {
|
||||||
|
locations['wardkey'] = longName;
|
||||||
|
} else if (addressComponent.types!
|
||||||
|
.contains('administrative_area_level_2') ||
|
||||||
|
addressComponent.types!.contains('sublocality_level_2') ||
|
||||||
|
addressComponent.types!.contains('locality')) {
|
||||||
|
locations['districtkey'] = longName;
|
||||||
|
} else if (addressComponent.types!
|
||||||
|
.contains('administrative_area_level_1')) {
|
||||||
|
locations['provincekey'] = longName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _processLocations(
|
||||||
|
Map<String, String> locations, DeviceUpdateBloc deviceUpdateBloc) async {
|
||||||
|
String provinceNameFromAPI = locations['provincekey'] ?? "";
|
||||||
|
String districtNameFromAPI = locations['districtkey'] ?? "";
|
||||||
|
String wardNameFromAPI = locations['wardkey'] ?? "";
|
||||||
|
|
||||||
|
final province =
|
||||||
|
await deviceUpdateBloc.getProvinceByName(provinceNameFromAPI);
|
||||||
|
if (province.name != "null") {
|
||||||
|
log("Province: ${province.fullName}, ProvinceCode: ${province.code}");
|
||||||
|
deviceUpdateBloc.sinkProvinceData
|
||||||
|
.add({"code": province.code!, "name": province.fullName!});
|
||||||
|
deviceUpdateBloc.getAllProvinces();
|
||||||
|
|
||||||
|
final district = await deviceUpdateBloc.getDistrictByName(
|
||||||
|
districtNameFromAPI, province.code!);
|
||||||
|
log("Districtname: ${district.fullName}, districtCode: ${district.code}");
|
||||||
|
deviceUpdateBloc.getAllDistricts(province.code!);
|
||||||
|
if (district.name != "null") {
|
||||||
|
deviceUpdateBloc.sinkDistrictData
|
||||||
|
.add({"code": district.code!, "name": district.fullName!});
|
||||||
|
final ward =
|
||||||
|
await deviceUpdateBloc.getWardByName(wardNameFromAPI, district.code!);
|
||||||
|
log("Wardname: ${ward.fullName}, WardCode: ${ward.code}");
|
||||||
|
deviceUpdateBloc.getAllWards(district.code!);
|
||||||
|
if (ward.name != "null") {
|
||||||
|
log("Xac dinh duoc het thong tin tu toa do");
|
||||||
|
deviceUpdateBloc.sinkWardData
|
||||||
|
.add({"code": ward.code!, "name": ward.fullName!});
|
||||||
|
} else {
|
||||||
|
deviceUpdateBloc.sinkWardData.add({});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deviceUpdateBloc.sinkDistrictData.add({});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deviceUpdateBloc.sinkProvinceData.add({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateCameraPosition(LatLng location, double zoom,
|
||||||
|
Completer<GoogleMapController> mapController) async {
|
||||||
|
final CameraPosition cameraPosition = CameraPosition(
|
||||||
|
target: LatLng(
|
||||||
|
location.latitude,
|
||||||
|
location.longitude,
|
||||||
|
),
|
||||||
|
zoom: zoom,
|
||||||
|
);
|
||||||
|
final GoogleMapController mapControllerNew = await mapController.future;
|
||||||
|
mapControllerNew.animateCamera(
|
||||||
|
CameraUpdate.newCameraPosition(
|
||||||
|
cameraPosition,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
35
lib/feature/devices/devices_manager_bloc.dart
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'device_model.dart';
|
||||||
|
import '../../product/base/bloc/base_bloc.dart';
|
||||||
|
import '../../product/services/api_services.dart';
|
||||||
|
|
||||||
|
import '../../product/utils/device_utils.dart';
|
||||||
|
|
||||||
|
class DevicesManagerBloc extends BlocBase {
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
|
||||||
|
final userRole = StreamController<String>.broadcast();
|
||||||
|
StreamSink<String> get sinkUserRole => userRole.sink;
|
||||||
|
Stream<String> get streamUserRole => userRole.stream;
|
||||||
|
|
||||||
|
final allDevices = StreamController<List<Device>>.broadcast();
|
||||||
|
StreamSink<List<Device>> get sinkAllDevices => allDevices.sink;
|
||||||
|
Stream<List<Device>> get streamAllDevices => allDevices.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {}
|
||||||
|
|
||||||
|
void getDevice() async {
|
||||||
|
String body = await apiServices.getOwnerDevices();
|
||||||
|
if (body != "") {
|
||||||
|
final data = jsonDecode(body);
|
||||||
|
List<dynamic> items = data['items'];
|
||||||
|
List<Device> originalDevices = Device.fromJsonDynamicList(items);
|
||||||
|
List<Device> devices =
|
||||||
|
DeviceUtils.instance.sortDeviceByState(originalDevices);
|
||||||
|
sinkAllDevices.add(devices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
274
lib/feature/devices/devices_manager_screen.dart
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'add_new_device_widget.dart';
|
||||||
|
import 'delete_device_widget.dart';
|
||||||
|
import 'device_model.dart';
|
||||||
|
import 'devices_manager_bloc.dart';
|
||||||
|
import '../../product/base/bloc/base_bloc.dart';
|
||||||
|
import '../../product/constant/enums/app_route_enums.dart';
|
||||||
|
import '../../product/constant/enums/role_enums.dart';
|
||||||
|
import '../../product/constant/icon/icon_constants.dart';
|
||||||
|
import '../../product/extention/context_extention.dart';
|
||||||
|
import '../../product/services/api_services.dart';
|
||||||
|
import '../../product/services/language_services.dart';
|
||||||
|
import '../../product/utils/device_utils.dart';
|
||||||
|
|
||||||
|
class DevicesManagerScreen extends StatefulWidget {
|
||||||
|
const DevicesManagerScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DevicesManagerScreen> createState() => _DevicesManagerScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
|
||||||
|
late DevicesManagerBloc devicesManagerBloc;
|
||||||
|
String role = "Undefine";
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
List<Device> devices = [];
|
||||||
|
Timer? getAllDevicesTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
devicesManagerBloc = BlocProvider.of(context);
|
||||||
|
getUserRole();
|
||||||
|
// devicesManagerBloc.getDevice();
|
||||||
|
// getAllOwnerDevices();
|
||||||
|
// const duration = Duration(seconds: 10);
|
||||||
|
// getAllDevicesTimer =
|
||||||
|
// Timer.periodic(duration, (Timer t) => devicesManagerBloc.getDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
getAllDevicesTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: StreamBuilder<List<Device>>(
|
||||||
|
stream: devicesManagerBloc.streamAllDevices,
|
||||||
|
initialData: devices,
|
||||||
|
builder: (context, allDeviceSnapshot) {
|
||||||
|
if (allDeviceSnapshot.data?.isEmpty ?? devices.isEmpty) {
|
||||||
|
devicesManagerBloc.getDevice();
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
StreamBuilder<String>(
|
||||||
|
stream: devicesManagerBloc.streamUserRole,
|
||||||
|
initialData: role,
|
||||||
|
builder: (context, roleSnapshot) {
|
||||||
|
return PaginatedDataTable(
|
||||||
|
header: Center(
|
||||||
|
child: Text(
|
||||||
|
appLocalization(context)
|
||||||
|
.paginated_data_table_title,
|
||||||
|
style: context.titleLargeTextStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
columns: [
|
||||||
|
if (roleSnapshot.data == RoleEnums.ADMIN.name ||
|
||||||
|
roleSnapshot.data == RoleEnums.USER.name)
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_action))),
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_deviceName))),
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_deviceStatus))),
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_deviceBaterry))),
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_deviceSignal))),
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_deviceTemperature))),
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_deviceHump))),
|
||||||
|
DataColumn(
|
||||||
|
label: Center(
|
||||||
|
child: Text(appLocalization(context)
|
||||||
|
.paginated_data_table_column_devicePower))),
|
||||||
|
],
|
||||||
|
onPageChanged: (int pageIndex) {
|
||||||
|
// log('Chuyen page: $pageIndex');
|
||||||
|
},
|
||||||
|
rowsPerPage: 5,
|
||||||
|
actions: [
|
||||||
|
if (roleSnapshot.data == RoleEnums.USER.name ||
|
||||||
|
roleSnapshot.data == RoleEnums.ADMIN.name)
|
||||||
|
IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty.all<Color>(
|
||||||
|
Colors.green),
|
||||||
|
iconColor:
|
||||||
|
MaterialStateProperty.all<Color>(
|
||||||
|
Colors.white)),
|
||||||
|
onPressed: () {
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.clearSnackBars();
|
||||||
|
addNewDevice(
|
||||||
|
context, roleSnapshot.data ?? role);
|
||||||
|
},
|
||||||
|
icon: IconConstants.instance
|
||||||
|
.getMaterialIcon(Icons.add))
|
||||||
|
],
|
||||||
|
source: DeviceSource(
|
||||||
|
devices: allDeviceSnapshot.data ?? devices,
|
||||||
|
context: context,
|
||||||
|
devicesBloc: devicesManagerBloc,
|
||||||
|
role: role));
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getUserRole() async {
|
||||||
|
role = await apiServices.getUserRole();
|
||||||
|
devicesManagerBloc.sinkUserRole.add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeviceSource extends DataTableSource {
|
||||||
|
String role;
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
List<Device> devices;
|
||||||
|
final DevicesManagerBloc devicesBloc;
|
||||||
|
final BuildContext context;
|
||||||
|
DeviceSource(
|
||||||
|
{required this.devices,
|
||||||
|
required this.context,
|
||||||
|
required this.devicesBloc,
|
||||||
|
required this.role});
|
||||||
|
@override
|
||||||
|
DataRow? getRow(int index) {
|
||||||
|
if (index >= devices.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final device = devices[index];
|
||||||
|
Map<String, dynamic> sensorMap = DeviceUtils.instance
|
||||||
|
.getDeviceSensors(context, device.status?.sensors ?? []);
|
||||||
|
String deviceState =
|
||||||
|
DeviceUtils.instance.checkStateDevice(context, device.state!);
|
||||||
|
return DataRow.byIndex(
|
||||||
|
// color: getTableRowColor(device.state!),
|
||||||
|
index: index,
|
||||||
|
cells: [
|
||||||
|
if (role == RoleEnums.USER.name || role == RoleEnums.ADMIN.name)
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
// style: ButtonStyle(),
|
||||||
|
hoverColor: Colors.black,
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(AppRoutes.DEVICE_UPDATE.name,
|
||||||
|
pathParameters: {'thingID': device.thingId!});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.build, color: Colors.blue)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
handleDeleteDevice(context, device.thingId!, role);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete, color: Colors.red)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Text(device.name!,
|
||||||
|
style: TextStyle(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getTableRowColor(device.state!))), onTap: () {
|
||||||
|
// log(device.thingId.toString());
|
||||||
|
context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
|
||||||
|
pathParameters: {'thingID': device.thingId!});
|
||||||
|
}),
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Text(deviceState,
|
||||||
|
style: TextStyle(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getTableRowColor(device.state!)))), onTap: () {
|
||||||
|
// log(device.thingId.toString());
|
||||||
|
context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
|
||||||
|
pathParameters: {'thingID': device.thingId!});
|
||||||
|
}),
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Center(
|
||||||
|
child: Text(sensorMap['sensorBattery'] + "%",
|
||||||
|
style: TextStyle(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getTableRowColor(device.state!))))),
|
||||||
|
onTap: () => context.pushNamed(AppRoutes.DEVICE_DETAIL.name,
|
||||||
|
pathParameters: {'thingID': device.thingId!}),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Center(
|
||||||
|
child: Text(sensorMap['sensorCsq'],
|
||||||
|
style: TextStyle(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getTableRowColor(device.state!))))),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Text(sensorMap['sensorTemp'],
|
||||||
|
style: TextStyle(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getTableRowColor(device.state!)))),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Text(sensorMap['sensorHum'],
|
||||||
|
style: TextStyle(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getTableRowColor(device.state!)))),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Text(sensorMap['sensorVolt'],
|
||||||
|
style: TextStyle(
|
||||||
|
color: DeviceUtils.instance
|
||||||
|
.getTableRowColor(device.state!)))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get rowCount => devices.length;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isRowCountApproximate => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get selectedRowCount => 0;
|
||||||
|
}
|
||||||
36
lib/feature/error/not_found_screen.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:sfm_app/product/constant/enums/app_route_enums.dart';
|
||||||
|
import 'package:sfm_app/product/constant/image/image_constants.dart';
|
||||||
|
|
||||||
|
class NotFoundScreen extends StatelessWidget {
|
||||||
|
const NotFoundScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
ImageConstants.instance.getImage('error_page'),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.of(context).size.height * 0.15,
|
||||||
|
left: MediaQuery.of(context).size.width * 0.3,
|
||||||
|
right: MediaQuery.of(context).size.width * 0.3,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.goNamed(AppRoutes.LOGIN.name);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Go Home".toUpperCase(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
lib/feature/home/device_alias_model.dart
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
|
||||||
|
import '../devices/device_model.dart';
|
||||||
|
|
||||||
|
class DeviceWithAlias {
|
||||||
|
String? extId;
|
||||||
|
String? thingId;
|
||||||
|
String? thingKey;
|
||||||
|
List<String>? channels;
|
||||||
|
String? areaPath;
|
||||||
|
String? fvers;
|
||||||
|
String? name;
|
||||||
|
HardwareInfo? hardwareInfo;
|
||||||
|
Settings? settings;
|
||||||
|
Status? status;
|
||||||
|
DateTime? connectionTime;
|
||||||
|
int? state;
|
||||||
|
String? visibility;
|
||||||
|
DateTime? createdAt;
|
||||||
|
DateTime? updatedAt;
|
||||||
|
bool? isOwner;
|
||||||
|
String? ownerId;
|
||||||
|
String? ownerName;
|
||||||
|
String? alias;
|
||||||
|
|
||||||
|
DeviceWithAlias({
|
||||||
|
this.extId,
|
||||||
|
this.thingId,
|
||||||
|
this.thingKey,
|
||||||
|
this.channels,
|
||||||
|
this.areaPath,
|
||||||
|
this.fvers,
|
||||||
|
this.name,
|
||||||
|
this.hardwareInfo,
|
||||||
|
this.settings,
|
||||||
|
this.status,
|
||||||
|
this.connectionTime,
|
||||||
|
this.state,
|
||||||
|
this.visibility,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.isOwner,
|
||||||
|
this.ownerId,
|
||||||
|
this.ownerName,
|
||||||
|
this.alias,
|
||||||
|
});
|
||||||
|
|
||||||
|
DeviceWithAlias.fromJson(Map<String, dynamic> json) {
|
||||||
|
extId = json['ext_id'];
|
||||||
|
thingId = json['thing_id'];
|
||||||
|
thingKey = json['thing_key'];
|
||||||
|
if (json['channels'] != null) {
|
||||||
|
channels = [];
|
||||||
|
json['channels'].forEach((v) {
|
||||||
|
channels!.add(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
areaPath = json['area_path'];
|
||||||
|
fvers = json['fvers'];
|
||||||
|
name = json['name'];
|
||||||
|
hardwareInfo = json['hardware_info'] != null
|
||||||
|
? HardwareInfo.fromJson(json['hardware_info'])
|
||||||
|
: null;
|
||||||
|
settings =
|
||||||
|
json['settings'] != null ? Settings.fromJson(json['settings']) : null;
|
||||||
|
status = json['status'] != null ? Status.fromJson(json['status']) : null;
|
||||||
|
connectionTime = DateTime.parse(json['connection_time']);
|
||||||
|
state = json['state'];
|
||||||
|
visibility = json['visibility'];
|
||||||
|
createdAt = DateTime.parse(json['created_at']);
|
||||||
|
updatedAt = DateTime.parse(json['updated_at']);
|
||||||
|
isOwner = json['is_owner'];
|
||||||
|
ownerId = json['owner_id'];
|
||||||
|
ownerName = json['owner_name'];
|
||||||
|
alias = json['alias'];
|
||||||
|
}
|
||||||
|
static List<DeviceWithAlias> fromJsonDynamicList(List<dynamic> list) {
|
||||||
|
return list.map((e) => DeviceWithAlias.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
92
lib/feature/home/home_bloc.dart
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'device_alias_model.dart';
|
||||||
|
|
||||||
|
import '../../product/base/bloc/base_bloc.dart';
|
||||||
|
|
||||||
|
class HomeBloc extends BlocBase {
|
||||||
|
|
||||||
|
final allDevicesAlias = StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkAllDevicesAlias =>
|
||||||
|
allDevicesAlias.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamAllDevicesAlias =>
|
||||||
|
allDevicesAlias.stream;
|
||||||
|
|
||||||
|
final onlineDevicesAlias =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkOnlineDevicesAlias =>
|
||||||
|
onlineDevicesAlias.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamOnlineDevicesAlias =>
|
||||||
|
onlineDevicesAlias.stream;
|
||||||
|
|
||||||
|
final offlineDevicesAlias =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkOfflineDevicesAlias =>
|
||||||
|
offlineDevicesAlias.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamOfflineDevicesAlias =>
|
||||||
|
offlineDevicesAlias.stream;
|
||||||
|
|
||||||
|
final warningDevicesAlias =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkWarningDevicesAlias =>
|
||||||
|
warningDevicesAlias.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamWarningDevicesAlias =>
|
||||||
|
warningDevicesAlias.stream;
|
||||||
|
|
||||||
|
final notUseDevicesAlias =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkNotUseDevicesAlias =>
|
||||||
|
notUseDevicesAlias.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamNotUseDevicesAlias =>
|
||||||
|
notUseDevicesAlias.stream;
|
||||||
|
|
||||||
|
final allDevicesAliasJoined =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkAllDevicesAliasJoined =>
|
||||||
|
allDevicesAliasJoined.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamAllDevicesAliasJoined =>
|
||||||
|
allDevicesAliasJoined.stream;
|
||||||
|
|
||||||
|
final onlineDevicesAliasJoined =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkOnlineDevicesAliasJoined =>
|
||||||
|
onlineDevicesAliasJoined.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamOnlineDevicesAliasJoined =>
|
||||||
|
onlineDevicesAliasJoined.stream;
|
||||||
|
|
||||||
|
final offlineDevicesAliasJoined =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkOfflineDevicesAliasJoined =>
|
||||||
|
offlineDevicesAliasJoined.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamOfflineDevicesAliasJoined =>
|
||||||
|
offlineDevicesAliasJoined.stream;
|
||||||
|
|
||||||
|
final warningDevicesAliasJoined =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkWarningDevicesAliasJoined =>
|
||||||
|
warningDevicesAliasJoined.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamWarningDevicesAliasJoined =>
|
||||||
|
warningDevicesAliasJoined.stream;
|
||||||
|
|
||||||
|
final notUseDevicesAliasJoined =
|
||||||
|
StreamController<List<DeviceWithAlias>>.broadcast();
|
||||||
|
StreamSink<List<DeviceWithAlias>> get sinkNotUseDevicesAliasJoined =>
|
||||||
|
notUseDevicesAliasJoined.sink;
|
||||||
|
Stream<List<DeviceWithAlias>> get streamNotUseDevicesAliasJoined =>
|
||||||
|
notUseDevicesAliasJoined.stream;
|
||||||
|
|
||||||
|
final countNotitication = StreamController<int>.broadcast();
|
||||||
|
StreamSink<int> get sinkCountNotitication => countNotitication.sink;
|
||||||
|
Stream<int> get streamCountNotitication => countNotitication.stream;
|
||||||
|
|
||||||
|
final ownerDevicesStatus =
|
||||||
|
StreamController<Map<String, List<DeviceWithAlias>>>.broadcast();
|
||||||
|
StreamSink<Map<String, List<DeviceWithAlias>>>
|
||||||
|
get sinkOwnerDevicesStatus => ownerDevicesStatus.sink;
|
||||||
|
Stream<Map<String, List<DeviceWithAlias>>> get streamOwnerDevicesStatus =>
|
||||||
|
ownerDevicesStatus.stream;
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {}
|
||||||
|
}
|
||||||
411
lib/feature/home/home_screen.dart
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'shared/alert_card.dart';
|
||||||
|
import 'shared/warning_card.dart';
|
||||||
|
import '../../product/utils/device_utils.dart';
|
||||||
|
import 'device_alias_model.dart';
|
||||||
|
import 'shared/overview_card.dart';
|
||||||
|
import '../settings/device_notification_settings/device_notification_settings_model.dart';
|
||||||
|
import '../../product/extention/context_extention.dart';
|
||||||
|
import '../../product/services/api_services.dart';
|
||||||
|
import '../../product/services/language_services.dart';
|
||||||
|
import 'home_bloc.dart';
|
||||||
|
import '../../product/base/bloc/base_bloc.dart';
|
||||||
|
|
||||||
|
class HomeScreen extends StatefulWidget {
|
||||||
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
late HomeBloc homeBloc;
|
||||||
|
APIServices apiServices = APIServices();
|
||||||
|
List<DeviceWithAlias> devices = [];
|
||||||
|
List<DeviceWithAlias> allDevicesAlias = [];
|
||||||
|
List<DeviceWithAlias> onlineDevicesAlias = [];
|
||||||
|
List<DeviceWithAlias> offlineDevicesAlias = [];
|
||||||
|
List<DeviceWithAlias> warningDevicesAlias = [];
|
||||||
|
List<DeviceWithAlias> notUseDevicesAlias = [];
|
||||||
|
|
||||||
|
List<DeviceWithAlias> allDevicesAliasJoined = [];
|
||||||
|
List<DeviceWithAlias> onlineDevicesAliasJoined = [];
|
||||||
|
List<DeviceWithAlias> offlineDevicesAliasJoined = [];
|
||||||
|
List<DeviceWithAlias> warningDevicesAliasJoined = [];
|
||||||
|
List<DeviceWithAlias> notUseDevicesAliasJoined = [];
|
||||||
|
bool isFunctionCall = false;
|
||||||
|
Timer? getAllDevicesTimer;
|
||||||
|
int notificationCount = 0;
|
||||||
|
Map<String, List<DeviceWithAlias>> ownerDevicesStatus = {};
|
||||||
|
List<String> ownerDevicesState = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
homeBloc = BlocProvider.of(context);
|
||||||
|
const duration = Duration(seconds: 20);
|
||||||
|
getOwnerAndJoinedDevices();
|
||||||
|
getAllDevicesTimer =
|
||||||
|
Timer.periodic(duration, (Timer t) => getOwnerAndJoinedDevices());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
getAllDevicesTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: context.paddingLow,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
appLocalization(context).notification,
|
||||||
|
style: context.titleMediumTextStyle,
|
||||||
|
),
|
||||||
|
SizedBox(width: context.lowValue),
|
||||||
|
StreamBuilder<int>(
|
||||||
|
stream: homeBloc.streamCountNotitication,
|
||||||
|
builder: (context, countSnapshot) {
|
||||||
|
return Text(
|
||||||
|
"(${countSnapshot.data ?? 0})",
|
||||||
|
style: context.titleMediumTextStyle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: StreamBuilder<Map<String, List<DeviceWithAlias>>>(
|
||||||
|
stream: homeBloc.streamOwnerDevicesStatus,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data?['state'] != null ||
|
||||||
|
snapshot.data?['battery'] != null) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (snapshot.data?['state'] != null)
|
||||||
|
...snapshot.data!['state']!
|
||||||
|
.map(
|
||||||
|
(item) => FutureBuilder<Widget>(
|
||||||
|
future: warningCard(
|
||||||
|
context, apiServices, item),
|
||||||
|
builder: (context, warningCardSnapshot) {
|
||||||
|
if (warningCardSnapshot.hasData) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400,
|
||||||
|
maxHeight: 260,
|
||||||
|
),
|
||||||
|
child: warningCardSnapshot.data!,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
if (snapshot.data?['battery'] != null)
|
||||||
|
...snapshot.data!['battery']!
|
||||||
|
.map(
|
||||||
|
(batteryItem) => FutureBuilder<Widget>(
|
||||||
|
future: notificationCard(
|
||||||
|
context,
|
||||||
|
"lowBattery",
|
||||||
|
"Cảnh báo pin yếu",
|
||||||
|
batteryItem.name!,
|
||||||
|
batteryItem.areaPath!,
|
||||||
|
),
|
||||||
|
builder: (context, warningCardSnapshot) {
|
||||||
|
if (warningCardSnapshot.hasData) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400,
|
||||||
|
maxHeight: 260,
|
||||||
|
),
|
||||||
|
child: warningCardSnapshot.data!,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return Padding(
|
||||||
|
padding: context.paddingMedium,
|
||||||
|
child: Center(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.check_circle_outline_rounded,
|
||||||
|
size: 40,
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
SizedBox(width: context.lowValue),
|
||||||
|
Text(
|
||||||
|
appLocalization(context)
|
||||||
|
.notification_description,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: true,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamAllDevicesAlias,
|
||||||
|
initialData: allDevicesAlias,
|
||||||
|
builder: (context, allDeviceSnapshot) {
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamOnlineDevicesAlias,
|
||||||
|
initialData: onlineDevicesAlias,
|
||||||
|
builder: (context, onlineDeviceSnapshot) {
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamOfflineDevicesAlias,
|
||||||
|
initialData: offlineDevicesAlias,
|
||||||
|
builder: (context, deviceDashboardSnapshot) {
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamWarningDevicesAlias,
|
||||||
|
initialData: warningDevicesAlias,
|
||||||
|
builder: (context, warningDeviceSnapshot) {
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamNotUseDevicesAlias,
|
||||||
|
initialData: notUseDevicesAlias,
|
||||||
|
builder: (context, notUseSnapshot) {
|
||||||
|
return OverviewCard(
|
||||||
|
isOwner: true,
|
||||||
|
total: allDeviceSnapshot.data!.length,
|
||||||
|
active: onlineDeviceSnapshot.data!.length,
|
||||||
|
inactive:
|
||||||
|
deviceDashboardSnapshot.data!.length,
|
||||||
|
warning: warningDeviceSnapshot.data!.length,
|
||||||
|
unused: notUseSnapshot.data!.length,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: context.lowValue),
|
||||||
|
StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamAllDevicesAliasJoined,
|
||||||
|
initialData: allDevicesAliasJoined,
|
||||||
|
builder: (context, allDeviceSnapshot) {
|
||||||
|
if (allDeviceSnapshot.data?.isEmpty ?? true) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamOnlineDevicesAliasJoined,
|
||||||
|
initialData: onlineDevicesAliasJoined,
|
||||||
|
builder: (context, onlineDeviceSnapshot) {
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamOfflineDevicesAliasJoined,
|
||||||
|
initialData: offlineDevicesAliasJoined,
|
||||||
|
builder: (context, deviceDashboardSnapshot) {
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamWarningDevicesAliasJoined,
|
||||||
|
initialData: warningDevicesAliasJoined,
|
||||||
|
builder: (context, warningDeviceSnapshot) {
|
||||||
|
return StreamBuilder<List<DeviceWithAlias>>(
|
||||||
|
stream: homeBloc.streamNotUseDevicesAliasJoined,
|
||||||
|
initialData: notUseDevicesAliasJoined,
|
||||||
|
builder: (context, notUseSnapshot) {
|
||||||
|
return OverviewCard(
|
||||||
|
isOwner: false,
|
||||||
|
total: allDeviceSnapshot.data!.length,
|
||||||
|
active: onlineDeviceSnapshot.data!.length,
|
||||||
|
inactive:
|
||||||
|
deviceDashboardSnapshot.data!.length,
|
||||||
|
warning: warningDeviceSnapshot.data!.length,
|
||||||
|
unused: notUseSnapshot.data!.length,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getOwnerAndJoinedDevices() async {
|
||||||
|
String response = await apiServices.getDashBoardDevices();
|
||||||
|
final data = jsonDecode(response);
|
||||||
|
List<dynamic> result = data["items"];
|
||||||
|
devices = DeviceWithAlias.fromJsonDynamicList(result);
|
||||||
|
getOwnerDeviceState(devices);
|
||||||
|
getDevicesStatusAlias(devices);
|
||||||
|
checkSettingdevice(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.sinkCountNotitication.add(notificationCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getDevicesStatusAlias(List<DeviceWithAlias> devices) async {
|
||||||
|
clearAllDeviceStatusAlias();
|
||||||
|
for (DeviceWithAlias device in devices) {
|
||||||
|
if (device.isOwner == true) {
|
||||||
|
allDevicesAlias.add(device);
|
||||||
|
if (device.state! == 0 || device.state! == 1) {
|
||||||
|
onlineDevicesAlias.add(device);
|
||||||
|
homeBloc.sinkOnlineDevicesAlias.add(onlineDevicesAlias);
|
||||||
|
}
|
||||||
|
if (device.state! == -1) {
|
||||||
|
offlineDevicesAlias.add(device);
|
||||||
|
homeBloc.sinkOfflineDevicesAlias.add(offlineDevicesAlias);
|
||||||
|
}
|
||||||
|
if (device.state! == 1) {
|
||||||
|
warningDevicesAlias.add(device);
|
||||||
|
homeBloc.sinkWarningDevicesAlias.add(warningDevicesAlias);
|
||||||
|
}
|
||||||
|
if (device.state! == -2) {
|
||||||
|
notUseDevicesAlias.add(device);
|
||||||
|
homeBloc.sinkNotUseDevicesAlias.add(notUseDevicesAlias);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allDevicesAliasJoined.add(device);
|
||||||
|
if (device.state! == 0 || device.state! == 1) {
|
||||||
|
onlineDevicesAliasJoined.add(device);
|
||||||
|
homeBloc.sinkOnlineDevicesAliasJoined.add(onlineDevicesAliasJoined);
|
||||||
|
}
|
||||||
|
if (device.state! == -1) {
|
||||||
|
offlineDevicesAliasJoined.add(device);
|
||||||
|
homeBloc.sinkOfflineDevicesAliasJoined.add(offlineDevicesAliasJoined);
|
||||||
|
}
|
||||||
|
if (device.state! == 1) {
|
||||||
|
warningDevicesAliasJoined.add(device);
|
||||||
|
homeBloc.sinkWarningDevicesAliasJoined.add(warningDevicesAliasJoined);
|
||||||
|
}
|
||||||
|
if (device.state! == -2) {
|
||||||
|
notUseDevicesAliasJoined.add(device);
|
||||||
|
homeBloc.sinkNotUseDevicesAliasJoined.add(notUseDevicesAliasJoined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkSettingdevice(allDevicesAliasJoined);
|
||||||
|
homeBloc.sinkAllDevicesAlias.add(allDevicesAlias);
|
||||||
|
homeBloc.sinkAllDevicesAliasJoined.add(allDevicesAliasJoined);
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkSettingdevice(List<DeviceWithAlias> devices) async {
|
||||||
|
if (isFunctionCall) {
|
||||||
|
} else {
|
||||||
|
String? response =
|
||||||
|
await apiServices.getAllSettingsNotificationOfDevices();
|
||||||
|
if (response != "") {
|
||||||
|
final data = jsonDecode(response);
|
||||||
|
final result = data['data'];
|
||||||
|
// log("Data ${DeviceNotificationSettings.mapFromJson(jsonDecode(data)).values.toList()}");
|
||||||
|
List<DeviceNotificationSettings> list =
|
||||||
|
DeviceNotificationSettings.mapFromJson(result).values.toList();
|
||||||
|
// log("List: $list");
|
||||||
|
Set<String> thingIdsInList =
|
||||||
|
list.map((device) => device.thingId!).toSet();
|
||||||
|
for (var device in devices) {
|
||||||
|
if (!thingIdsInList.contains(device.thingId)) {
|
||||||
|
log("Device with Thing ID ${device.thingId} is not in the notification settings list.");
|
||||||
|
await apiServices.setupDeviceNotification(
|
||||||
|
device.thingId!, device.name!);
|
||||||
|
} else {
|
||||||
|
log("All devives are in the notification settings list.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log("apiServices: getAllSettingsNotificationofDevices error!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isFunctionCall = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearAllDeviceStatusAlias() {
|
||||||
|
allDevicesAlias.clear();
|
||||||
|
homeBloc.sinkAllDevicesAlias.add(allDevicesAlias);
|
||||||
|
onlineDevicesAlias.clear();
|
||||||
|
homeBloc.sinkOnlineDevicesAlias.add(onlineDevicesAlias);
|
||||||
|
offlineDevicesAlias.clear();
|
||||||
|
homeBloc.sinkOfflineDevicesAlias.add(offlineDevicesAlias);
|
||||||
|
warningDevicesAlias.clear();
|
||||||
|
homeBloc.sinkWarningDevicesAlias.add(warningDevicesAlias);
|
||||||
|
notUseDevicesAlias.clear();
|
||||||
|
homeBloc.sinkNotUseDevicesAlias.add(notUseDevicesAlias);
|
||||||
|
allDevicesAliasJoined.clear();
|
||||||
|
homeBloc.sinkAllDevicesAliasJoined.add(allDevicesAliasJoined);
|
||||||
|
onlineDevicesAliasJoined.clear();
|
||||||
|
homeBloc.sinkOnlineDevicesAliasJoined.add(onlineDevicesAliasJoined);
|
||||||
|
offlineDevicesAliasJoined.clear();
|
||||||
|
homeBloc.sinkOfflineDevicesAliasJoined.add(offlineDevicesAliasJoined);
|
||||||
|
warningDevicesAliasJoined.clear();
|
||||||
|
homeBloc.sinkWarningDevicesAliasJoined.add(warningDevicesAliasJoined);
|
||||||
|
notUseDevicesAliasJoined.clear();
|
||||||
|
homeBloc.sinkNotUseDevicesAliasJoined.add(notUseDevicesAliasJoined);
|
||||||
|
}
|
||||||
|
}
|
||||||