diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..e77609d --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +ENV_NAME=development +BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..4e9dcb8 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +ENV_NAME=production +BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file diff --git a/.env.staging b/.env.staging new file mode 100644 index 0000000..9565b42 --- /dev/null +++ b/.env.staging @@ -0,0 +1,2 @@ +ENV_NAME=staging +BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3a83c2f..c3e0679 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,42 @@ -# See https://www.dartlang.org/guides/libraries/private-files +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ -# Files and directories created by pub +# 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/ +*.env +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id .dart_tool/ -.packages -build/ -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -# dotenv environment variables file -.env* - -# Avoid committing generated Javascript files: -*.dart.js -*.info.json # Produced by the --dump-info flag. -*.js # When generated by dart2js. Don't specify *.js if your - # project includes source files written in JavaScript. -*.js_ -*.js.deps -*.js.map - .flutter-plugins .flutter-plugins-dependencies +.pub-cache/ +/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 diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..b2bb4f6 --- /dev/null +++ b/.metadata @@ -0,0 +1,36 @@ +# 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 and should not be manually edited. + +version: + revision: "ef1af02aead6fe2414f3aafa5a61087b610e1332" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + - platform: android + create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + - platform: ios + create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + - platform: web + create_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + base_revision: ef1af02aead6fe2414f3aafa5a61087b610e1332 + + # 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' diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b6f83bd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,61 @@ +{ + "configurations": [ + + { + + "name": "DEVELOPMENT", + + "request": "launch", + + "type": "dart", + + "args": [ + + "--dart-define", + + "FLAVOR=development" + + ], + + "flutterMode": "debug" + + },{ + + "name": "STAGING", + + "request": "launch", + + "type": "dart", + + "args": [ + + "--dart-define", + + "FLAVOR=staging" + + ], + + "flutterMode": "debug" + + },{ + + "name": "PRODUCTION", + + "request": "launch", + + "type": "dart", + + "args": [ + + "--dart-define", + + "FLAVOR=production" + + ], + + "flutterMode": "debug" + + }, + + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..418c018 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# syncrow_app + +This is the mobile application project, developed with Flutter for Syncrow IOT Project.. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + +## Development Process + +1- You'll receive a task assignment in Jira that's been assigned to you. + +2- In Jira, change the status of the task to "in progress". + +3- Create a new branch for the task using the command "git checkout -b task_number". + +4- Make your changes and commit them using the command "git commit -m 'Add my changes'". + +5- Push your changes to the task branch using the command "git push origin task-branch". +Open a pull request on the DEV branch and add a reviewer to it. + +6- Once the reviewer approves your pull request, merge your changes into the DEV branch. + +7- Use the command "git checkout DEV" to switch to the DEV branch. + +8- Upload apk file and ipa file to Firebase distribution (you can find the steps to upload your app to Firebase Distribution for both Android and iOS platforms in the Deployment section). + +9- Update the task status in Jira to "QA". +## Deployment + +### Android + +**- Firebase Distribution:** + +To test the app, we use Firebase Distribution to send testing builds of the app to the testers. + +- Create an Android build for testing with the command: `flutter build apk --dart-define FLAVOR=staging --build-name={build_version}-qa --build-number={build_number}`. + +- Upload the apk file to Firebase distribution + +**- Google Play Store:** + +1- To create an APK file from your Flutter project, you can use the command: `flutter build apk --release` + +2- Upload your APK file to the Google Play Console and provide all necessary information about the release, such as release notes and version number. + +3- Submit your app for review, which can take several days to complete. Once your app is approved, it will be available for download on the Google Play Store. + +### iOS + +**- Firebase Distribution:** + +To test the app, we use Firebase Distribution to send testing builds of the app to the testers. + +1- Create an iOS for testing with the command: `flutter build ios --dart-define FLAVOR=staging --build-name=1.0.0-qa --build-number=1`. + +2- Create an archive of your app: Open Xcode and go to the "Product" menu, then select "Archive" to create an archive of your app. Make sure to select the "Generic iOS Device" as the build destination. + +3- Once the archive is complete, go to the "Organizer" window and select the archive you just created and click on the "Distribute App" button. + +4- Choose "Ad Hoc" as the distribution method and click "Next". + +5- Choose the appropriate signing identity and click "Export". + +6- Choose a location to save the exported app and click "Save". + +7- Your HOC build is now ready to be signed and uploaded to Firebase Distribution. + +8- Upload the ipa file to Firebase distribution + +You can also create an archive through these commands lines: + +1- Create an iOS for testing with the command: `flutter build ios --dart-define FLAVOR=staging --build-name={build_version}-qa --build-number={build_number}`. + +2- Create an archive with this command: `xcodebuild -workspace Runner.xcworkspace -scheme Runner -archivePath "build/Runner.xcarchive" archive`. + +3- Export the ipa file with this command: `-exportArchive -archivePath "build/Runner.xcarchive" -exportPath "build/exported_ipas" -exportOptionsPlist "build/ExportOptions.plist"` + +**- Apple Store** + +1- Build your app: Use Flutter to build your app for release. To build your app for iOS, use the command: `flutter build ios --release --no-codesign` + +Note: The --no-codesign flag will tell Flutter not to sign your app, since you'll be doing that later with Xcode. + +2- Create an archive of your app: Open Xcode and go to the "Product" menu, then select "Archive" to create an archive of your app. Make sure to select the "Generic iOS Device" as the build destination. + +3- Validate and upload your app: Once the archive is created, Xcode will automatically open the Organizer window. Select the archive you just created and click the "Validate App" button to validate your app. Once the validation process is complete, click the "Distribute App" button to upload your app to the App Store Connect. + +4- Submit your app for review: After uploading your app, you'll need to submit it for review by Apple. This process can take several days, and you'll be notified by email once your app is approved or rejected. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# 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.dev/lints. + # + # 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 diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -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 diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..4a14eae --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,71 @@ +plugins { + id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + id 'com.google.firebase.crashlytics' + // END: FlutterFire Configuration + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.syncrow_application" + compileSdkVersion 34 + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.syncrow_application" + // 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 21 + targetSdkVersion flutter.targetSdkVersion + 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 {} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..b304aae --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "427332280600", + "project_id": "test2-8a3d2", + "storage_bucket": "test2-8a3d2.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:427332280600:android:550f67441246cb1a0c7e6d", + "android_client_info": { + "package_name": "com.example.syncrow_application" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:427332280600:android:bb6047adeeb80fb00c7e6d", + "android_client_info": { + "package_name": "com.example.syncrow_application" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ad16c69 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/syncrow_app/MainActivity.kt b/android/app/src/main/kotlin/com/example/syncrow_app/MainActivity.kt new file mode 100644 index 0000000..1e03aee --- /dev/null +++ b/android/app/src/main/kotlin/com/example/syncrow_app/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.syncrow_application + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..fdc23c4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..63a7e34 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..629b51e Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..f2bc9ef Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..837c4b2 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + 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 +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..60df464 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 03 23:37:40 EET 2024 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..0e6c956 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,33 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + id "com.google.firebase.crashlytics" version "2.8.1" apply false + // END: FlutterFire Configuration +} + +include ":app" diff --git a/assets/fonts/AftikaRegular.ttf b/assets/fonts/AftikaRegular.ttf new file mode 100644 index 0000000..5ca4dc5 Binary files /dev/null and b/assets/fonts/AftikaRegular.ttf differ diff --git a/assets/icons/3GangSwitch.svg b/assets/icons/3GangSwitch.svg new file mode 100644 index 0000000..ecb9992 --- /dev/null +++ b/assets/icons/3GangSwitch.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/AC.svg b/assets/icons/AC.svg new file mode 100644 index 0000000..92f6fc5 --- /dev/null +++ b/assets/icons/AC.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/CO2.svg b/assets/icons/CO2.svg new file mode 100644 index 0000000..09a2db6 --- /dev/null +++ b/assets/icons/CO2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/Curtain.svg b/assets/icons/Curtain.svg new file mode 100644 index 0000000..a2e4f23 --- /dev/null +++ b/assets/icons/Curtain.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/Devices-fill.svg b/assets/icons/Devices-fill.svg new file mode 100644 index 0000000..de6e9af --- /dev/null +++ b/assets/icons/Devices-fill.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/Devices.svg b/assets/icons/Devices.svg new file mode 100644 index 0000000..51d527c --- /dev/null +++ b/assets/icons/Devices.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/assets/icons/DoorLockLinkage.svg b/assets/icons/DoorLockLinkage.svg new file mode 100644 index 0000000..b9730e3 --- /dev/null +++ b/assets/icons/DoorLockLinkage.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/DoorLockLock.svg b/assets/icons/DoorLockLock.svg new file mode 100644 index 0000000..931e17f --- /dev/null +++ b/assets/icons/DoorLockLock.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/DoorLockMembers.svg b/assets/icons/DoorLockMembers.svg new file mode 100644 index 0000000..f7a60fb --- /dev/null +++ b/assets/icons/DoorLockMembers.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/DoorLockPassword.svg b/assets/icons/DoorLockPassword.svg new file mode 100644 index 0000000..583035b --- /dev/null +++ b/assets/icons/DoorLockPassword.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/DoorLockRecords.svg b/assets/icons/DoorLockRecords.svg new file mode 100644 index 0000000..125f3d9 --- /dev/null +++ b/assets/icons/DoorLockRecords.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/Facebook.svg b/assets/icons/Facebook.svg new file mode 100644 index 0000000..929d386 --- /dev/null +++ b/assets/icons/Facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/Gateway.svg b/assets/icons/Gateway.svg new file mode 100644 index 0000000..e293999 --- /dev/null +++ b/assets/icons/Gateway.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/Google.svg b/assets/icons/Google.svg new file mode 100644 index 0000000..750ccdd --- /dev/null +++ b/assets/icons/Google.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/Layout-fill.svg b/assets/icons/Layout-fill.svg new file mode 100644 index 0000000..0e69648 --- /dev/null +++ b/assets/icons/Layout-fill.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/Layout.svg b/assets/icons/Layout.svg new file mode 100644 index 0000000..4756e3b --- /dev/null +++ b/assets/icons/Layout.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/assets/icons/Light.svg b/assets/icons/Light.svg new file mode 100644 index 0000000..c8cfff5 --- /dev/null +++ b/assets/icons/Light.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/Menu-fill.svg b/assets/icons/Menu-fill.svg new file mode 100644 index 0000000..db5821c --- /dev/null +++ b/assets/icons/Menu-fill.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/Menu.svg b/assets/icons/Menu.svg new file mode 100644 index 0000000..072d3e9 --- /dev/null +++ b/assets/icons/Menu.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/MenuIcons/GeneralSettingsIcons/clearCach.svg b/assets/icons/MenuIcons/GeneralSettingsIcons/clearCach.svg new file mode 100644 index 0000000..20ec573 --- /dev/null +++ b/assets/icons/MenuIcons/GeneralSettingsIcons/clearCach.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/GeneralSettingsIcons/language.svg b/assets/icons/MenuIcons/GeneralSettingsIcons/language.svg new file mode 100644 index 0000000..15bd55d --- /dev/null +++ b/assets/icons/MenuIcons/GeneralSettingsIcons/language.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/GeneralSettingsIcons/networkDiagnosis.svg b/assets/icons/MenuIcons/GeneralSettingsIcons/networkDiagnosis.svg new file mode 100644 index 0000000..89edd09 --- /dev/null +++ b/assets/icons/MenuIcons/GeneralSettingsIcons/networkDiagnosis.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/GeneralSettingsIcons/temperatureUnit.svg b/assets/icons/MenuIcons/GeneralSettingsIcons/temperatureUnit.svg new file mode 100644 index 0000000..ff1291a --- /dev/null +++ b/assets/icons/MenuIcons/GeneralSettingsIcons/temperatureUnit.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/MenuIcons/GeneralSettingsIcons/touchTone.svg b/assets/icons/MenuIcons/GeneralSettingsIcons/touchTone.svg new file mode 100644 index 0000000..2d1b5eb --- /dev/null +++ b/assets/icons/MenuIcons/GeneralSettingsIcons/touchTone.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/MenuIcons/GeneralSettingsIcons/voiceAssistant.svg b/assets/icons/MenuIcons/GeneralSettingsIcons/voiceAssistant.svg new file mode 100644 index 0000000..053b9ae --- /dev/null +++ b/assets/icons/MenuIcons/GeneralSettingsIcons/voiceAssistant.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg b/assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg new file mode 100644 index 0000000..5d7d222 --- /dev/null +++ b/assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/MenuIcons/HomeManagementIcons/ManageYourHome.svg b/assets/icons/MenuIcons/HomeManagementIcons/ManageYourHome.svg new file mode 100644 index 0000000..6e729d7 --- /dev/null +++ b/assets/icons/MenuIcons/HomeManagementIcons/ManageYourHome.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg b/assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg new file mode 100644 index 0000000..1ad882a --- /dev/null +++ b/assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/LeagalInfoIcons/About.svg b/assets/icons/MenuIcons/LeagalInfoIcons/About.svg new file mode 100644 index 0000000..47e30f5 --- /dev/null +++ b/assets/icons/MenuIcons/LeagalInfoIcons/About.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/MenuIcons/LeagalInfoIcons/PrivacyPolicy.svg b/assets/icons/MenuIcons/LeagalInfoIcons/PrivacyPolicy.svg new file mode 100644 index 0000000..c5d1e3d --- /dev/null +++ b/assets/icons/MenuIcons/LeagalInfoIcons/PrivacyPolicy.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/LeagalInfoIcons/UserAgreement.svg b/assets/icons/MenuIcons/LeagalInfoIcons/UserAgreement.svg new file mode 100644 index 0000000..6cabfad --- /dev/null +++ b/assets/icons/MenuIcons/LeagalInfoIcons/UserAgreement.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/MessagesCenterIcons/Alerts.svg b/assets/icons/MenuIcons/MessagesCenterIcons/Alerts.svg new file mode 100644 index 0000000..7762dd4 --- /dev/null +++ b/assets/icons/MenuIcons/MessagesCenterIcons/Alerts.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/MenuIcons/MessagesCenterIcons/FAQs.svg b/assets/icons/MenuIcons/MessagesCenterIcons/FAQs.svg new file mode 100644 index 0000000..ab3dc9f --- /dev/null +++ b/assets/icons/MenuIcons/MessagesCenterIcons/FAQs.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/MenuIcons/MessagesCenterIcons/HelpAndFeedback.svg b/assets/icons/MenuIcons/MessagesCenterIcons/HelpAndFeedback.svg new file mode 100644 index 0000000..fb53dd0 --- /dev/null +++ b/assets/icons/MenuIcons/MessagesCenterIcons/HelpAndFeedback.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/MessagesCenterIcons/Messages.svg b/assets/icons/MenuIcons/MessagesCenterIcons/Messages.svg new file mode 100644 index 0000000..c9a7e35 --- /dev/null +++ b/assets/icons/MenuIcons/MessagesCenterIcons/Messages.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MenuIcons/SecurityAndPrivacyIcons/Privacy.svg b/assets/icons/MenuIcons/SecurityAndPrivacyIcons/Privacy.svg new file mode 100644 index 0000000..69ea2cd --- /dev/null +++ b/assets/icons/MenuIcons/SecurityAndPrivacyIcons/Privacy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/MenuIcons/SecurityAndPrivacyIcons/Securty.svg b/assets/icons/MenuIcons/SecurityAndPrivacyIcons/Securty.svg new file mode 100644 index 0000000..beecb3e --- /dev/null +++ b/assets/icons/MenuIcons/SecurityAndPrivacyIcons/Securty.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/Routines-fill.svg b/assets/icons/Routines-fill.svg new file mode 100644 index 0000000..99cbc5d --- /dev/null +++ b/assets/icons/Routines-fill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/Routines.svg b/assets/icons/Routines.svg new file mode 100644 index 0000000..adcf9b8 --- /dev/null +++ b/assets/icons/Routines.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/Scan.svg b/assets/icons/Scan.svg new file mode 100644 index 0000000..11fed1b --- /dev/null +++ b/assets/icons/Scan.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/Screen.svg b/assets/icons/Screen.svg new file mode 100644 index 0000000..cc0cd8f --- /dev/null +++ b/assets/icons/Screen.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/Summer.svg b/assets/icons/Summer.svg new file mode 100644 index 0000000..32a0c3c --- /dev/null +++ b/assets/icons/Summer.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/Vector-1.svg b/assets/icons/Vector-1.svg new file mode 100644 index 0000000..ce3fd9d --- /dev/null +++ b/assets/icons/Vector-1.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/Vector.svg b/assets/icons/Vector.svg new file mode 100644 index 0000000..2b26ff9 --- /dev/null +++ b/assets/icons/Vector.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/Wifi.svg b/assets/icons/Wifi.svg new file mode 100644 index 0000000..4f795c7 --- /dev/null +++ b/assets/icons/Wifi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/Winter.svg b/assets/icons/Winter.svg new file mode 100644 index 0000000..1494cc0 --- /dev/null +++ b/assets/icons/Winter.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/Winter_mode.svg b/assets/icons/Winter_mode.svg new file mode 100644 index 0000000..6293ee6 --- /dev/null +++ b/assets/icons/Winter_mode.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/ac_switch_ic.svg b/assets/icons/ac_switch_ic.svg new file mode 100644 index 0000000..3ee128e --- /dev/null +++ b/assets/icons/ac_switch_ic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/active.svg b/assets/icons/active.svg new file mode 100644 index 0000000..7ea03f0 --- /dev/null +++ b/assets/icons/active.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/add.svg b/assets/icons/add.svg new file mode 100644 index 0000000..bbd96d7 --- /dev/null +++ b/assets/icons/add.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/automated_clock.svg b/assets/icons/automated_clock.svg new file mode 100644 index 0000000..f537623 --- /dev/null +++ b/assets/icons/automated_clock.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstChargeddmOff.svg b/assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstChargeddmOff.svg new file mode 100644 index 0000000..93cb142 --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstChargeddmOff.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstDefaultdmOff.svg b/assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstDefaultdmOff.svg new file mode 100644 index 0000000..e8c4852 --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstDefaultdmOff.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOfflowOffpmOnstChargeddmOff.svg b/assets/icons/battery/dmOff/perOffchargOfflowOffpmOnstChargeddmOff.svg new file mode 100644 index 0000000..d4c7880 --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOfflowOffpmOnstChargeddmOff.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOfflowOnpmOffstDefaultdmOff.svg b/assets/icons/battery/dmOff/perOffchargOfflowOnpmOffstDefaultdmOff.svg new file mode 100644 index 0000000..dfaf086 --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOfflowOnpmOffstDefaultdmOff.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOfflowOnpmOnstDefaultdmOff.svg b/assets/icons/battery/dmOff/perOffchargOfflowOnpmOnstDefaultdmOff.svg new file mode 100644 index 0000000..e82252c --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOfflowOnpmOnstDefaultdmOff.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOnlowOffpmOffstChargeddmOff.svg b/assets/icons/battery/dmOff/perOffchargOnlowOffpmOffstChargeddmOff.svg new file mode 100644 index 0000000..a7e3893 --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOnlowOffpmOffstChargeddmOff.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOnlowOnpmOffstlowBatterydmOff.svg b/assets/icons/battery/dmOff/perOffchargOnlowOnpmOffstlowBatterydmOff.svg new file mode 100644 index 0000000..6a58104 --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOnlowOnpmOffstlowBatterydmOff.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOff/perOffchargOnlowOnpmOnstlowpmdmOff.svg b/assets/icons/battery/dmOff/perOffchargOnlowOnpmOnstlowpmdmOff.svg new file mode 100644 index 0000000..7664a08 --- /dev/null +++ b/assets/icons/battery/dmOff/perOffchargOnlowOnpmOnstlowpmdmOff.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstChargeddmOff.svg b/assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstChargeddmOff.svg new file mode 100644 index 0000000..b1d3600 --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstChargeddmOff.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstDefaultdmOff.svg b/assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstDefaultdmOff.svg new file mode 100644 index 0000000..44f83ec --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstDefaultdmOff.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOfflowOffpmOnstChargeddmOff.svg b/assets/icons/battery/dmOff/perOnchargOfflowOffpmOnstChargeddmOff.svg new file mode 100644 index 0000000..8aa497f --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOfflowOffpmOnstChargeddmOff.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOfflowOnpmOffstDefaultdmOff.svg b/assets/icons/battery/dmOff/perOnchargOfflowOnpmOffstDefaultdmOff.svg new file mode 100644 index 0000000..792f06b --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOfflowOnpmOffstDefaultdmOff.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOfflowOnpmOnstDefaultdmOff.svg b/assets/icons/battery/dmOff/perOnchargOfflowOnpmOnstDefaultdmOff.svg new file mode 100644 index 0000000..f712afe --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOfflowOnpmOnstDefaultdmOff.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOnlowOffpmOffstChargeddmOff.svg b/assets/icons/battery/dmOff/perOnchargOnlowOffpmOffstChargeddmOff.svg new file mode 100644 index 0000000..f56167d --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOnlowOffpmOffstChargeddmOff.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOnlowOnpmOffstlowBatterydmOff.svg b/assets/icons/battery/dmOff/perOnchargOnlowOnpmOffstlowBatterydmOff.svg new file mode 100644 index 0000000..d580188 --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOnlowOnpmOffstlowBatterydmOff.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/battery/dmOff/perOnchargOnlowOnpmOnstlowpmdmOff.svg b/assets/icons/battery/dmOff/perOnchargOnlowOnpmOnstlowpmdmOff.svg new file mode 100644 index 0000000..c290192 --- /dev/null +++ b/assets/icons/battery/dmOff/perOnchargOnlowOnpmOnstlowpmdmOff.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstChargeddmOn.svg b/assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstChargeddmOn.svg new file mode 100644 index 0000000..ba2ff6c --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstChargeddmOn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstDefaultdmOn.svg b/assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstDefaultdmOn.svg new file mode 100644 index 0000000..5f713b6 --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstDefaultdmOn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOfflowOffpmOnstChargeddmOn.svg b/assets/icons/battery/dmOn/perOffchargOfflowOffpmOnstChargeddmOn.svg new file mode 100644 index 0000000..31fb0cd --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOfflowOffpmOnstChargeddmOn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOfflowOnpmOffstDefaultdmOn.svg b/assets/icons/battery/dmOn/perOffchargOfflowOnpmOffstDefaultdmOn.svg new file mode 100644 index 0000000..e198cd4 --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOfflowOnpmOffstDefaultdmOn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOfflowOnpmOnstDefaultdmOn.svg b/assets/icons/battery/dmOn/perOffchargOfflowOnpmOnstDefaultdmOn.svg new file mode 100644 index 0000000..6175ffe --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOfflowOnpmOnstDefaultdmOn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOnlowOffpmOffstChargeddmOn.svg b/assets/icons/battery/dmOn/perOffchargOnlowOffpmOffstChargeddmOn.svg new file mode 100644 index 0000000..017e6c3 --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOnlowOffpmOffstChargeddmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOnlowOnpmOffstlowBatterydmOn.svg b/assets/icons/battery/dmOn/perOffchargOnlowOnpmOffstlowBatterydmOn.svg new file mode 100644 index 0000000..895745f --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOnlowOnpmOffstlowBatterydmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOffchargOnlowOnpmOnstlowpmdmOn.svg b/assets/icons/battery/dmOn/perOffchargOnlowOnpmOnstlowpmdmOn.svg new file mode 100644 index 0000000..9b32e45 --- /dev/null +++ b/assets/icons/battery/dmOn/perOffchargOnlowOnpmOnstlowpmdmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstChargeddmOn.svg b/assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstChargeddmOn.svg new file mode 100644 index 0000000..334aa8a --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstChargeddmOn.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstDefaultdmOn.svg b/assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstDefaultdmOn.svg new file mode 100644 index 0000000..c2e406f --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstDefaultdmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOfflowOffpmOnstChargeddmOn.svg b/assets/icons/battery/dmOn/perOnchargOfflowOffpmOnstChargeddmOn.svg new file mode 100644 index 0000000..a7c162c --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOfflowOffpmOnstChargeddmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOfflowOnpmOffstDefaultdmOn.svg b/assets/icons/battery/dmOn/perOnchargOfflowOnpmOffstDefaultdmOn.svg new file mode 100644 index 0000000..8e766d7 --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOfflowOnpmOffstDefaultdmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOfflowOnpmOnstDefaultdmOn.svg b/assets/icons/battery/dmOn/perOnchargOfflowOnpmOnstDefaultdmOn.svg new file mode 100644 index 0000000..00f679c --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOfflowOnpmOnstDefaultdmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOnlowOffpmOffstChargeddmOn.svg b/assets/icons/battery/dmOn/perOnchargOnlowOffpmOffstChargeddmOn.svg new file mode 100644 index 0000000..952d9ca --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOnlowOffpmOffstChargeddmOn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOnlowOnpmOffstlowBatterydmOn.svg b/assets/icons/battery/dmOn/perOnchargOnlowOnpmOffstlowBatterydmOn.svg new file mode 100644 index 0000000..22ce56e --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOnlowOnpmOffstlowBatterydmOn.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/battery/dmOn/perOnchargOnlowOnpmOnstlowpmdmOn.svg b/assets/icons/battery/dmOn/perOnchargOnlowOnpmOnstlowpmdmOn.svg new file mode 100644 index 0000000..5ab5765 --- /dev/null +++ b/assets/icons/battery/dmOn/perOnchargOnlowOnpmOnstlowpmdmOn.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/blue_checkbox_ic.svg b/assets/icons/blue_checkbox_ic.svg new file mode 100644 index 0000000..46d0160 --- /dev/null +++ b/assets/icons/blue_checkbox_ic.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/coldMode.svg b/assets/icons/coldMode.svg new file mode 100644 index 0000000..ab17a65 --- /dev/null +++ b/assets/icons/coldMode.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/color_wheel.svg b/assets/icons/color_wheel.svg new file mode 100644 index 0000000..793ac1b --- /dev/null +++ b/assets/icons/color_wheel.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/curtainsIcon/closeCurtain.svg b/assets/icons/curtainsIcon/closeCurtain.svg new file mode 100644 index 0000000..8fa548f --- /dev/null +++ b/assets/icons/curtainsIcon/closeCurtain.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/curtainsIcon/curtainHolder.svg b/assets/icons/curtainsIcon/curtainHolder.svg new file mode 100644 index 0000000..8ed2e91 --- /dev/null +++ b/assets/icons/curtainsIcon/curtainHolder.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/curtainsIcon/openCurtain.svg b/assets/icons/curtainsIcon/openCurtain.svg new file mode 100644 index 0000000..67fd13c --- /dev/null +++ b/assets/icons/curtainsIcon/openCurtain.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/curtainsIcon/verticalBlade.svg b/assets/icons/curtainsIcon/verticalBlade.svg new file mode 100644 index 0000000..3089113 --- /dev/null +++ b/assets/icons/curtainsIcon/verticalBlade.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/dashboard-fill.svg b/assets/icons/dashboard-fill.svg new file mode 100644 index 0000000..ed518e5 --- /dev/null +++ b/assets/icons/dashboard-fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/dashboard.svg b/assets/icons/dashboard.svg new file mode 100644 index 0000000..e112256 --- /dev/null +++ b/assets/icons/dashboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/delay.svg b/assets/icons/delay.svg new file mode 100644 index 0000000..2285837 --- /dev/null +++ b/assets/icons/delay.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/delete_room_ic.svg b/assets/icons/delete_room_ic.svg new file mode 100644 index 0000000..3ac2dbd --- /dev/null +++ b/assets/icons/delete_room_ic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/doorLock.svg b/assets/icons/doorLock.svg new file mode 100644 index 0000000..6f27673 --- /dev/null +++ b/assets/icons/doorLock.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/BatteryIndicator.svg b/assets/icons/doorlock-assets/BatteryIndicator.svg new file mode 100644 index 0000000..e82252c --- /dev/null +++ b/assets/icons/doorlock-assets/BatteryIndicator.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/doorlock-assets/door_un_look_ic.svg b/assets/icons/doorlock-assets/door_un_look_ic.svg new file mode 100644 index 0000000..b647ec8 --- /dev/null +++ b/assets/icons/doorlock-assets/door_un_look_ic.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/doorlock-button-one.svg b/assets/icons/doorlock-assets/doorlock-button-one.svg new file mode 100644 index 0000000..492fdc0 --- /dev/null +++ b/assets/icons/doorlock-assets/doorlock-button-one.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/doorlock-button-two.svg b/assets/icons/doorlock-assets/doorlock-button-two.svg new file mode 100644 index 0000000..3aa3bed --- /dev/null +++ b/assets/icons/doorlock-assets/doorlock-button-two.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/lockIcon.svg b/assets/icons/doorlock-assets/lockIcon.svg new file mode 100644 index 0000000..a78161c --- /dev/null +++ b/assets/icons/doorlock-assets/lockIcon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/members-management.svg b/assets/icons/doorlock-assets/members-management.svg new file mode 100644 index 0000000..f7a60fb --- /dev/null +++ b/assets/icons/doorlock-assets/members-management.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/smart-linkage.svg b/assets/icons/doorlock-assets/smart-linkage.svg new file mode 100644 index 0000000..b9730e3 --- /dev/null +++ b/assets/icons/doorlock-assets/smart-linkage.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/temporary-password.svg b/assets/icons/doorlock-assets/temporary-password.svg new file mode 100644 index 0000000..583035b --- /dev/null +++ b/assets/icons/doorlock-assets/temporary-password.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/doorlock-assets/unlocking-records.svg b/assets/icons/doorlock-assets/unlocking-records.svg new file mode 100644 index 0000000..125f3d9 --- /dev/null +++ b/assets/icons/doorlock-assets/unlocking-records.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/empty_checkbox_ic.svg b/assets/icons/empty_checkbox_ic.svg new file mode 100644 index 0000000..34522c8 --- /dev/null +++ b/assets/icons/empty_checkbox_ic.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/fan-0.svg b/assets/icons/fan-0.svg new file mode 100644 index 0000000..82841be --- /dev/null +++ b/assets/icons/fan-0.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/fan-1.svg b/assets/icons/fan-1.svg new file mode 100644 index 0000000..3a4dbf2 --- /dev/null +++ b/assets/icons/fan-1.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/fan-2.svg b/assets/icons/fan-2.svg new file mode 100644 index 0000000..9e54725 --- /dev/null +++ b/assets/icons/fan-2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/fan-3.svg b/assets/icons/fan-3.svg new file mode 100644 index 0000000..2650a57 --- /dev/null +++ b/assets/icons/fan-3.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/filter.png b/assets/icons/filter.png new file mode 100644 index 0000000..ccb920e Binary files /dev/null and b/assets/icons/filter.png differ diff --git a/assets/icons/frequency.svg b/assets/icons/frequency.svg new file mode 100644 index 0000000..3054ec7 --- /dev/null +++ b/assets/icons/frequency.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_cooling.svg b/assets/icons/functions_icons/ac_cooling.svg new file mode 100644 index 0000000..e95c0d4 --- /dev/null +++ b/assets/icons/functions_icons/ac_cooling.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_auto.svg b/assets/icons/functions_icons/ac_fan_auto.svg new file mode 100644 index 0000000..0acacfe --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_auto.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_high.svg b/assets/icons/functions_icons/ac_fan_high.svg new file mode 100644 index 0000000..d613153 --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_high.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_low.svg b/assets/icons/functions_icons/ac_fan_low.svg new file mode 100644 index 0000000..f4bf56b --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_low.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_fan_middle.svg b/assets/icons/functions_icons/ac_fan_middle.svg new file mode 100644 index 0000000..ee94023 --- /dev/null +++ b/assets/icons/functions_icons/ac_fan_middle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_heating.svg b/assets/icons/functions_icons/ac_heating.svg new file mode 100644 index 0000000..47a160c --- /dev/null +++ b/assets/icons/functions_icons/ac_heating.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/ac_power.svg b/assets/icons/functions_icons/ac_power.svg new file mode 100644 index 0000000..cc2127f --- /dev/null +++ b/assets/icons/functions_icons/ac_power.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/ac_power_off.svg b/assets/icons/functions_icons/ac_power_off.svg new file mode 100644 index 0000000..70f7f9a --- /dev/null +++ b/assets/icons/functions_icons/ac_power_off.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/automation_functions/card_unlock.svg b/assets/icons/functions_icons/automation_functions/card_unlock.svg new file mode 100644 index 0000000..dd77680 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/card_unlock.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/current_temp.svg b/assets/icons/functions_icons/automation_functions/current_temp.svg new file mode 100644 index 0000000..42cceb2 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/current_temp.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/doorbell.svg b/assets/icons/functions_icons/automation_functions/doorbell.svg new file mode 100644 index 0000000..1dc515a --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/doorbell.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg b/assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg new file mode 100644 index 0000000..8f4a590 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/double_lock.svg b/assets/icons/functions_icons/automation_functions/double_lock.svg new file mode 100644 index 0000000..d8ad971 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/double_lock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg b/assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg new file mode 100644 index 0000000..f9f5b84 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/hijack_alarm.svg b/assets/icons/functions_icons/automation_functions/hijack_alarm.svg new file mode 100644 index 0000000..e32997f --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/hijack_alarm.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/lock_alarm.svg b/assets/icons/functions_icons/automation_functions/lock_alarm.svg new file mode 100644 index 0000000..8bd2dee --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/lock_alarm.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/motion.svg b/assets/icons/functions_icons/automation_functions/motion.svg new file mode 100644 index 0000000..8d69463 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/motion.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/password_unlock.svg b/assets/icons/functions_icons/automation_functions/password_unlock.svg new file mode 100644 index 0000000..1920b69 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/password_unlock.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/presence.svg b/assets/icons/functions_icons/automation_functions/presence.svg new file mode 100644 index 0000000..d71a474 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/presence.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/presence_state.svg b/assets/icons/functions_icons/automation_functions/presence_state.svg new file mode 100644 index 0000000..d5de48e --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/presence_state.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/remote_unlock_req.svg b/assets/icons/functions_icons/automation_functions/remote_unlock_req.svg new file mode 100644 index 0000000..da128ff --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/remote_unlock_req.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg b/assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg new file mode 100644 index 0000000..39fc859 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/residual_electricity.svg b/assets/icons/functions_icons/automation_functions/residual_electricity.svg new file mode 100644 index 0000000..6a5b612 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/residual_electricity.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/functions_icons/automation_functions/self_test_result.svg b/assets/icons/functions_icons/automation_functions/self_test_result.svg new file mode 100644 index 0000000..8739327 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/self_test_result.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/automation_functions/temp_password_unlock.svg b/assets/icons/functions_icons/automation_functions/temp_password_unlock.svg new file mode 100644 index 0000000..98d7573 --- /dev/null +++ b/assets/icons/functions_icons/automation_functions/temp_password_unlock.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/celsius_degrees.svg b/assets/icons/functions_icons/celsius_degrees.svg new file mode 100644 index 0000000..7acbd6e --- /dev/null +++ b/assets/icons/functions_icons/celsius_degrees.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/functions_icons/child_lock.svg b/assets/icons/functions_icons/child_lock.svg new file mode 100644 index 0000000..6b0138b --- /dev/null +++ b/assets/icons/functions_icons/child_lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/factory_reset.svg b/assets/icons/functions_icons/factory_reset.svg new file mode 100644 index 0000000..7a47f24 --- /dev/null +++ b/assets/icons/functions_icons/factory_reset.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/functions_icons/fan_speed.svg b/assets/icons/functions_icons/fan_speed.svg new file mode 100644 index 0000000..07a4883 --- /dev/null +++ b/assets/icons/functions_icons/fan_speed.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/far_detection.svg b/assets/icons/functions_icons/far_detection.svg new file mode 100644 index 0000000..2827d94 --- /dev/null +++ b/assets/icons/functions_icons/far_detection.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/far_detection_function.svg b/assets/icons/functions_icons/far_detection_function.svg new file mode 100644 index 0000000..894b84e --- /dev/null +++ b/assets/icons/functions_icons/far_detection_function.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/freezing.svg b/assets/icons/functions_icons/freezing.svg new file mode 100644 index 0000000..6c02f2e --- /dev/null +++ b/assets/icons/functions_icons/freezing.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/functions_icons/indicator.svg b/assets/icons/functions_icons/indicator.svg new file mode 100644 index 0000000..b58a976 --- /dev/null +++ b/assets/icons/functions_icons/indicator.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/light_countdown.svg b/assets/icons/functions_icons/light_countdown.svg new file mode 100644 index 0000000..94f65b9 --- /dev/null +++ b/assets/icons/functions_icons/light_countdown.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/master_state.svg b/assets/icons/functions_icons/master_state.svg new file mode 100644 index 0000000..0aafae1 --- /dev/null +++ b/assets/icons/functions_icons/master_state.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/motion_detection.svg b/assets/icons/functions_icons/motion_detection.svg new file mode 100644 index 0000000..a9b2d68 --- /dev/null +++ b/assets/icons/functions_icons/motion_detection.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/motionless_detection.svg b/assets/icons/functions_icons/motionless_detection.svg new file mode 100644 index 0000000..25a767c --- /dev/null +++ b/assets/icons/functions_icons/motionless_detection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/functions_icons/nobody_time.svg b/assets/icons/functions_icons/nobody_time.svg new file mode 100644 index 0000000..df80b51 --- /dev/null +++ b/assets/icons/functions_icons/nobody_time.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/functions_icons/reset_off.svg b/assets/icons/functions_icons/reset_off.svg new file mode 100644 index 0000000..eac88f2 --- /dev/null +++ b/assets/icons/functions_icons/reset_off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/functions_icons/scene_child_lock.svg b/assets/icons/functions_icons/scene_child_lock.svg new file mode 100644 index 0000000..7e56164 --- /dev/null +++ b/assets/icons/functions_icons/scene_child_lock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/scene_child_unlock.svg b/assets/icons/functions_icons/scene_child_unlock.svg new file mode 100644 index 0000000..4eafbde --- /dev/null +++ b/assets/icons/functions_icons/scene_child_unlock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/scene_refresh.svg b/assets/icons/functions_icons/scene_refresh.svg new file mode 100644 index 0000000..c54ffb0 --- /dev/null +++ b/assets/icons/functions_icons/scene_refresh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/functions_icons/sensitivity.svg b/assets/icons/functions_icons/sensitivity.svg new file mode 100644 index 0000000..b75ebd3 --- /dev/null +++ b/assets/icons/functions_icons/sensitivity.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/functions_icons/sesitivity_operation_icon.svg b/assets/icons/functions_icons/sesitivity_operation_icon.svg new file mode 100644 index 0000000..612148c --- /dev/null +++ b/assets/icons/functions_icons/sesitivity_operation_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/functions_icons/switch_alarm_sound.svg b/assets/icons/functions_icons/switch_alarm_sound.svg new file mode 100644 index 0000000..db64533 --- /dev/null +++ b/assets/icons/functions_icons/switch_alarm_sound.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/functions_icons/tempreture.svg b/assets/icons/functions_icons/tempreture.svg new file mode 100644 index 0000000..448083a --- /dev/null +++ b/assets/icons/functions_icons/tempreture.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/gang.svg b/assets/icons/gang.svg new file mode 100644 index 0000000..85e6e9b --- /dev/null +++ b/assets/icons/gang.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/gateway_icon.svg b/assets/icons/gateway_icon.svg new file mode 100644 index 0000000..d64135c --- /dev/null +++ b/assets/icons/gateway_icon.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/hand_click.svg b/assets/icons/hand_click.svg new file mode 100644 index 0000000..f0c81f2 --- /dev/null +++ b/assets/icons/hand_click.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/home.svg b/assets/icons/home.svg new file mode 100644 index 0000000..9ab0dca --- /dev/null +++ b/assets/icons/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/hot1.jpg b/assets/icons/hot1.jpg new file mode 100644 index 0000000..5281185 Binary files /dev/null and b/assets/icons/hot1.jpg differ diff --git a/assets/icons/kalvin.svg b/assets/icons/kalvin.svg new file mode 100644 index 0000000..67332f4 --- /dev/null +++ b/assets/icons/kalvin.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/lightSwitchOff.svg b/assets/icons/lightSwitchOff.svg new file mode 100644 index 0000000..b982c13 --- /dev/null +++ b/assets/icons/lightSwitchOff.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/lightSwitchOn.svg b/assets/icons/lightSwitchOn.svg new file mode 100644 index 0000000..e0245bc --- /dev/null +++ b/assets/icons/lightSwitchOn.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/linkageIcons/doorLockAlarm.svg b/assets/icons/linkageIcons/doorLockAlarm.svg new file mode 100644 index 0000000..3452356 --- /dev/null +++ b/assets/icons/linkageIcons/doorLockAlarm.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/linkageIcons/familyHome.svg b/assets/icons/linkageIcons/familyHome.svg new file mode 100644 index 0000000..d4b0a95 --- /dev/null +++ b/assets/icons/linkageIcons/familyHome.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/lock.svg b/assets/icons/lock.svg new file mode 100644 index 0000000..23e3db3 --- /dev/null +++ b/assets/icons/lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/logo.png b/assets/icons/logo.png new file mode 100644 index 0000000..417841e Binary files /dev/null and b/assets/icons/logo.png differ diff --git a/assets/icons/minus.svg b/assets/icons/minus.svg new file mode 100644 index 0000000..c6d62f4 --- /dev/null +++ b/assets/icons/minus.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/noValidPasswords.svg b/assets/icons/noValidPasswords.svg new file mode 100644 index 0000000..1a660df --- /dev/null +++ b/assets/icons/noValidPasswords.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/no_units_ic.svg b/assets/icons/no_units_ic.svg new file mode 100644 index 0000000..f336e9d --- /dev/null +++ b/assets/icons/no_units_ic.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/oneTimePassword.svg b/assets/icons/oneTimePassword.svg new file mode 100644 index 0000000..06bd9b5 --- /dev/null +++ b/assets/icons/oneTimePassword.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/pause_ic.svg b/assets/icons/pause_ic.svg new file mode 100644 index 0000000..c6f6d5c --- /dev/null +++ b/assets/icons/pause_ic.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/play_ic.svg b/assets/icons/play_ic.svg new file mode 100644 index 0000000..e9242b0 --- /dev/null +++ b/assets/icons/play_ic.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/player.svg b/assets/icons/player.svg new file mode 100644 index 0000000..859abdd --- /dev/null +++ b/assets/icons/player.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/plus.svg b/assets/icons/plus.svg new file mode 100644 index 0000000..0a6cfb1 --- /dev/null +++ b/assets/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/presence-sensor-assets/Distance.svg b/assets/icons/presence-sensor-assets/Distance.svg new file mode 100644 index 0000000..5057814 --- /dev/null +++ b/assets/icons/presence-sensor-assets/Distance.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/Empty.svg b/assets/icons/presence-sensor-assets/Empty.svg new file mode 100644 index 0000000..cf1e5df --- /dev/null +++ b/assets/icons/presence-sensor-assets/Empty.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/presence-sensor-assets/Illuminance-Record.svg b/assets/icons/presence-sensor-assets/Illuminance-Record.svg new file mode 100644 index 0000000..5f64243 --- /dev/null +++ b/assets/icons/presence-sensor-assets/Illuminance-Record.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/Illuminance-Value.svg b/assets/icons/presence-sensor-assets/Illuminance-Value.svg new file mode 100644 index 0000000..c087b7c --- /dev/null +++ b/assets/icons/presence-sensor-assets/Illuminance-Value.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/Indicator.svg b/assets/icons/presence-sensor-assets/Indicator.svg new file mode 100644 index 0000000..600d6da --- /dev/null +++ b/assets/icons/presence-sensor-assets/Indicator.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/Presence.svg b/assets/icons/presence-sensor-assets/Presence.svg new file mode 100644 index 0000000..638cb39 --- /dev/null +++ b/assets/icons/presence-sensor-assets/Presence.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/Record.svg b/assets/icons/presence-sensor-assets/Record.svg new file mode 100644 index 0000000..ff39c7f --- /dev/null +++ b/assets/icons/presence-sensor-assets/Record.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/Sensitivity.svg b/assets/icons/presence-sensor-assets/Sensitivity.svg new file mode 100644 index 0000000..bac78f7 --- /dev/null +++ b/assets/icons/presence-sensor-assets/Sensitivity.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/Time.svg b/assets/icons/presence-sensor-assets/Time.svg new file mode 100644 index 0000000..f5b43ad --- /dev/null +++ b/assets/icons/presence-sensor-assets/Time.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/help-description.svg b/assets/icons/presence-sensor-assets/help-description.svg new file mode 100644 index 0000000..1dd6f19 --- /dev/null +++ b/assets/icons/presence-sensor-assets/help-description.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/induction-recording.svg b/assets/icons/presence-sensor-assets/induction-recording.svg new file mode 100644 index 0000000..97e2c46 --- /dev/null +++ b/assets/icons/presence-sensor-assets/induction-recording.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/maximum_distance.svg b/assets/icons/presence-sensor-assets/maximum_distance.svg new file mode 100644 index 0000000..9b0faa1 --- /dev/null +++ b/assets/icons/presence-sensor-assets/maximum_distance.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/parameter-settings.svg b/assets/icons/presence-sensor-assets/parameter-settings.svg new file mode 100644 index 0000000..a996635 --- /dev/null +++ b/assets/icons/presence-sensor-assets/parameter-settings.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/presence-sensor-motion.svg b/assets/icons/presence-sensor-assets/presence-sensor-motion.svg new file mode 100644 index 0000000..6d33deb --- /dev/null +++ b/assets/icons/presence-sensor-assets/presence-sensor-motion.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence-sensor-assets/space_type_icon.svg b/assets/icons/presence-sensor-assets/space_type_icon.svg new file mode 100644 index 0000000..198948f --- /dev/null +++ b/assets/icons/presence-sensor-assets/space_type_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/refresh.svg b/assets/icons/refresh.svg new file mode 100644 index 0000000..9f12d98 --- /dev/null +++ b/assets/icons/refresh.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/sensors.svg b/assets/icons/sensors.svg new file mode 100644 index 0000000..7fbb150 --- /dev/null +++ b/assets/icons/sensors.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/settings.png b/assets/icons/settings.png new file mode 100644 index 0000000..4a8bcbf Binary files /dev/null and b/assets/icons/settings.png differ diff --git a/assets/icons/settings.svg b/assets/icons/settings.svg new file mode 100644 index 0000000..5df067d --- /dev/null +++ b/assets/icons/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/summer_mode.svg b/assets/icons/summer_mode.svg new file mode 100644 index 0000000..d5de0a4 --- /dev/null +++ b/assets/icons/summer_mode.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/sunnyMode.svg b/assets/icons/sunnyMode.svg new file mode 100644 index 0000000..bb350d3 --- /dev/null +++ b/assets/icons/sunnyMode.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/sustainability.svg b/assets/icons/sustainability.svg new file mode 100644 index 0000000..ce1ce44 --- /dev/null +++ b/assets/icons/sustainability.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/timeLimitedPassword.svg b/assets/icons/timeLimitedPassword.svg new file mode 100644 index 0000000..be4fdbc --- /dev/null +++ b/assets/icons/timeLimitedPassword.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/timeLimitedPasswordIcon.svg b/assets/icons/timeLimitedPasswordIcon.svg new file mode 100644 index 0000000..60cc3d6 --- /dev/null +++ b/assets/icons/timeLimitedPasswordIcon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/unlock_ic.svg b/assets/icons/unlock_ic.svg new file mode 100644 index 0000000..8ce3cc4 --- /dev/null +++ b/assets/icons/unlock_ic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/unlockingMethodsIcons/face.svg b/assets/icons/unlockingMethodsIcons/face.svg new file mode 100644 index 0000000..2a317e4 --- /dev/null +++ b/assets/icons/unlockingMethodsIcons/face.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/unlockingMethodsIcons/fingerprint.svg b/assets/icons/unlockingMethodsIcons/fingerprint.svg new file mode 100644 index 0000000..f9f55c5 --- /dev/null +++ b/assets/icons/unlockingMethodsIcons/fingerprint.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/unlockingMethodsIcons/remote.svg b/assets/icons/unlockingMethodsIcons/remote.svg new file mode 100644 index 0000000..da59643 --- /dev/null +++ b/assets/icons/unlockingMethodsIcons/remote.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/volt-meter.svg b/assets/icons/volt-meter.svg new file mode 100644 index 0000000..6691a7d --- /dev/null +++ b/assets/icons/volt-meter.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/windyMode.svg b/assets/icons/windyMode.svg new file mode 100644 index 0000000..a7e6284 --- /dev/null +++ b/assets/icons/windyMode.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/winter1.jpg b/assets/icons/winter1.jpg new file mode 100644 index 0000000..12e8b56 Binary files /dev/null and b/assets/icons/winter1.jpg differ diff --git a/assets/images/Background.png b/assets/images/Background.png new file mode 100644 index 0000000..02529ba Binary files /dev/null and b/assets/images/Background.png differ diff --git a/assets/images/Down.png b/assets/images/Down.png new file mode 100644 index 0000000..efd6460 Binary files /dev/null and b/assets/images/Down.png differ diff --git a/assets/images/HorizintalBlade.png b/assets/images/HorizintalBlade.png new file mode 100644 index 0000000..f4e939b Binary files /dev/null and b/assets/images/HorizintalBlade.png differ diff --git a/assets/images/Logo.svg b/assets/images/Logo.svg new file mode 100644 index 0000000..0a592c0 --- /dev/null +++ b/assets/images/Logo.svg @@ -0,0 +1,38 @@ + + + diff --git a/assets/images/Pause.png b/assets/images/Pause.png new file mode 100644 index 0000000..b5659fc Binary files /dev/null and b/assets/images/Pause.png differ diff --git a/assets/images/Up.png b/assets/images/Up.png new file mode 100644 index 0000000..f4b13a8 Binary files /dev/null and b/assets/images/Up.png differ diff --git a/assets/images/Vector.png b/assets/images/Vector.png new file mode 100644 index 0000000..c105e7e Binary files /dev/null and b/assets/images/Vector.png differ diff --git a/assets/images/Window.png b/assets/images/Window.png new file mode 100644 index 0000000..be38a0b Binary files /dev/null and b/assets/images/Window.png differ diff --git a/assets/images/automation.jpg b/assets/images/automation.jpg new file mode 100644 index 0000000..23dc891 Binary files /dev/null and b/assets/images/automation.jpg differ diff --git a/assets/images/black-logo.png b/assets/images/black-logo.png new file mode 100644 index 0000000..0d42805 Binary files /dev/null and b/assets/images/black-logo.png differ diff --git a/assets/images/blind.png b/assets/images/blind.png new file mode 100644 index 0000000..35cad04 Binary files /dev/null and b/assets/images/blind.png differ diff --git a/assets/images/box-empty.jpg b/assets/images/box-empty.jpg new file mode 100644 index 0000000..2064a31 Binary files /dev/null and b/assets/images/box-empty.jpg differ diff --git a/assets/images/curtain.png b/assets/images/curtain.png new file mode 100644 index 0000000..c8b55c9 Binary files /dev/null and b/assets/images/curtain.png differ diff --git a/assets/images/logo_horizontal.png b/assets/images/logo_horizontal.png new file mode 100644 index 0000000..2d29681 Binary files /dev/null and b/assets/images/logo_horizontal.png differ diff --git a/assets/images/test_dash.png b/assets/images/test_dash.png new file mode 100644 index 0000000..efa07cf Binary files /dev/null and b/assets/images/test_dash.png differ diff --git a/assets/images/test_dash2.png b/assets/images/test_dash2.png new file mode 100644 index 0000000..b4cfa2a Binary files /dev/null and b/assets/images/test_dash2.png differ diff --git a/assets/images/white-logo.png b/assets/images/white-logo.png new file mode 100644 index 0000000..2e73e35 Binary files /dev/null and b/assets/images/white-logo.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..5c27c3e --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,2 @@ +extensions: + - provider: true \ No newline at end of file diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..cb3f9b2 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:android:bb6047adeeb80fb00c7e6d","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:373a65cce55a3af40c7e6d","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"test2-8a3d2","configurations":{"android":"1:427332280600:android:bb6047adeeb80fb00c7e6d","ios":"1:427332280600:ios:373a65cce55a3af40c7e6d","web":"1:427332280600:web:ad50516a87a35a1a0c7e6d"}}}}}} \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -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 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..5d30226 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,52 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + ## dart: PermissionGroup.photos + 'PERMISSION_PHOTOS=1', + ] + end + end +end + diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..8ebd9e9 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,338 @@ +PODS: + - device_info_plus (0.0.1): + - Flutter + - Firebase/Analytics (10.25.0): + - Firebase/Core + - Firebase/Core (10.25.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 10.25.0) + - Firebase/CoreOnly (10.25.0): + - FirebaseCore (= 10.25.0) + - Firebase/Crashlytics (10.25.0): + - Firebase/CoreOnly + - FirebaseCrashlytics (~> 10.25.0) + - Firebase/Database (10.25.0): + - Firebase/CoreOnly + - FirebaseDatabase (~> 10.25.0) + - firebase_analytics (10.8.7): + - Firebase/Analytics (= 10.25.0) + - firebase_core + - Flutter + - firebase_core (2.32.0): + - Firebase/CoreOnly (= 10.25.0) + - Flutter + - firebase_crashlytics (3.4.16): + - Firebase/Crashlytics (= 10.25.0) + - firebase_core + - Flutter + - firebase_database (10.5.7): + - Firebase/Database (= 10.25.0) + - firebase_core + - Flutter + - FirebaseAnalytics (10.25.0): + - FirebaseAnalytics/AdIdSupport (= 10.25.0) + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30911.0, >= 2.30908.0) + - FirebaseAnalytics/AdIdSupport (10.25.0): + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleAppMeasurement (= 10.25.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30911.0, >= 2.30908.0) + - FirebaseAppCheckInterop (10.29.0) + - FirebaseCore (10.25.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.12) + - GoogleUtilities/Logger (~> 7.12) + - FirebaseCoreExtension (10.29.0): + - FirebaseCore (~> 10.0) + - FirebaseCoreInternal (10.29.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseCrashlytics (10.25.0): + - FirebaseCore (~> 10.5) + - FirebaseInstallations (~> 10.0) + - FirebaseRemoteConfigInterop (~> 10.23) + - FirebaseSessions (~> 10.5) + - GoogleDataTransport (~> 9.2) + - GoogleUtilities/Environment (~> 7.8) + - nanopb (< 2.30911.0, >= 2.30908.0) + - PromisesObjC (~> 2.1) + - FirebaseDatabase (10.25.0): + - FirebaseAppCheckInterop (~> 10.17) + - FirebaseCore (~> 10.0) + - FirebaseSharedSwift (~> 10.0) + - GoogleUtilities/UserDefaults (~> 7.13) + - leveldb-library (~> 1.22) + - FirebaseInstallations (10.29.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) + - PromisesObjC (~> 2.1) + - FirebaseRemoteConfigInterop (10.29.0) + - FirebaseSessions (10.29.0): + - FirebaseCore (~> 10.5) + - FirebaseCoreExtension (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleDataTransport (~> 9.2) + - GoogleUtilities/Environment (~> 7.13) + - GoogleUtilities/UserDefaults (~> 7.13) + - nanopb (< 2.30911.0, >= 2.30908.0) + - PromisesSwift (~> 2.1) + - FirebaseSharedSwift (10.29.0) + - Flutter (1.0.0) + - flutter_localization (0.0.1): + - Flutter + - flutter_secure_storage (6.0.0): + - Flutter + - GoogleAppMeasurement (10.25.0): + - GoogleAppMeasurement/AdIdSupport (= 10.25.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30911.0, >= 2.30908.0) + - GoogleAppMeasurement/AdIdSupport (10.25.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.25.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30911.0, >= 2.30908.0) + - GoogleAppMeasurement/WithoutAdIdSupport (10.25.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30911.0, >= 2.30908.0) + - GoogleDataTransport (9.4.1): + - GoogleUtilities/Environment (~> 7.7) + - nanopb (< 2.30911.0, >= 2.30908.0) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.13.3): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (7.13.3): + - GoogleUtilities/Privacy + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.13.3): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (7.13.3): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/Network (7.13.3): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.13.3)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (7.13.3) + - GoogleUtilities/Reachability (7.13.3): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (7.13.3): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - image_picker_ios (0.0.1): + - Flutter + - leveldb-library (1.22.5) + - nanopb (2.30910.0): + - nanopb/decode (= 2.30910.0) + - nanopb/encode (= 2.30910.0) + - nanopb/decode (2.30910.0) + - nanopb/encode (2.30910.0) + - onesignal_flutter (5.2.0): + - Flutter + - OneSignalXCFramework (= 5.2.0) + - OneSignalXCFramework (5.2.0): + - OneSignalXCFramework/OneSignalComplete (= 5.2.0) + - OneSignalXCFramework/OneSignal (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalExtension + - OneSignalXCFramework/OneSignalLiveActivities + - OneSignalXCFramework/OneSignalNotifications + - OneSignalXCFramework/OneSignalOSCore + - OneSignalXCFramework/OneSignalOutcomes + - OneSignalXCFramework/OneSignalUser + - OneSignalXCFramework/OneSignalComplete (5.2.0): + - OneSignalXCFramework/OneSignal + - OneSignalXCFramework/OneSignalInAppMessages + - OneSignalXCFramework/OneSignalLocation + - OneSignalXCFramework/OneSignalCore (5.2.0) + - OneSignalXCFramework/OneSignalExtension (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalOutcomes + - OneSignalXCFramework/OneSignalInAppMessages (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalNotifications + - OneSignalXCFramework/OneSignalOSCore + - OneSignalXCFramework/OneSignalOutcomes + - OneSignalXCFramework/OneSignalUser + - OneSignalXCFramework/OneSignalLiveActivities (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalOSCore + - OneSignalXCFramework/OneSignalUser + - OneSignalXCFramework/OneSignalLocation (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalNotifications + - OneSignalXCFramework/OneSignalOSCore + - OneSignalXCFramework/OneSignalUser + - OneSignalXCFramework/OneSignalNotifications (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalExtension + - OneSignalXCFramework/OneSignalOutcomes + - OneSignalXCFramework/OneSignalOSCore (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalOutcomes (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalUser (5.2.0): + - OneSignalXCFramework/OneSignalCore + - OneSignalXCFramework/OneSignalNotifications + - OneSignalXCFramework/OneSignalOSCore + - OneSignalXCFramework/OneSignalOutcomes + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - permission_handler_apple (9.3.0): + - Flutter + - PromisesObjC (2.4.0) + - PromisesSwift (2.4.0): + - PromisesObjC (= 2.4.0) + - share_plus (0.0.1): + - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) + - firebase_database (from `.symlinks/plugins/firebase_database/ios`) + - Flutter (from `Flutter`) + - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - onesignal_flutter (from `.symlinks/plugins/onesignal_flutter/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +SPEC REPOS: + trunk: + - Firebase + - FirebaseAnalytics + - FirebaseAppCheckInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - FirebaseCrashlytics + - FirebaseDatabase + - FirebaseInstallations + - FirebaseRemoteConfigInterop + - FirebaseSessions + - FirebaseSharedSwift + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleUtilities + - leveldb-library + - nanopb + - OneSignalXCFramework + - PromisesObjC + - PromisesSwift + +EXTERNAL SOURCES: + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_crashlytics: + :path: ".symlinks/plugins/firebase_crashlytics/ios" + firebase_database: + :path: ".symlinks/plugins/firebase_database/ios" + Flutter: + :path: Flutter + flutter_localization: + :path: ".symlinks/plugins/flutter_localization/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + onesignal_flutter: + :path: ".symlinks/plugins/onesignal_flutter/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite: + :path: ".symlinks/plugins/sqflite/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d + Firebase: 0312a2352584f782ea56f66d91606891d4607f06 + firebase_analytics: 3a9263fedec72970e6bd30a7132bbdd386de2c14 + firebase_core: a626d00494efa398e7c54f25f1454a64c8abf197 + firebase_crashlytics: 0b7cb41f5fb3b6889d0fb408cfce3cc7a4247061 + firebase_database: 2713033e426b176d4fe5e7195f3d19aa1b549a91 + FirebaseAnalytics: ec00fe8b93b41dc6fe4a28784b8e51da0647a248 + FirebaseAppCheckInterop: 6a1757cfd4067d8e00fccd14fcc1b8fd78cfac07 + FirebaseCore: 7ec4d0484817f12c3373955bc87762d96842d483 + FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f + FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934 + FirebaseCrashlytics: 4b96efb0ce73b38b2a85e8b8bd1bd8f63f09d015 + FirebaseDatabase: faa489a42f5f868d23a55dd442d6e2099348458e + FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd + FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d + FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc + FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be + GoogleAppMeasurement: 9abf64b682732fed36da827aa2a68f0221fd2356 + GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a + GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + leveldb-library: e8eadf9008a61f9e1dde3978c086d2b6d9b9dc28 + nanopb: 438bc412db1928dac798aa6fd75726007be04262 + onesignal_flutter: 5ce68a29861960168e81101cb1bd685d264361de + OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 + share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + +PODFILE CHECKSUM: 4243bd7f9184f79552dd731a7c9d5cad03bd2706 + +COCOAPODS: 1.15.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..38b52d7 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,795 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 611C662010675536F855E5CA /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 490AAF90B8FBFCC5BA996845 /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 8BB48ED4ACF8DB5A4F5DB78C /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 90790D9750E58AEFF7247821 /* GoogleService-Info.plist */; }; + 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 */; }; + D31283674D2826D7EF8E56BC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25B37F5982CD6994FABA2CC1 /* Pods_Runner.framework */; }; +/* 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 = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 25B37F5982CD6994FABA2CC1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 444D77D28A8CDF32047CD0AF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 490AAF90B8FBFCC5BA996845 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 90790D9750E58AEFF7247821 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; + 949637473C534E1F68B19CC0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AAC9129FD50E64509AD1B9AF /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + BFD4DDED98208034B60B5311 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + DFB6BB492A265F2BF6FDC8C0 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F323D632CA976B68DDB0E669 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D31283674D2826D7EF8E56BC /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C2B33A7265AF659D80692473 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 611C662010675536F855E5CA /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2F70EB4341A83C900EB253DC /* Pods */ = { + isa = PBXGroup; + children = ( + BFD4DDED98208034B60B5311 /* Pods-Runner.debug.xcconfig */, + 949637473C534E1F68B19CC0 /* Pods-Runner.release.xcconfig */, + 444D77D28A8CDF32047CD0AF /* Pods-Runner.profile.xcconfig */, + DFB6BB492A265F2BF6FDC8C0 /* Pods-RunnerTests.debug.xcconfig */, + F323D632CA976B68DDB0E669 /* Pods-RunnerTests.release.xcconfig */, + AAC9129FD50E64509AD1B9AF /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 876D3217A8BBDAF41961161F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 25B37F5982CD6994FABA2CC1 /* Pods_Runner.framework */, + 490AAF90B8FBFCC5BA996845 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 2F70EB4341A83C900EB253DC /* Pods */, + 876D3217A8BBDAF41961161F /* Frameworks */, + 90790D9750E58AEFF7247821 /* GoogleService-Info.plist */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 3B971DE531245D7FD2921C30 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + C2B33A7265AF659D80692473 /* Frameworks */, + ); + 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 = ( + 3DC878D0674AA34AEC9695FB /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 315A05630CF83C532DBBCBF2 /* [CP] Embed Pods Frameworks */, + 0D61909C49A20C9AA28568EA /* FlutterFire: "flutterfire upload-crashlytics-symbols" */, + 3724F7A126D8469D5B04D144 /* [CP] Copy Pods Resources */, + ); + 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 = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + 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 15.0"; + 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 */, + 8BB48ED4ACF8DB5A4F5DB78C /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0D61909C49A20C9AA28568EA /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\""; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\n#!/bin/bash\nPATH=${PATH}:$FLUTTER_ROOT/bin:$HOME/.pub-cache/bin\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=$PODS_ROOT/FirebaseCrashlytics/upload-symbols --platform=ios --apple-project-path=${SRCROOT} --env-platform-name=${PLATFORM_NAME} --env-configuration=${CONFIGURATION} --env-project-dir=${PROJECT_DIR} --env-built-products-dir=${BUILT_PRODUCTS_DIR} --env-dwarf-dsym-folder-path=${DWARF_DSYM_FOLDER_PATH} --env-dwarf-dsym-file-name=${DWARF_DSYM_FILE_NAME} --env-infoplist-path=${INFOPLIST_PATH} --default-config=default\n"; + }; + 315A05630CF83C532DBBCBF2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3724F7A126D8469D5B04D144 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + 3B971DE531245D7FD2921C30 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3DC878D0674AA34AEC9695FB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 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 = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 20; + DEVELOPMENT_TEAM = 48V27SBR8J; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Syncrow; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.2; + PRODUCT_BUNDLE_IDENTIFIER = com.example.syncrow.app; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DFB6BB492A265F2BF6FDC8C0 /* 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.syncrowApp.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 = F323D632CA976B68DDB0E669 /* 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.syncrowApp.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 = AAC9129FD50E64509AD1B9AF /* 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.syncrowApp.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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 20; + DEVELOPMENT_TEAM = 48V27SBR8J; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Syncrow; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.2; + PRODUCT_BUNDLE_IDENTIFIER = com.example.syncrow.app; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + 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; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 20; + DEVELOPMENT_TEAM = 48V27SBR8J; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Syncrow; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.2; + PRODUCT_BUNDLE_IDENTIFIER = com.example.syncrow.app; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + 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 */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/AppIcon.appiconset/1024.png b/ios/Runner/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..5cc0985 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/1024.png differ diff --git a/ios/Runner/AppIcon.appiconset/114.png b/ios/Runner/AppIcon.appiconset/114.png new file mode 100644 index 0000000..4002c14 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/114.png differ diff --git a/ios/Runner/AppIcon.appiconset/120.png b/ios/Runner/AppIcon.appiconset/120.png new file mode 100644 index 0000000..ddee75b Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/120.png differ diff --git a/ios/Runner/AppIcon.appiconset/180.png b/ios/Runner/AppIcon.appiconset/180.png new file mode 100644 index 0000000..357cd0b Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/180.png differ diff --git a/ios/Runner/AppIcon.appiconset/29.png b/ios/Runner/AppIcon.appiconset/29.png new file mode 100644 index 0000000..2a93116 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/29.png differ diff --git a/ios/Runner/AppIcon.appiconset/40.png b/ios/Runner/AppIcon.appiconset/40.png new file mode 100644 index 0000000..9ecf032 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/40.png differ diff --git a/ios/Runner/AppIcon.appiconset/57.png b/ios/Runner/AppIcon.appiconset/57.png new file mode 100644 index 0000000..a0767a3 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/57.png differ diff --git a/ios/Runner/AppIcon.appiconset/58.png b/ios/Runner/AppIcon.appiconset/58.png new file mode 100644 index 0000000..af3366d Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/58.png differ diff --git a/ios/Runner/AppIcon.appiconset/60.png b/ios/Runner/AppIcon.appiconset/60.png new file mode 100644 index 0000000..de657d6 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/60.png differ diff --git a/ios/Runner/AppIcon.appiconset/80.png b/ios/Runner/AppIcon.appiconset/80.png new file mode 100644 index 0000000..4739c0f Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/80.png differ diff --git a/ios/Runner/AppIcon.appiconset/87.png b/ios/Runner/AppIcon.appiconset/87.png new file mode 100644 index 0000000..3757b16 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/87.png differ diff --git a/ios/Runner/AppIcon.appiconset/Contents.json b/ios/Runner/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..8a28773 --- /dev/null +++ b/ios/Runner/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "logo-20@2x.png", + "scale": "2x" + }, + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "logo-20@3x.png", + "scale": "3x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "logo-20.png", + "scale": "1x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "logo-20@2x.png", + "scale": "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "logo-29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "logo-29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "logo-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "logo-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "logo-60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "logo-60@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "logo-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "logo-29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "logo-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "logo-40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "logo-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "logo-76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "logo-83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "logo-1024.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/AppIcon.appiconset/logo-1024.png b/ios/Runner/AppIcon.appiconset/logo-1024.png new file mode 100644 index 0000000..20102d9 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-1024.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-20.png b/ios/Runner/AppIcon.appiconset/logo-20.png new file mode 100644 index 0000000..03f60c3 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-20.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-20@2x.png b/ios/Runner/AppIcon.appiconset/logo-20@2x.png new file mode 100644 index 0000000..fe27f08 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-20@2x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-20@3x.png b/ios/Runner/AppIcon.appiconset/logo-20@3x.png new file mode 100644 index 0000000..1d09d46 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-20@3x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-29.png b/ios/Runner/AppIcon.appiconset/logo-29.png new file mode 100644 index 0000000..490fd47 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-29.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-29@2x.png b/ios/Runner/AppIcon.appiconset/logo-29@2x.png new file mode 100644 index 0000000..90ce766 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-29@2x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-29@3x.png b/ios/Runner/AppIcon.appiconset/logo-29@3x.png new file mode 100644 index 0000000..b33e6bc Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-29@3x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-40.png b/ios/Runner/AppIcon.appiconset/logo-40.png new file mode 100644 index 0000000..fe27f08 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-40.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-40@2x.png b/ios/Runner/AppIcon.appiconset/logo-40@2x.png new file mode 100644 index 0000000..240758a Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-40@2x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-40@3x.png b/ios/Runner/AppIcon.appiconset/logo-40@3x.png new file mode 100644 index 0000000..dc30e8e Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-40@3x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-60@2x.png b/ios/Runner/AppIcon.appiconset/logo-60@2x.png new file mode 100644 index 0000000..dc30e8e Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-60@2x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-60@3x.png b/ios/Runner/AppIcon.appiconset/logo-60@3x.png new file mode 100644 index 0000000..dbaf1a7 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-60@3x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-76.png b/ios/Runner/AppIcon.appiconset/logo-76.png new file mode 100644 index 0000000..93043c1 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-76.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-76@2x.png b/ios/Runner/AppIcon.appiconset/logo-76@2x.png new file mode 100644 index 0000000..92107f2 Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-76@2x.png differ diff --git a/ios/Runner/AppIcon.appiconset/logo-83.5@2x.png b/ios/Runner/AppIcon.appiconset/logo-83.5@2x.png new file mode 100644 index 0000000..039243c Binary files /dev/null and b/ios/Runner/AppIcon.appiconset/logo-83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a59299f --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "filename" : "Syncrow Icon-20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Syncrow Icon-20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Syncrow Icon-29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Syncrow Icon-29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Syncrow Icon-29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Syncrow Icon-40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Syncrow Icon-40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Syncrow Icon-60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Syncrow Icon-60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Syncrow Icon-20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Syncrow Icon-20@2x 1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Syncrow Icon-29 1.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Syncrow Icon-29@2x 1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Syncrow Icon-40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Syncrow Icon-40@2x 1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Syncrow Icon-76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Syncrow Icon-76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Syncrow Icon-83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "Syncrow Icon-1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-1024.png new file mode 100644 index 0000000..076f903 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20.png new file mode 100644 index 0000000..73e4ade Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@2x 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@2x 1.png new file mode 100644 index 0000000..ba7adc7 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@2x 1.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@2x.png new file mode 100644 index 0000000..ba7adc7 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@3x.png new file mode 100644 index 0000000..be3275f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29 1.png new file mode 100644 index 0000000..0a529fc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29 1.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29.png new file mode 100644 index 0000000..0a529fc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@2x 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@2x 1.png new file mode 100644 index 0000000..066af50 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@2x 1.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@2x.png new file mode 100644 index 0000000..066af50 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@3x.png new file mode 100644 index 0000000..d2ec659 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40.png new file mode 100644 index 0000000..ba7adc7 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@2x 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@2x 1.png new file mode 100644 index 0000000..7c1cdcb Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@2x 1.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@2x.png new file mode 100644 index 0000000..7c1cdcb Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@3x.png new file mode 100644 index 0000000..bd0ae4c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-60@2x.png new file mode 100644 index 0000000..bd0ae4c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-60@3x.png new file mode 100644 index 0000000..682b8d8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-76.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-76.png new file mode 100644 index 0000000..047ff85 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-76.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-76@2x.png new file mode 100644 index 0000000..dd495ce Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-83.5@2x.png new file mode 100644 index 0000000..84eaebd Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Syncrow Icon-83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -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" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -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. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f80b97a --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..196076a --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw + GCM_SENDER_ID + 427332280600 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.syncrow.app + PROJECT_ID + test2-8a3d2 + STORAGE_BUCKET + test2-8a3d2.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:427332280600:ios:c904c0a7a19a4ed90c7e6d + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..36d1814 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + NSPhotoLibraryUsageDescription + We need access to your photo library to allow you to select and upload photos. + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Syncrow App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + syncrow_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -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. + } + +} diff --git a/lib/features/app_layout/bloc/home_cubit.dart b/lib/features/app_layout/bloc/home_cubit.dart new file mode 100644 index 0000000..3e85cdc --- /dev/null +++ b/lib/features/app_layout/bloc/home_cubit.dart @@ -0,0 +1,460 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:onesignal_flutter/onesignal_flutter.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/app_layout/view/widgets/app_bar_home_dropdown.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; +import 'package:syncrow_app/features/dashboard/view/dashboard_view.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/room_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/devices_view_body.dart'; +import 'package:syncrow_app/features/menu/view/menu_view.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/view/scene_view.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/services/api/profile_api.dart'; +import 'package:syncrow_app/services/api/spaces_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +part 'home_state.dart'; + +class HomeCubit extends Cubit { + HomeCubit._() : super(HomeInitial()) { + checkIfNotificationPermissionGranted(); + fetchUserInfo(); + if (selectedSpace == null) { + fetchUnitsByUserId(); + // .then((value) { + // if (selectedSpace != null) { + // fetchRoomsByUnitId(selectedSpace!); + // } + // }); + } + } + static UserModel? user; + static HomeCubit? _instance; + static HomeCubit getInstance() { + // If an instance already exists, return it + _instance ??= HomeCubit._(); + return _instance!; + } + + Future fetchUserInfo() async { + try { + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + user = await ProfileApi().fetchUserInfo(uuid); + emit(HomeUserInfoLoaded(user!)); // Emit state after fetching user info + } catch (e) { + return; + } + } + + void emitSafe(HomeState newState) { + final cubit = this; + if (!cubit.isClosed) { + cubit.emit(newState); + } + } + + @override + Future close() { + _instance = null; + selectedSpace = null; + selectedRoom = null; + pageIndex = 0; + OneSignal.User.pushSubscription.removeObserver((stateChanges) => oneSignalSubscriptionObserver); + OneSignal.Notifications.removePermissionObserver((permission) => oneSignalPermissionObserver); + OneSignal.Notifications.removeClickListener((event) => oneSignalClickListenerObserver); + return super.close(); + } + + static HomeCubit get(context) => BlocProvider.of(context); + + List? spaces; + + SpaceModel? selectedSpace; + + RoomModel? selectedRoom; + + PageController devicesPageController = PageController(); + + PageController roomsPageController = PageController(); + + var duration = const Duration(milliseconds: 300); + + void oneSignalPermissionObserver; + void oneSignalSubscriptionObserver; + void oneSignalClickListenerObserver; + + // selectSpace(SpaceModel space) async { + // selectedSpace = space; + // emit(SpaceSelected(space)); + // } + + checkIfNotificationPermissionGranted() async { + try { + OneSignal.initialize('762350c9-1e5d-4d95-a648-16d4dc8a25e1'); + + //Show native push notification dialog + if (Platform.isIOS) { + await OneSignal.Notifications.permissionNative(); + } else { + await OneSignal.Notifications.requestPermission(true); + } + + if (await Permission.notification.isGranted == false) { + return; + } + + var userUuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ?? ''; + if (userUuid.isNotEmpty) { + await OneSignal.login(userUuid); + } + //Enable push notifications + await OneSignal.User.pushSubscription.optIn(); + + //this function will be called once a user is subscribed + oneSignalSubscriptionObserver = OneSignal.User.pushSubscription.addObserver((state) async { + if (state.current.optedIn) { + await _sendSubscriptionId(); + } + }); + + // Send the player id when a user allows notifications + oneSignalPermissionObserver = OneSignal.Notifications.addPermissionObserver((state) async { + await _sendSubscriptionId(); + }); + + //check if the player id is sent, if not send it again + await _sendSubscriptionId(); + + oneSignalClickListenerObserver = OneSignal.Notifications.addClickListener((event) async { + //Once the user clicks on the notification + }); + } catch (err) { + debugPrint("******* Error"); + debugPrint(err.toString()); + rethrow; + } + } + + _sendSubscriptionId() async { + // String? subscriptionId = OneSignal.User.pushSubscription.id ?? ''; + //TODO send the subscription id to BE + } + + changeSelectedSpace(SpaceModel space) { + selectedSpace = space; + emitSafe(SpaceSelected(space)); + fetchRoomsByUnitId(space); + } + + roomSliderPageChanged(int index) { + devicesPageController.animateToPage( + index, + duration: duration, + curve: Curves.linear, + ); + + if (index == 0) { + unselectRoom(); + } else { + selectedRoom = selectedSpace!.rooms![index - 1]; + emitSafe(RoomSelected(selectedRoom!)); + } + } + + devicesPageChanged(int index) { + roomsPageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + ); + + if (index <= 0) { + unselectRoom(); + } else { + selectedRoom = selectedSpace!.rooms![index - 1]; + emitSafe(RoomSelected(selectedRoom!)); + } + } + + unselectRoom() { + // selectedRoom = null; + devicesPageController.animateToPage( + 0, + duration: duration, + curve: Curves.linear, + ); + + roomsPageController.animateToPage( + 0, + duration: duration, + curve: Curves.linear, + ); + + emitSafe(RoomUnSelected()); + } + +//////////////////////////////////////// API //////////////////////////////////////// + generateInvitation(String unitId) async { + try { + final invitationCode = await SpacesAPI.generateInvitationCode(unitId); + if (invitationCode.isNotEmpty) { + Share.share('The invitation code is $invitationCode'); + CustomSnackBar.displaySnackBar( + 'Invitation code generated successfully the code is: $invitationCode'); + } else { + CustomSnackBar.displaySnackBar('Please try again!'); + } + } catch (failure) { + CustomSnackBar.displaySnackBar('Something went wrong'); + return; + } + } + + Future joinAUnit(String code) async { + try { + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ?? ''; + Map body = {'userUuid': uuid, 'inviteCode': code}; + + final success = await SpacesAPI.joinUnit(body); + if (success) { + await fetchUnitsByUserId(); + CustomSnackBar.displaySnackBar('Done successfully'); + } + return true; + } catch (failure) { + CustomSnackBar.displaySnackBar('Something went wrong'); + return false; + } + } + + fetchUnitsByUserId() async { + emitSafe(GetSpacesLoading()); + try { + spaces = await SpacesAPI.getUnitsByUserId(); + } catch (failure) { + emitSafe(GetSpacesError("No units found")); + return; + } + + if (spaces != null && spaces!.isNotEmpty) { + selectedSpace = spaces!.first; + await fetchRoomsByUnitId(selectedSpace!); + emitSafe(GetSpacesSuccess(spaces!)); + } else { + emitSafe(GetSpacesError("No spaces found")); + } + } + + fetchRoomsByUnitId(SpaceModel space) async { + emitSafe(GetSpaceRoomsLoading()); + try { + space.rooms = await SpacesAPI.getRoomsBySpaceId(space.id!); + } catch (failure) { + emitSafe(GetSpaceRoomsError(failure.toString())); + return; + } + if (space.rooms != null && space.rooms!.isNotEmpty) { + emitSafe(GetSpaceRoomsSuccess(space.rooms!)); + } else { + emitSafe(GetSpaceRoomsError("No rooms found")); + } + } + + /////////////////////////////////////// Nav /////////////////////////////////////// + + static int pageIndex = 0; + + static Map> appBarActions = { + 'Dashboard': [ + // IconButton( + // icon: const Icon( + // Icons.add, + // size: 25, + // ), + // style: ButtonStyle( + // foregroundColor: WidgetStateProperty.all(ColorsManager.textPrimaryColor), + // ), + // onPressed: () { + // Navigator.push( + // NavigationService.navigatorKey.currentContext!, + // CustomPageRoute( + // builder: (context) => CurtainView( + // curtain: DeviceModel( + // name: "Curtain", + // status: [StatusModel(code: "awd", value: 1)], + // productType: DeviceType.Curtain, + // ), + // ), + // ), + // ); + // }, + // ), + ], + 'Devices': [ + //TODO: to be checked + // IconButton( + // icon: const Icon( + // Icons.add, + // size: 25, + // ), + // style: ButtonStyle( + // foregroundColor: + // MaterialStateProperty.all(ColorsManager.textPrimaryColor), + // ), + // onPressed: () {}, + // ), + // IconButton( + // icon: const Icon( + // Icons.more_vert, + // size: 25, + // ), + // style: ButtonStyle( + // foregroundColor: + // MaterialStateProperty.all(ColorsManager.textPrimaryColor), + // ), + // onPressed: () {}, + // ), + ], + 'Routine': [ + // IconButton( + // icon: Image.asset( + // Assets.assetsIconsFilter, + // height: 20, + // width: 20, + // ), + // onPressed: () {}, + // ), + IconButton( + icon: const Icon( + Icons.add, + size: 32, + ), + style: ButtonStyle( + foregroundColor: WidgetStateProperty.all(ColorsManager.textPrimaryColor), + ), + onPressed: () { + Navigator.pushNamed( + NavigationService.navigatorKey.currentContext!, + Routes.sceneTasksRoute, + arguments: SceneSettingsRouteArguments( + sceneType: '', + sceneId: '', + sceneName: '', + ), + ); + NavigationService.navigatorKey.currentContext! + .read() + .add(const ClearTaskListEvent()); + NavigationService.navigatorKey.currentContext! + .read() + .add(const SceneTypeEvent(CreateSceneEnum.none)); + NavigationService.navigatorKey.currentContext! + .read() + .add(const SmartSceneClearEvent()); + BlocProvider.of(NavigationService.navigatorKey.currentState!.context) + .add(ResetEffectivePeriod()); + }, + ), + IconButton( + icon: const Icon( + Icons.more_vert, + size: 28, + ), + style: ButtonStyle( + foregroundColor: WidgetStateProperty.all(ColorsManager.textPrimaryColor), + ), + onPressed: () {}, + ), + ], + 'Menu': [ + IconButton( + icon: SvgPicture.asset( + Assets.assetsIconsScan, + height: 20, + width: 20, + ), + onPressed: () {}, + ), + ], + }; + + static Map appBarLeading = { + 'Dashboard': const AppBarHomeDropdown(), + 'Devices': const AppBarHomeDropdown(), + 'Routine': const AppBarHomeDropdown(), + 'Menu': Padding( + padding: const EdgeInsets.only(left: 15), + child: Image.asset( + Assets.assetsImagesLogoHorizontal, + height: 15, + width: 100, + fit: BoxFit.scaleDown, + ), + ), + }; + + static var bottomNavItems = [ + defaultBottomNavBarItem(icon: Assets.assetsIconsDashboard, label: 'Dashboard'), + // defaultBottomNavBarItem(icon: Assets.assetsIconslayout, label: 'Layout'), + defaultBottomNavBarItem(icon: Assets.assetsIconsDevices, label: 'Devices'), + defaultBottomNavBarItem(icon: Assets.assetsIconsRoutines, label: 'Routine'), + defaultBottomNavBarItem(icon: Assets.assetsIconsMenu, label: 'Menu'), + ]; + + final List pages = [ + const DashboardView(), + // const LayoutPage(), + BlocProvider( + create: (context) => DevicesCubit.getInstance(), + child: const DevicesViewBody(), + ), + const SceneView(), + const MenuView(), + ]; + + void updatePageIndex(int index) { + pageIndex = index; + + emitSafe(NavChangePage()); + } + + void updateDevice(String deviceId) async { + try { + final response = await DevicesAPI.firmwareDevice(deviceId: deviceId, firmwareVersion: '0'); + if (response['success'] ?? false) { + CustomSnackBar.displaySnackBar('No updates available'); + } + } catch (_) {} + } +} + +BottomNavigationBarItem defaultBottomNavBarItem({required String icon, required String label}) { + return BottomNavigationBarItem( + icon: SvgPicture.asset(icon), + activeIcon: SvgPicture.asset( + icon.replaceAll('.svg', '-fill.svg'), + colorFilter: const ColorFilter.mode( + ColorsManager.primaryColor, + BlendMode.srcIn, + ), + ), + label: label, + ); +} diff --git a/lib/features/app_layout/bloc/home_state.dart b/lib/features/app_layout/bloc/home_state.dart new file mode 100644 index 0000000..1125e01 --- /dev/null +++ b/lib/features/app_layout/bloc/home_state.dart @@ -0,0 +1,66 @@ +part of 'home_cubit.dart'; + +abstract class HomeState {} + +class HomeInitial extends HomeState {} + +//base states +class HomeLoading extends HomeState {} + +class HomeError extends HomeState { + final String errMessage; + + HomeError(this.errMessage); +} + +class HomeSuccess extends HomeState {} + +///specific states +//get spaces +class GetSpacesLoading extends HomeLoading {} + +class GetSpacesSuccess extends HomeSuccess { + final List spaces; + + GetSpacesSuccess(this.spaces); +} + +class GetSpacesError extends HomeError { + GetSpacesError(super.errMessage); +} + +//get rooms +class GetSpaceRoomsLoading extends HomeLoading {} + +class GetSpaceRoomsSuccess extends HomeSuccess { + final List rooms; + + GetSpaceRoomsSuccess(this.rooms); +} + +class GetSpaceRoomsError extends HomeError { + GetSpaceRoomsError(super.errMessage); +} + +//UI states +class SpaceSelected extends HomeState { + final SpaceModel space; + + SpaceSelected(this.space); +} + +class RoomSelected extends HomeState { + final RoomModel room; + + RoomSelected(this.room); +} + +class RoomUnSelected extends HomeState {} + +class NavChangePage extends HomeState {} +// Define new state classes +class HomeUserInfoLoaded extends HomeState { + final UserModel user; + + HomeUserInfoLoaded(this.user); +} diff --git a/lib/features/app_layout/model/space_model.dart b/lib/features/app_layout/model/space_model.dart new file mode 100644 index 0000000..8d7d5b8 --- /dev/null +++ b/lib/features/app_layout/model/space_model.dart @@ -0,0 +1,37 @@ +import 'package:syncrow_app/features/devices/model/room_model.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class SpaceModel { + final String? id; + final String? name; + final SpaceType type; + late List? rooms; + + SpaceModel({ + required this.type, + required this.id, + required this.name, + required this.rooms, + }); + + Map toJson() { + return { + 'id': id, + 'name': name, + 'rooms': rooms, + }; + } + + factory SpaceModel.fromJson(Map json) { + return SpaceModel( + id: json['uuid'], + name: json['name'], + type: spaceTypesMap[json['type']]!, + rooms: [], + ); + } + + static List fromJsonList(List jsonList) { + return jsonList.map((item) => SpaceModel.fromJson(item)).toList(); + } +} diff --git a/lib/features/app_layout/view/app_layout.dart b/lib/features/app_layout/view/app_layout.dart new file mode 100644 index 0000000..214cce2 --- /dev/null +++ b/lib/features/app_layout/view/app_layout.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/app_layout/view/widgets/app_body.dart'; +import 'package:syncrow_app/features/app_layout/view/widgets/default_app_bar.dart'; +import 'package:syncrow_app/features/app_layout/view/widgets/default_nav_bar.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; + +class AppLayout extends StatelessWidget { + const AppLayout({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => HomeCubit.getInstance(), + child: BlocBuilder( + builder: (context, state) { + return DefaultScaffold( + appBar: HomeCubit.getInstance().spaces != null ? const DefaultAppBar() : null, + bottomNavBar: const DefaultNavBar(), + child: const AppBody(), + ); + }, + ), + ); + } +} diff --git a/lib/features/app_layout/view/widgets/app_bar_home_dropdown.dart b/lib/features/app_layout/view/widgets/app_bar_home_dropdown.dart new file mode 100644 index 0000000..e00eae3 --- /dev/null +++ b/lib/features/app_layout/view/widgets/app_bar_home_dropdown.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class AppBarHomeDropdown extends StatelessWidget { + const AppBarHomeDropdown({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Center( + child: DropdownButton( + icon: const Icon( + Icons.expand_more, + color: ColorsManager.textPrimaryColor, + size: 16, + ), + underline: const SizedBox.shrink(), + padding: EdgeInsets.zero, + borderRadius: BorderRadius.circular(20), + value: HomeCubit.getInstance().selectedSpace!.id, + items: HomeCubit.getInstance().spaces!.map((space) { + return DropdownMenuItem( + alignment: AlignmentDirectional.centerStart, + value: space.id, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + Assets.assetsIconsHome, + width: 25, + height: 25, + colorFilter: const ColorFilter.mode( + ColorsManager.textPrimaryColor, + BlendMode.srcIn, + ), + ), + const SizedBox(width: 5), + Flexible( + fit: FlexFit.loose, + child: BodyMedium( + text: space.name ?? "??", + style: context.bodyMedium.copyWith( + fontSize: 15, + color: ColorsManager.textPrimaryColor, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const SizedBox(width: 5), + ], + ), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + HomeCubit.getInstance().changeSelectedSpace( + HomeCubit.getInstance() + .spaces! + .firstWhere((element) => element.id == value)); + } + }, + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/app_layout/view/widgets/app_body.dart b/lib/features/app_layout/view/widgets/app_body.dart new file mode 100644 index 0000000..f902db1 --- /dev/null +++ b/lib/features/app_layout/view/widgets/app_body.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; + +class AppBody extends StatelessWidget { + const AppBody({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is GetSpacesError) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errMessage), + ), + ); + } + }, + builder: (context, state) { + return state is! GetSpacesLoading + ? state is! GetSpaceRoomsLoading + ? HomeCubit.getInstance().pages[HomeCubit.pageIndex] + : const Center(child: CircularProgressIndicator()) + : const Center(child: CircularProgressIndicator()); + }, + ); + } +} diff --git a/lib/features/app_layout/view/widgets/default_app_bar.dart b/lib/features/app_layout/view/widgets/default_app_bar.dart new file mode 100644 index 0000000..6c67536 --- /dev/null +++ b/lib/features/app_layout/view/widgets/default_app_bar.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { + const DefaultAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.only( + top: 20, + ), + child: AppBar( + backgroundColor: Colors.transparent, + leadingWidth: 150, + toolbarHeight: Constants.appBarHeight, + leading: HomeCubit.getInstance().spaces!.isNotEmpty + ? HomeCubit.appBarLeading[ + HomeCubit.bottomNavItems[HomeCubit.pageIndex].label] + : null, + actions: HomeCubit.appBarActions[ + HomeCubit.bottomNavItems[HomeCubit.pageIndex].label], + )); + }, + ); + } + + @override + Size get preferredSize => Size.fromHeight(Constants.appBarHeight); +} diff --git a/lib/features/app_layout/view/widgets/default_nav_bar.dart b/lib/features/app_layout/view/widgets/default_nav_bar.dart new file mode 100644 index 0000000..79754a6 --- /dev/null +++ b/lib/features/app_layout/view/widgets/default_nav_bar.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DefaultNavBar extends StatelessWidget { + const DefaultNavBar({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + var cubit = HomeCubit.getInstance(); + return BottomNavigationBar( + backgroundColor: Colors.transparent, + onTap: (int index) { + cubit.updatePageIndex(index); + HomeCubit.getInstance().updatePageIndex(index); + }, + currentIndex: HomeCubit.pageIndex, + selectedItemColor: ColorsManager.primaryColor, + selectedLabelStyle: const TextStyle( + color: ColorsManager.primaryColor, + fontSize: 10, + ), + showUnselectedLabels: true, + unselectedItemColor: Colors.grey, + type: BottomNavigationBarType.fixed, + elevation: 0, + items: HomeCubit.bottomNavItems, + ); + }, + ); + } +} diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart new file mode 100644 index 0000000..2b1ae24 --- /dev/null +++ b/lib/features/auth/bloc/auth_cubit.dart @@ -0,0 +1,340 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/features/auth/model/login_with_email_model.dart'; +import 'package:syncrow_app/features/auth/model/signup_model.dart'; +import 'package:syncrow_app/features/auth/model/token.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/services/api/authentication_api.dart'; +import 'package:syncrow_app/services/api/profile_api.dart'; +import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; +part 'auth_state.dart'; + +class AuthCubit extends Cubit { + AuthCubit() : super(AuthInitial()); + static AuthCubit get(context) => BlocProvider.of(context); + + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + + String fullName = ''; + String email = ''; + String forgetPasswordEmail = ''; + String signUpPassword = ''; + String newPassword = ''; + String maskedEmail = ''; + String otpCode = ''; + final loginFormKey = GlobalKey(); + final signUpFormKey = GlobalKey(); + final checkEmailFormKey = GlobalKey(); + final createNewPasswordKey = GlobalKey(); + + bool isPasswordVisible = false; + bool showValidationMessage = false; + + void changePasswordVisibility() { + isPasswordVisible = !isPasswordVisible; + emit(AuthPasswordVisibilityChanged()); + } + + bool agreeToTerms = false; + + void changeAgreeToTerms() { + agreeToTerms = !agreeToTerms; + emit(AuthAgreeToTermsChanged()); + } + + static UserModel? user; + + static Token token = Token.emptyConstructor(); + + setOtpCode(String value) { + otpCode = value; + } + +/////////////////////////////////////VALIDATORS///////////////////////////////////// + String? passwordValidator(String? value) { + if (value == null || value.isEmpty) { + return "Please enter your password"; + } + + if (value.length < 8) { + return 'Password must be at least 8 characters long'; + } + + if (!RegExp(r'[a-z]').hasMatch(value)) { + return 'Password must contain at least one lowercase letter'; + } + + if (!RegExp(r'[A-Z]').hasMatch(value)) { + return 'Password must contain at least one uppercase letter'; + } + + if (!RegExp(r'\d').hasMatch(value)) { + return 'Password must contain at least one number'; + } + + if (!RegExp(r'[!"#$%&()*+,-./:;<=>?@[\]^_`{|}~]').hasMatch(value)) { + return 'Password must contain at least one special character'; + } + + return null; + } + + String? fullNameValidator(String? value) { + if (value == null) return 'Full name is required'; + + final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim(); + + if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) { + return 'Full name must be between 2 and 30 characters long'; + } + + // Test if it contains anything but alphanumeric spaces and single quote + + if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) { + return 'Only alphanumeric characters, space, dash and single quote are allowed'; + } + + final parts = withoutExtraSpaces.split(' '); + + if (parts.length < 2) return 'Full name must contain first and last names'; + + if (parts.length > 3) return 'Full name can at most contain 3 parts'; + + if (parts.any((part) => part.length < 2 || part.length > 30)) { + return 'Full name parts must be between 2 and 30 characters long'; + } + return null; + } + + String? reEnterPasswordCheck(String? value) { + passwordValidator(value); + if (signUpPassword == value) { + return null; + } else { + return 'Passwords do not match'; + } + } + + String? reEnterPasswordCheckForgetPass(String? value) { + passwordValidator(value); + if (newPassword == value) { + return null; + } else { + return 'Passwords do not match'; + } + } + + String? emailAddressValidator(String? value) { + if (value != null && value.isNotEmpty && value != "") { + if (checkValidityOfEmail(value)) { + return null; + } else { + return 'Please enter a valid email'; + } + } else { + return 'Email address is required'; + } + } + + bool checkValidityOfEmail(String? email) { + if (email != null) { + return RegExp( + r"^[a-zA-Z0-9]+([.!#$%&'*+/=?^_`{|}~-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-]?[a-zA-Z0-9]+)*\.[a-zA-Z0-9]{2,}$") + .hasMatch(email); + } else { + return false; + } + } + + // Function to mask the email + String maskEmail(String email) { + final emailParts = email.split('@'); + if (emailParts.length != 2) return email; + + final localPart = emailParts[0]; + final domainPart = emailParts[1]; + + if (localPart.length < 3) return email; + + final start = localPart.substring(0, 2); + final end = localPart.substring(localPart.length - 1); + + final maskedLocalPart = '$start******$end'; + return '$maskedLocalPart@$domainPart'; + } + +/////////////////////////////////////API CALLS///////////////////////////////////// + login() async { + emit(AuthLoginLoading()); + try { + if (emailController.text.isEmpty || passwordController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter your credentials'); + emit(AuthLoginError(message: 'Something went wrong')); + return; + } + + token = await AuthenticationAPI.loginWithEmail( + model: LoginWithEmailModel( + email: emailController.text.toLowerCase(), + password: passwordController.text, + ), + ); + } catch (failure) { + emit(AuthLoginError(message: failure.toString())); + return; + } + if (token.accessTokenIsNotEmpty) { + debugPrint('token: ${token.accessToken}'); + FlutterSecureStorage storage = const FlutterSecureStorage(); + await storage.write( + key: Token.loginAccessTokenKey, value: token.accessToken); + const FlutterSecureStorage().write( + key: UserModel.userUuidKey, + value: Token.decodeToken(token.accessToken)['uuid'].toString()); + user = UserModel.fromToken(token); + emailController.clear(); + passwordController.clear(); + emit(AuthLoginSuccess()); + } else { + emit(AuthLoginError(message: 'Something went wrong')); + } + } + + signUp() async { + emit(AuthLoginLoading()); + final response; + try { + List userFullName = fullName.split(' '); + response = await AuthenticationAPI.signUp( + model: SignUpModel( + email: email.toLowerCase(), + password: signUpPassword, + firstName: userFullName[0], + lastName: userFullName[1]), + ); + } catch (failure) { + emit(AuthLoginError(message: failure.toString())); + return; + } + if (response) { + maskedEmail = maskEmail(email); + await sendOtp(); + } else { + emit(AuthLoginError(message: 'Something went wrong')); + } + } + + sendOtp() async { + try { + emit(AuthLoading()); + await AuthenticationAPI.sendOtp( + body: {'email': email, 'type': 'VERIFICATION'}); + emit(AuthSignUpSuccess()); + } catch (_) { + emit(AuthLoginError(message: 'Something went wrong')); + } + } + + reSendOtp() async { + try { + emit(AuthLoading()); + await AuthenticationAPI.sendOtp( + body: {'email': email, 'type': 'VERIFICATION'}); + emit(ResendOtpSuccess()); + } catch (_) { + emit(AuthLoginError(message: 'Something went wrong')); + } + } + + verifyOtp(bool isForgotPass) async { + emit(AuthLoginLoading()); + try { + final response = await AuthenticationAPI.verifyPassCode( + body: {'email': email, 'type': 'VERIFICATION', 'otpCode': otpCode}); + if (response['statusCode'] == 200) { + if (!isForgotPass) { + emailController.text = email; + passwordController.text = signUpPassword; + await login(); + } + emit(AuthOtpSuccess()); + } else { + emit(AuthLoginError(message: 'Something went wrong')); + } + } catch (failure) { + emit(AuthLoginError(message: 'Something went wrong')); + return; + } + } + + logout() async { + emit(AuthLogoutLoading()); + try { + FlutterSecureStorage storage = const FlutterSecureStorage(); + await storage.delete(key: Token.loginAccessTokenKey); + NavigationService.navigatorKey.currentState!.pushNamedAndRemoveUntil( + Routes.splash, + (Route route) => false, + ); + } catch (failure) { + emit(AuthLogoutError(message: 'Something went wrong')); + return; + } + } + + getTokenAndValidate() async { + try { + emit(AuthTokenLoading()); + const storage = FlutterSecureStorage(); + final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( + StringsManager.firstLaunch) ?? + true; + + if (firstLaunch) { + storage.deleteAll(); + } + + await SharedPreferencesHelper.saveBoolToSP( + StringsManager.firstLaunch, false); + + final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; + if (value.isEmpty) { + emit(AuthTokenError(message: "Token not found")); + return; + } + + final tokenData = Token.decodeToken(value); + + if (tokenData.containsKey('exp')) { + final exp = tokenData['exp'] ?? 0; + final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; + + if (currentTime < exp) { + emit(AuthTokenSuccess()); + } else { + emit(AuthTokenError(message: "Token expired")); + } + } else { + emit(AuthTokenError(message: "Something went wrong")); + } + } catch (_) { + emit(AuthTokenError(message: "Something went wrong")); + } + } + + sendToForgetPassword({required String password}) async { + try { + emit(AuthForgetPassLoading()); + await AuthenticationAPI.forgetPassword(email: email, password: password); + emit(AuthForgetPassSuccess()); + } catch (_) { + emit(AuthForgetPassError(message: 'Something went wrong')); + } + } +} diff --git a/lib/features/auth/bloc/auth_state.dart b/lib/features/auth/bloc/auth_state.dart new file mode 100644 index 0000000..9dbcc2b --- /dev/null +++ b/lib/features/auth/bloc/auth_state.dart @@ -0,0 +1,66 @@ +part of 'auth_cubit.dart'; + +abstract class AuthState {} + +class AuthInitial extends AuthState {} + +//base states +class AuthLoading extends AuthState {} + +class AuthError extends AuthState { + final String message; + String? code; + AuthError({required this.message, this.code}); +} + +class AuthSuccess extends AuthState {} + +//user log states +class AuthLoginLoading extends AuthLoading {} + +class AuthLoginSuccess extends AuthSuccess {} + +class AuthOtpSuccess extends AuthSuccess {} + +class AuthSignUpSuccess extends AuthSuccess {} + +class ResendOtpSuccess extends AuthSuccess {} + +class AuthLoginError extends AuthError { + AuthLoginError({required super.message, super.code}); +} + +class AuthLogoutLoading extends AuthLoading {} + +class AuthLogoutSuccess extends AuthSuccess {} + +class AuthLogoutError extends AuthError { + AuthLogoutError({required super.message, super.code}); +} + +// UI states +class AuthPasswordVisibilityChanged extends AuthState {} + +class AuthAgreeToTermsChanged extends AuthState {} + +//token states +class AuthTokenLoading extends AuthLoading {} + +class AuthTokenSuccess extends AuthSuccess {} + +class AuthTokenError extends AuthError { + AuthTokenError({required super.message, super.code}); +} + + + + +//ForgetPassword log states +class AuthForgetPassLoading extends AuthLoading {} + +class AuthForgetPassSuccess extends AuthSuccess {} + +class AuthForgetPassError extends AuthError { + AuthForgetPassError({required super.message, super.code}); +} + diff --git a/lib/features/auth/model/login_with_email_model.dart b/lib/features/auth/model/login_with_email_model.dart new file mode 100644 index 0000000..c387b0d --- /dev/null +++ b/lib/features/auth/model/login_with_email_model.dart @@ -0,0 +1,23 @@ +class LoginWithEmailModel { + final String email; + final String password; + + LoginWithEmailModel({ + required this.email, + required this.password, + }); + + factory LoginWithEmailModel.fromJson(Map json) { + return LoginWithEmailModel( + email: json['email'], + password: json['password'], + ); + } + + Map toJson() { + return { + 'email': email, + 'password': password, + }; + } +} diff --git a/lib/features/auth/model/signup_model.dart b/lib/features/auth/model/signup_model.dart new file mode 100644 index 0000000..c4a0adf --- /dev/null +++ b/lib/features/auth/model/signup_model.dart @@ -0,0 +1,29 @@ +class SignUpModel { + final String email; + final String password; + final String firstName; + final String lastName; + + SignUpModel( + {required this.email, + required this.password, + required this.firstName, + required this.lastName}); + + factory SignUpModel.fromJson(Map json) { + return SignUpModel( + email: json['email'], + password: json['password'], + firstName: json['firstName'], + lastName: json['lastName']); + } + + Map toJson() { + return { + 'email': email, + 'password': password, + 'firstName': firstName, + 'lastName': lastName, + }; + } +} diff --git a/lib/features/auth/model/token.dart b/lib/features/auth/model/token.dart new file mode 100644 index 0000000..7513673 --- /dev/null +++ b/lib/features/auth/model/token.dart @@ -0,0 +1,79 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/utils/helpers/decode_base64.dart'; + +class Token { + static const String loginAccessTokenKey = 'accessToken'; + static const String loginRefreshTokenKey = 'refreshToken'; + + final String accessToken; + final String refreshToken; + + //{ + // "email": "test@test.com", + // "userId": 3, + // "uuid": "563e22d2-cb30-46d3-8c48-fa7d762342f0", + // "sessionId": "f76aa067-c915-4921-b04d-9fbc71c4965a", + // "iat": 1710137435, + // "exp": 1710137735 + // } + + final String sessionId; + + final int iat; + final int exp; + + Token.emptyConstructor() + : accessToken = '', + refreshToken = '', + sessionId = '', + iat = 0, + exp = 0; + + bool get accessTokenIsNotEmpty => accessToken.isNotEmpty; + + bool get refreshTokenIsNotEmpty => refreshToken.isNotEmpty; + + bool get isNotEmpty => accessToken.isNotEmpty && refreshToken.isNotEmpty; + + Token( + this.accessToken, + this.refreshToken, + this.sessionId, + this.iat, + this.exp, + ); + + Token.refreshToken(this.refreshToken) + : accessToken = '', + sessionId = '', + iat = 0, + exp = 0; + + factory Token.fromJson(Map json) { + //save token to secure storage + var storage = const FlutterSecureStorage(); + storage.write( + key: loginAccessTokenKey, value: json[loginAccessTokenKey] ?? ''); + storage.write( + key: loginRefreshTokenKey, value: json[loginRefreshTokenKey] ?? ''); + //create token object ? + return Token(json[loginAccessTokenKey] ?? '', + json[loginRefreshTokenKey] ?? '', '', 0, 0); + } + + Map refreshTokenToJson() => + {loginRefreshTokenKey: refreshToken}; + + Map accessTokenToJson() => {loginAccessTokenKey: accessToken}; + + static Map decodeToken(String accessToken) { + final parts = accessToken.split('.'); + if (parts.length != 3) { + throw Exception('invalid access token'); + } + final payload = decodeBase64(parts[1]); + return json.decode(payload); + } +} diff --git a/lib/features/auth/model/user_model.dart b/lib/features/auth/model/user_model.dart new file mode 100644 index 0000000..fd68fb4 --- /dev/null +++ b/lib/features/auth/model/user_model.dart @@ -0,0 +1,84 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:syncrow_app/features/auth/model/token.dart'; + +class UserModel { + static String userUuidKey = 'userUuid'; + final String? uuid; + final String? email; + final String? firstName; + final String? lastName; + final Uint8List? profilePicture; + final String? phoneNumber; + final bool? isEmailVerified; + final String? regionName; + final String? timeZone; + final bool? isAgreementAccepted; + + UserModel({ + required this.uuid, + required this.email, + required this.firstName, + required this.lastName, + required this.profilePicture, + required this.phoneNumber, + required this.isEmailVerified, + required this.isAgreementAccepted, + required this.regionName, // Add this line + required this.timeZone, // Add this line + + }); + + factory UserModel.fromJson(Map json) { + return UserModel( + uuid: json['uuid'], + email: json['email'], + firstName: json['firstName'], + lastName: json['lastName'], + profilePicture: UserModel.decodeBase64Image(json['profilePicture']), + phoneNumber: json['phoneNumber'], + isEmailVerified: json['isEmailVerified'], + isAgreementAccepted: json['isAgreementAccepted'], + regionName: json['region']?['regionName'], // Extract regionName + timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName + ); + } + //uuid to json + //from token + factory UserModel.fromToken(Token token) { + Map tempJson = Token.decodeToken(token.accessToken); + return UserModel( + uuid: tempJson['uuid'].toString(), + email: tempJson['email'], + lastName: tempJson['lastName'], + firstName:tempJson['firstName'] , + profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']), + phoneNumber: null, + isEmailVerified: null, + isAgreementAccepted: null, + regionName: tempJson['region']?['regionName'], + timeZone: tempJson['timezone']?['timeZoneOffset'], + ); + } + + static Uint8List? decodeBase64Image(String? base64String) { + if (base64String != null) { + return base64.decode(base64String); + } + return null; + } + + Map toJson() { + return { + 'id': uuid, + 'email': email, + 'lastName': lastName, + 'firstName': firstName, + 'photoUrl': profilePicture, + 'phoneNumber': phoneNumber, + 'isEmailVerified': isEmailVerified, + 'isAgreementAccepted': isAgreementAccepted, + }; + } +} diff --git a/lib/features/auth/model/verify_code.dart b/lib/features/auth/model/verify_code.dart new file mode 100644 index 0000000..da29c25 --- /dev/null +++ b/lib/features/auth/model/verify_code.dart @@ -0,0 +1,27 @@ +class VerifyPassCode { + static const String verificationPhone = 'phone'; + static const String verificationPassCode = 'passCode'; + static const String verificationAgent = 'agent'; + static const String verificationDeviceId = 'deviceId'; + + final String phone; + final String passCode; + final String agent; + final String deviceId; + + VerifyPassCode( + {required this.phone, required this.passCode, required this.agent, required this.deviceId}); + + factory VerifyPassCode.fromJson(Map json) => VerifyPassCode( + phone: json[verificationPhone], + passCode: json[verificationPassCode], + agent: json[verificationAgent], + deviceId: json[verificationDeviceId]); + + Map toJson() => { + verificationPhone: phone, + verificationPassCode: passCode, + verificationAgent: agent, + verificationDeviceId: deviceId, + }; +} diff --git a/lib/features/auth/view/check_email_page.dart b/lib/features/auth/view/check_email_page.dart new file mode 100644 index 0000000..63fc691 --- /dev/null +++ b/lib/features/auth/view/check_email_page.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/auth/view/otp_view.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/styles_manager.dart'; + +class checkEmailPage extends StatelessWidget { + const checkEmailPage({super.key}); + @override + Widget build(BuildContext context) { + final formKey = AuthCubit.get(context).checkEmailFormKey; + + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.light)); + return BlocConsumer( + listener: (context, state) { + if (state is AuthError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + ), + ); + } else if (state is AuthSignUpSuccess) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const OtpView( + isForgetPage: true, + ), + )); + } + }, + builder: (context, state) { + return Scaffold( + body: Stack( + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + ), + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.assetsImagesVector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.only( + right: Constants.defaultPadding, + left: Constants.defaultPadding, + top: Constants.defaultPadding, + ), + child: Form( + key: formKey, + child: SingleChildScrollView( + child: Center( + child: Column( + children: [ + Center( + child: SvgPicture.asset( + Assets.assetsImagesLogo, + width: 160, + ), + ), + Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: MediaQuery.sizeOf(context).height / 5.5, + ), + TitleMedium( + text: 'Forgot password?', + style: context.titleMedium.copyWith( + fontWeight: FontsManager.extraBold, + color: Colors.white, + ), + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + text: "Enter email address", + fontColor: Colors.white, + ), + TextFormField( + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.email], + validator: AuthCubit.get(context).emailAddressValidator, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + onChanged: (value) { + AuthCubit.get(context).email = value; + }, + decoration: defaultInputDecoration(context, + hint: "Example@email.com"), + ), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: DefaultButton( + isDone: state is AuthLoginSuccess, + isLoading: state is AuthLoading, + customButtonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Colors.black.withOpacity(.25), + ), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + ), + child: const Text( + 'Send Code', + ), + onPressed: () { + AuthCubit.get(context).showValidationMessage = true; + if (formKey.currentState!.validate()) { + if ((state is! AuthLoading)) { + AuthCubit.get(context).sendOtp(); + FocusScope.of(context).unfocus(); + } + } + }, + ), + ), + ], + ), + Padding( + padding: EdgeInsets.only( + top: MediaQuery.sizeOf(context).height / 5.5), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BodyLarge( + text: "Do you have an account? ", + style: + context.displaySmall.copyWith(color: Colors.white), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: BodyLarge( + text: "Sign in", + style: context.displaySmall.copyWith( + color: Colors.black, + fontWeight: FontsManager.bold, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + )), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/auth/view/create_new_password.dart b/lib/features/auth/view/create_new_password.dart new file mode 100644 index 0000000..4452154 --- /dev/null +++ b/lib/features/auth/view/create_new_password.dart @@ -0,0 +1,186 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/styles_manager.dart'; + +class CreateNewPasswordPage extends StatelessWidget { + const CreateNewPasswordPage({super.key,}); + + @override + Widget build(BuildContext context) { + final formKey = AuthCubit.get(context).createNewPasswordKey; + + return BlocConsumer( + listener: (context, state) { + if (state is AuthForgetPassSuccess) { + Navigator.of(context).pop(); + } + }, + builder: (context, state) { + return Scaffold( + body: Stack( + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + padding: const EdgeInsets.symmetric(vertical: 24), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + ), + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.assetsImagesVector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.only( + right: Constants.defaultPadding, + left: Constants.defaultPadding, + top: Constants.defaultPadding, + ), + child: SingleChildScrollView( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: SvgPicture.asset( + Assets.assetsImagesLogo, + width: 160, + ), + ), + const SizedBox( + height: 40, + ), + TitleMedium( + text: 'Create new password', + style: context.titleMedium.copyWith( + fontWeight: FontsManager.extraBold, + color: Colors.white, + ), + ), + const SizedBox( + height: 20, + ), + Form( + key: formKey, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + const BodyMedium( + text: "Password", + fontColor: Colors.white, + ), + TextFormField( + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + autofillHints: const [AutofillHints.password], + validator: AuthCubit.get(context).passwordValidator, + onChanged: (value) { + AuthCubit.get(context).newPassword = value; + }, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + obscureText: !AuthCubit.get(context).isPasswordVisible, + decoration: defaultInputDecoration(context, + hint: "At least 8 characters"), + ), + const SizedBox(height: 16), + const BodyMedium( + text: "Re-enter Password", + fontColor: Colors.white, + ), + TextFormField( + autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.password], + onChanged: (value) {}, + validator: AuthCubit.get(context).reEnterPasswordCheckForgetPass, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + obscureText: !AuthCubit.get(context).isPasswordVisible, + decoration: defaultInputDecoration(context, + hint: "At least 8 characters"), + ), + const SizedBox(height: 40), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: DefaultButton( + isDone: state is AuthLoginSuccess, + isLoading: state is AuthLoading, + customButtonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Colors.black.withOpacity(.25), + ), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + ), + child: const Text( + 'Confirm', + ), + onPressed: () { + AuthCubit.get(context).showValidationMessage = true; + if (formKey.currentState!.validate()) { + if ((state is! AuthForgetPassLoading)) { + AuthCubit.get(context).sendToForgetPassword(password:AuthCubit.get(context).newPassword); + FocusScope.of(context).unfocus(); + } + } + }, + ), + ), + ], + ) + ], + ), + ), + ), + ], + ), + ), + ), + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/auth/view/login_view.dart b/lib/features/auth/view/login_view.dart new file mode 100644 index 0000000..05e3b92 --- /dev/null +++ b/lib/features/auth/view/login_view.dart @@ -0,0 +1,114 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/auth/view/widgets/dont_have_an_account.dart'; +import 'package:syncrow_app/features/auth/view/widgets/login_divider.dart'; +import 'package:syncrow_app/features/auth/view/widgets/login_form.dart'; +import 'package:syncrow_app/features/auth/view/widgets/login_with_google_facebook.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class LoginView extends StatelessWidget { + const LoginView({super.key}); + + @override + Widget build(BuildContext context) { + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.light)); + return BlocConsumer( + listener: (context, state) { + if (state is AuthError) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(state.message), + // ), + // ); + } + else if (state is AuthLoginSuccess) { + Navigator.popAndPushNamed(context, Routes.homeRoute); + } + }, + builder: (context, state) { + return Scaffold( + body: Stack( + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + ), + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.assetsImagesVector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.only( + right: Constants.defaultPadding, + left: Constants.defaultPadding, + top: Constants.defaultPadding, + ), + child: SingleChildScrollView( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: SvgPicture.asset( + Assets.assetsImagesLogo, + width: 160, + ), + ), + const SizedBox( + height: 40, + ), + TitleMedium( + text: 'Login', + style: context.titleMedium.copyWith( + fontWeight: FontsManager.extraBold, + color: Colors.white, + ), + ), + const SizedBox( + height: 20, + ), + const LoginForm(), + const LoginDivider(), + const LoginWithGoogleFacebook(), + const DontHaveAnAccount(), + ], + ), + ), + ), + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/auth/view/otp_view.dart b/lib/features/auth/view/otp_view.dart new file mode 100644 index 0000000..e8ca1da --- /dev/null +++ b/lib/features/auth/view/otp_view.dart @@ -0,0 +1,366 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/auth/view/create_new_password.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/life_cycle_event_handler.dart'; +import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class OtpView extends StatefulWidget { + final bool isForgetPage; + const OtpView({super.key, this.isForgetPage = false}); + + @override + State createState() => _OtpViewState(); +} + +class _OtpViewState extends State { + bool timerCanceled = false; + Timer? countdownTimer; + Duration myDuration = const Duration(); + late LifecycleEventHandler _lifecycleEventHandler; + String otpCode = ''; + int? remainingSec = 30; + + @override + void initState() { + super.initState(); + bool timerStarted = false; + _lifecycleEventHandler = LifecycleEventHandler( + resumeCallBack: () async { + SharedPreferencesHelper.saveBoolToSP('timeStampSaved', false); + String timeStampInBackground = await SharedPreferencesHelper.readStringFromSP('timeStamp'); + int savedCounter = await SharedPreferencesHelper.readIntFromSP('savedCounter'); + DateTime currentTime = DateTime.now(); + int differenceInSeconds = timeStampInBackground.isNotEmpty + ? currentTime.difference(DateTime.parse(timeStampInBackground)).inSeconds + : 0; + remainingSec = differenceInSeconds > savedCounter ? 0 : savedCounter - differenceInSeconds; + timerStarted = true; + startTimer(remainingSec ?? 0); + return; + }, + suspendingCallBack: () async { + handleTimerOnBackground(); + }, + onPauseCallBack: () async { + handleTimerOnBackground(); + }, + inactiveCallBack: () async { + handleTimerOnBackground(); + }, + ); + WidgetsBinding.instance.addObserver(_lifecycleEventHandler); + if (!timerStarted) { + timerStarted = false; + startTimer(remainingSec ?? 0); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(_lifecycleEventHandler); + super.dispose(); + } + + handleTimerOnBackground() async { + bool timeStampSaved = await SharedPreferencesHelper.readBoolFromSP('timeStampSaved') ?? false; + if (!timeStampSaved) { + final dateInString = DateTime.now().toString(); + SharedPreferencesHelper.saveIntToSP('savedCounter', remainingSec ?? 0); + SharedPreferencesHelper.saveStringToSP('timeStamp', dateInString); + SharedPreferencesHelper.saveBoolToSP('timeStampSaved', true); + } + } + + void startTimer(int sec) { + timerCanceled = false; + if (countdownTimer != null) { + countdownTimer!.cancel(); + countdownTimer = null; + } + + myDuration = Duration(seconds: sec); + int seconds = sec; + countdownTimer = Timer.periodic(const Duration(seconds: 1), (values) { + seconds = seconds - 1; + remainingSec = seconds; + + if (mounted) { + if (seconds < 0) { + setState(() { + countdownTimer!.cancel(); + timerCanceled = true; + WidgetsBinding.instance.removeObserver(_lifecycleEventHandler); + }); + } else { + setState(() { + myDuration = Duration(seconds: remainingSec ?? seconds); + }); + } + } + }); + } + + @override + Widget build(BuildContext context) { + String maskedEmail = AuthCubit.get(context).maskEmail(AuthCubit.get(context).email); + return BlocConsumer( + listener: (context, state) { + if (state is AuthOtpSuccess) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + widget.isForgetPage + ? Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CreateNewPasswordPage(), + )) + : Navigator.popAndPushNamed(context, Routes.homeRoute); + } + if (state is ResendOtpSuccess) { + startTimer(30); + } + }, + builder: (context, state) { + return Scaffold( + body: Stack( + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + ), + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.assetsImagesVector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.only( + right: Constants.defaultPadding, + left: Constants.defaultPadding, + top: Constants.defaultPadding, + ), + child: SingleChildScrollView( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: SvgPicture.asset( + Assets.assetsImagesLogo, + width: 160, + ), + ), + const SizedBox( + height: 40, + ), + TitleMedium( + text: 'Verification Code', + style: context.titleMedium.copyWith( + fontWeight: FontsManager.extraBold, + color: Colors.white, + ), + ), + const SizedBox( + height: 20, + ), + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: RichText( + text: TextSpan( + text: 'We have sent the verification code to', + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: Colors.white, + fontWeight: FontsManager.regular, + fontSize: 14, + ), + children: [ + TextSpan( + text: ' $maskedEmail', + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: Colors.black, + fontWeight: FontsManager.bold, + fontSize: 14, + ), + ), + TextSpan( + text: ' change email?', + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: const Color(0xFF87C7FF), + fontWeight: FontsManager.regular, + fontSize: 14, + ), + ), + ]), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 40, + ), + PinCodeTextField( + key: const Key('pin_code_text_field'), + appContext: context, + length: 6, + obscuringCharacter: '-', + cursorHeight: 25, + keyboardType: TextInputType.number, + autoFocus: true, + backgroundColor: Colors.transparent, + animationDuration: const Duration(milliseconds: 30), + beforeTextPaste: (text) { + // Allow pasting only if all characters are numeric + return int.tryParse(text!) != null; + }, + textStyle: Theme.of(context) + .textTheme + .headlineMedium! + .copyWith(color: Colors.white), + hintStyle: Theme.of(context) + .textTheme + .headlineMedium! + .copyWith(color: Colors.white), + enablePinAutofill: true, + pinTheme: PinTheme( + borderRadius: BorderRadius.circular(8), + inactiveBorderWidth: 1, + disabledBorderWidth: 1, + selectedBorderWidth: 1, + activeBorderWidth: 1, + errorBorderWidth: 1, + borderWidth: 1, + errorBorderColor: Colors.red, + activeColor: state is AuthLoginError ? Colors.red : Colors.white, + inactiveColor: + state is AuthLoginError ? Colors.red : Colors.white, + activeFillColor: + state is AuthLoginError ? Colors.red : Colors.white, + inactiveFillColor: + state is AuthLoginError ? Colors.red : Colors.white, + selectedFillColor: + state is AuthLoginError ? Colors.red : Colors.white, + disabledColor: Colors.white, + fieldHeight: 56, + fieldWidth: MediaQuery.sizeOf(context).width > 340 ? 40 : 20, + // fieldWidth: 40, + selectedColor: Colors.white, + shape: PinCodeFieldShape.box, + ), + onChanged: (value) { + AuthCubit.get(context).setOtpCode(value); + }, + onCompleted: (value) {}, + onSubmitted: (value) { + // AuthCubit.get(context).setOtpCode(value); + }, + ), + const SizedBox(height: 40), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: DefaultButton( + isDone: state is AuthLoginSuccess, + isLoading: state is AuthLoading, + customButtonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Colors.black.withOpacity(.25), + ), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + ), + child: const Text( + 'Verify', + ), + onPressed: () { + if ((state is! AuthLoading)) { + AuthCubit.get(context).verifyOtp(widget.isForgetPage); + FocusScope.of(context).unfocus(); + } + }, + ), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: DefaultButton( + isDone: state is AuthLoginSuccess, + isLoading: state is AuthLoading, + customButtonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Colors.black.withOpacity(.25), + ), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + ), + child: Text( + timerCanceled + ? 'Resend' + : myDuration.inSeconds + .remainder(60) + .toString() + .padLeft(2, '0'), + ), + onPressed: () async { + if (!timerCanceled) { + return; + } + if ((state is! AuthLoading)) { + await AuthCubit.get(context).reSendOtp(); + FocusScope.of(context).unfocus(); + } + }, + ), + ), + ], + ) + ], + ), + ], + ), + ), + ), + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/auth/view/sign_up_view.dart b/lib/features/auth/view/sign_up_view.dart new file mode 100644 index 0000000..e2ba925 --- /dev/null +++ b/lib/features/auth/view/sign_up_view.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/styles_manager.dart'; + +class SignUpView extends StatelessWidget { + const SignUpView({super.key}); + + @override + Widget build(BuildContext context) { + final formKey = AuthCubit.get(context).signUpFormKey; + return BlocConsumer( + listener: (context, state) { + if (state is AuthSignUpSuccess) { + Navigator.pushNamed(context, Routes.otpRoute); + // Navigator.popAndPushNamed(context, Routes.otpRoute); + } + }, + builder: (context, state) { + return Scaffold( + body: Stack( + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + padding: const EdgeInsets.symmetric(vertical: 24), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + ), + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.assetsImagesVector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.only( + right: Constants.defaultPadding, + left: Constants.defaultPadding, + top: Constants.defaultPadding, + ), + child: SingleChildScrollView( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: SvgPicture.asset( + Assets.assetsImagesLogo, + width: 160, + ), + ), + const SizedBox( + height: 40, + ), + TitleMedium( + text: 'Create new account', + style: context.titleMedium.copyWith( + fontWeight: FontsManager.extraBold, + color: Colors.white, + ), + ), + const SizedBox( + height: 20, + ), + Form( + key: formKey, + // autovalidateMode: AutovalidateMode.disabled, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: "Full Name", + fontColor: Colors.white, + ), + TextFormField( + // autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.name, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + autofillHints: const [AutofillHints.name], + // controller: AuthCubit.get(context).fullNameController, + validator: (value) => + AuthCubit.get(context).fullNameValidator(value), + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + onChanged: (value) { + AuthCubit.get(context).fullName = value; + }, + onTap: () {}, + decoration: defaultInputDecoration(context, hint: "Full Name"), + ), + const SizedBox(height: 16), + const BodyMedium( + text: "Email", + fontColor: Colors.white, + ), + TextFormField( + // autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.email], + validator: AuthCubit.get(context).emailAddressValidator, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + onChanged: (value) { + AuthCubit.get(context).email = value; + }, + decoration: + defaultInputDecoration(context, hint: "Example@email.com"), + ), + const SizedBox(height: 16), + const BodyMedium( + text: "Password", + fontColor: Colors.white, + ), + TextFormField( + // autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + autofillHints: const [AutofillHints.password], + validator: AuthCubit.get(context).passwordValidator, + onChanged: (value) { + AuthCubit.get(context).signUpPassword = value; + }, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + obscureText: !AuthCubit.get(context).isPasswordVisible, + decoration: defaultInputDecoration(context, + hint: "At least 8 characters"), + ), + const SizedBox(height: 16), + const BodyMedium( + text: "Re-enter Password", + fontColor: Colors.white, + ), + TextFormField( + autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + enableSuggestions: false, + autofillHints: const [AutofillHints.password], + onChanged: (value) {}, + validator: AuthCubit.get(context).reEnterPasswordCheck, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + obscureText: !AuthCubit.get(context).isPasswordVisible, + decoration: defaultInputDecoration(context, + hint: "At least 8 characters"), + ), + const SizedBox(height: 40), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: DefaultButton( + isDone: state is AuthLoginSuccess, + isLoading: state is AuthLoading, + customButtonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Colors.black.withOpacity(.25), + ), + foregroundColor: MaterialStateProperty.all( + Colors.white, + ), + ), + child: const Text( + 'Sign up', + ), + onPressed: () { + AuthCubit.get(context).showValidationMessage = true; + if (formKey.currentState!.validate()) { + if ((state is! AuthLoading)) { + AuthCubit.get(context).signUp(); + FocusScope.of(context).unfocus(); + } + } + }, + ), + ), + ], + ) + ], + ), + ), + ), + ], + ), + ), + ), + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/auth/view/widgets/dont_have_an_account.dart b/lib/features/auth/view/widgets/dont_have_an_account.dart new file mode 100644 index 0000000..4ad03db --- /dev/null +++ b/lib/features/auth/view/widgets/dont_have_an_account.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class DontHaveAnAccount extends StatelessWidget { + const DontHaveAnAccount({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BodyLarge( + text: "Don't have an account?", + style: context.displaySmall.copyWith(color: Colors.white), + ), + TextButton( + onPressed: () { + Navigator.pushNamed(context, Routes.authSignUp); + }, + child: BodyLarge( + text: "Sign Up", + style: context.displaySmall.copyWith( + color: Colors.black, + fontWeight: FontsManager.bold, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/auth/view/widgets/forget_password.dart b/lib/features/auth/view/widgets/forget_password.dart new file mode 100644 index 0000000..0b0f31e --- /dev/null +++ b/lib/features/auth/view/widgets/forget_password.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/auth/view/check_email_page.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class ForgetPassword extends StatelessWidget { + const ForgetPassword({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + const Spacer(), + TextButton( + onPressed: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => const checkEmailPage(),)); + }, + child: BodyMedium( + text: "Forgot Password?", + style: context.bodyMedium.copyWith( + color: Colors.white, + ), + ), + ), + ], + ); + } +} diff --git a/lib/features/auth/view/widgets/login_divider.dart b/lib/features/auth/view/widgets/login_divider.dart new file mode 100644 index 0000000..7193586 --- /dev/null +++ b/lib/features/auth/view/widgets/login_divider.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; + +class LoginDivider extends StatelessWidget { + const LoginDivider({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Row( + children: [ + Expanded( + child: Divider( + color: Colors.white, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: BodyMedium( + text: "or Sign in with", + style: TextStyle( + color: Colors.white, + ), + ), + ), + Expanded( + child: Divider( + color: Colors.white, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/auth/view/widgets/login_form.dart b/lib/features/auth/view/widgets/login_form.dart new file mode 100644 index 0000000..6493800 --- /dev/null +++ b/lib/features/auth/view/widgets/login_form.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/auth/view/widgets/forget_password.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/styles_manager.dart'; + +class LoginForm extends StatelessWidget { + const LoginForm({ + super.key, + }); + + @override + Widget build(BuildContext context) { + var pressed = false; + return BlocBuilder( + builder: (context, state) { + final formKey = AuthCubit.get(context).loginFormKey; + return Form( + key: formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: "Email", + fontColor: Colors.white, + ), + TextFormField( + autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + autofillHints: const [AutofillHints.email], + controller: AuthCubit.get(context).emailController, + validator: (value) { + if (state is AuthTokenError && !pressed) { + return null; + } + return AuthCubit.get(context).emailAddressValidator(value); + }, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + onChanged: (value) {}, + decoration: defaultInputDecoration(context, hint: "Example@email.com"), + ), + const SizedBox(height: 10), + const BodyMedium( + text: "Password", + fontColor: Colors.white, + ), + TextFormField( + autovalidateMode: AutovalidateMode.disabled, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + scrollPadding: EdgeInsets.zero, + autocorrect: false, + autofillHints: const [AutofillHints.password], + controller: AuthCubit.get(context).passwordController, + validator: (value) { + if (state is AuthTokenError && !pressed) { + return null; + } + // return AuthCubit.get(context).passwordValidator(value); + }, + onTapOutside: (event) { + FocusScope.of(context).unfocus(); + }, + obscureText: !AuthCubit.get(context).isPasswordVisible, + decoration: defaultInputDecoration(context, hint: "At least 8 characters"), + ), + const SizedBox(height: 10), + // const LoginUserAgreement(), + const ForgetPassword(), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: DefaultButton( + isDone: state is AuthLoginSuccess, + isLoading: state is AuthLoading, + customButtonStyle: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + Colors.black.withOpacity(.25), + ), + foregroundColor: WidgetStateProperty.all( + Colors.white, + ), + ), + child: const Text( + 'Login', + ), + onPressed: () { + pressed = true; + if (formKey.currentState!.validate()) { + if ((state is! AuthLoading)) { + AuthCubit.get(context).login(); + FocusScope.of(context).unfocus(); + } + } + }, + ), + ), + ], + ) + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/auth/view/widgets/login_user_agreement.dart b/lib/features/auth/view/widgets/login_user_agreement.dart new file mode 100644 index 0000000..a4e7ee8 --- /dev/null +++ b/lib/features/auth/view/widgets/login_user_agreement.dart @@ -0,0 +1,74 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class LoginUserAgreement extends StatelessWidget { + const LoginUserAgreement({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Row( + children: [ + Checkbox( + value: AuthCubit.get(context).agreeToTerms, + onChanged: (value) => AuthCubit.get(context).changeAgreeToTerms(), + ), + Expanded( + child: RichText( + softWrap: true, + maxLines: 2, + text: TextSpan( + text: 'I Agree to the ', + style: context.bodySmall, + children: [ + TextSpan( + text: 'Privacy Policy', + style: context.bodySmall.copyWith( + color: Colors.blue, + decoration: TextDecoration.underline, + decorationColor: Colors.blue, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.pushNamed(context, Routes.policyRoute); + }, + ), + TextSpan( + text: ' and ', + style: context.bodySmall, + ), + TextSpan( + text: 'User Agreement', + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.pushNamed(context, Routes.termsRoute); + }, + style: const TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + decorationColor: Colors.blue, + ), + ), + const TextSpan( + text: '.', + style: TextStyle( + color: Colors.black, + ), + ), + ], + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/features/auth/view/widgets/login_with_google_facebook.dart b/lib/features/auth/view/widgets/login_with_google_facebook.dart new file mode 100644 index 0000000..b7e2394 --- /dev/null +++ b/lib/features/auth/view/widgets/login_with_google_facebook.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class LoginWithGoogleFacebook extends StatelessWidget { + const LoginWithGoogleFacebook({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: DefaultContainer( + child: SizedBox.square( + dimension: 24, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.assetsIconsGoogle), + const SizedBox(width: 10), + BodyMedium( + text: "Google", + style: context.bodyMedium.copyWith( + color: Colors.black, + ), + ), + ], + ), + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultContainer( + child: SizedBox.square( + dimension: 24, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.assetsIconsFacebook), + const SizedBox(width: 10), + BodyMedium( + text: "Facebook", + style: context.bodyMedium.copyWith(color: Colors.black), + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/features/dashboard/bloc/dashboard_cubit.dart b/lib/features/dashboard/bloc/dashboard_cubit.dart new file mode 100644 index 0000000..da13e50 --- /dev/null +++ b/lib/features/dashboard/bloc/dashboard_cubit.dart @@ -0,0 +1,9 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'dashboard_state.dart'; + +class DashboardCubit extends Cubit { + DashboardCubit() : super(DashboardInitial()); + + static DashboardCubit of(context) => BlocProvider.of(context); +} diff --git a/lib/features/dashboard/bloc/dashboard_state.dart b/lib/features/dashboard/bloc/dashboard_state.dart new file mode 100644 index 0000000..12e988c --- /dev/null +++ b/lib/features/dashboard/bloc/dashboard_state.dart @@ -0,0 +1,5 @@ +part of 'dashboard_cubit.dart'; + +abstract class DashboardState {} + +class DashboardInitial extends DashboardState {} diff --git a/lib/features/dashboard/view/dashboard_view.dart b/lib/features/dashboard/view/dashboard_view.dart new file mode 100644 index 0000000..601552b --- /dev/null +++ b/lib/features/dashboard/view/dashboard_view.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/carbon_emission.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/consumption.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/live_monitor_tab.dart'; +import 'package:syncrow_app/features/shared_widgets/create_unit.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +import 'widgets/energy_usage.dart'; + +class DashboardView extends StatelessWidget { + const DashboardView({super.key}); + + @override + Widget build(BuildContext context) { + return HomeCubit.getInstance().spaces?.isEmpty ?? true + ? const CreateUnitWidget() + : SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const TitleMedium( + text: StringsManager.dashboard, + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + ), + ), + const LiveMonitorTab(), + const SizedBox(height: 10), + const EnergyUsage(), + Container( + padding: const EdgeInsets.only(top: 20), + constraints: const BoxConstraints( + minHeight: 220, + maxHeight: 240, + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Consumption(), + SizedBox(height: 20), + CarbonEmission(), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/dashboard/view/widgets/carbon_emission.dart b/lib/features/dashboard/view/widgets/carbon_emission.dart new file mode 100644 index 0000000..4823210 --- /dev/null +++ b/lib/features/dashboard/view/widgets/carbon_emission.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/card_title.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/features/shared_widgets/united_text.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class CarbonEmission extends StatelessWidget { + const CarbonEmission({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.only(right: 20, left: 20, top: 10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + constraints: const BoxConstraints( + minHeight: 80, + maxHeight: 100, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CardTitle( + title: "Carbon Emission", + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox.square( + dimension: 30, + child: SvgPicture.asset( + Assets.assetsIconsCO2, + fit: BoxFit.contain, + ), + ), + const SizedBox(height: 5), + const Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodySmall( + text: StringsManager.emissions, + ), + UnitedText( + value: '120.00', + valueSize: 14, + unit: 'kg', + unitSize: 10, + ), + ], + ), + ], + ), + const SizedBox(width: 20), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox.square( + dimension: 30, + child: SvgPicture.asset( + Assets.assetsIconsSustainability, + fit: BoxFit.contain, + ), + ), + const SizedBox(width: 5), + const Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodySmall( + text: StringsManager.reductions, + ), + UnitedText( + value: '20.00', + valueSize: 14, + unit: 'kg', + unitSize: 10, + ), + ], + ), + ], + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/dashboard/view/widgets/card_title.dart b/lib/features/dashboard/view/widgets/card_title.dart new file mode 100644 index 0000000..4e64e7c --- /dev/null +++ b/lib/features/dashboard/view/widgets/card_title.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class CardTitle extends StatelessWidget { + const CardTitle({ + super.key, + required this.title, + }); + + final String title; + + @override + Widget build(BuildContext context) { + return BodySmall( + text: title, + fontColor: Colors.grey, + fontSize: MediaQuery.sizeOf(context).height.ceil() > 680 ? 12 : 8, + ); + } +} diff --git a/lib/features/dashboard/view/widgets/consumption.dart b/lib/features/dashboard/view/widgets/consumption.dart new file mode 100644 index 0000000..0e39498 --- /dev/null +++ b/lib/features/dashboard/view/widgets/consumption.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/card_title.dart'; +import 'package:syncrow_app/features/shared_widgets/united_text.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +import 'package:syncrow_app/generated/assets.dart'; + +class Consumption extends StatelessWidget { + const Consumption({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.only(right: 20, left: 20, top: 10, bottom: 10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + constraints: const BoxConstraints( + minHeight: 80, + maxHeight: 100, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CardTitle( + title: StringsManager.ACConsumption, + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + UnitedText( + value: "2", + valueSize: MediaQuery.sizeOf(context).height.ceil() > 680 + ? 35 + : 24, + valueWeight: FontWeight.normal, + unit: "Units", + ), + const SizedBox(width: 30), + UnitedText( + value: "${MediaQuery.sizeOf(context).height.ceil()}", + valueSize: MediaQuery.sizeOf(context).height.ceil() > 680 + ? 35 + : 24, + valueWeight: FontWeight.normal, + unit: "kWh", + ), + ], + ), + const Spacer(), + ], + ), + //TODO: Replace with actual pie chart + SizedBox.square( + dimension: 60, + child: Image.asset( + Assets.assetsImagesTestDash2, + fit: BoxFit.contain, + ), + ) + ], + ), + ); + } +} diff --git a/lib/features/dashboard/view/widgets/energy_usage.dart b/lib/features/dashboard/view/widgets/energy_usage.dart new file mode 100644 index 0000000..9c4dd1a --- /dev/null +++ b/lib/features/dashboard/view/widgets/energy_usage.dart @@ -0,0 +1,178 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/energy_usage_header.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class EnergyUsage extends StatelessWidget { + const EnergyUsage({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const EnergyUsageHeader(), + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 150, + minHeight: 150, + ), + child: LineChart( + LineChartData( + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + horizontalInterval: 2, + drawVerticalLine: false, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(.5), + strokeWidth: 1, + ); + }, + ), + titlesData: FlTitlesData( + show: true, + rightTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, + getTitlesWidget: leftTitleWidgets, + reservedSize: 25, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + interval: 12, + getTitlesWidget: (value, meta) { + switch (value.toInt()) { + case 0: + return SideTitleWidget( + axisSide: meta.axisSide, + child: const BodySmall(text: '1'), + ); + + case 11: + return SideTitleWidget( + axisSide: meta.axisSide, + child: const BodySmall(text: '28'), + ); + default: + return Container(); + } + }, + ), + ), + leftTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + minX: 0, + maxX: 11, + minY: 0, + maxY: 6, + lineBarsData: [ + LineChartBarData( + spots: const [ + FlSpot(0, 3), + FlSpot(2.6, 2), + FlSpot(4.9, 5), + FlSpot(6.8, 3.1), + FlSpot(8, 4), + FlSpot(9.5, 3), + FlSpot(11, 4), + ], + isCurved: true, + gradient: LinearGradient( + colors: [ + ColorsManager.primaryColor, + ColorsManager.primaryColor.withOpacity(0.3), + ], + ), + barWidth: 5, + isStrokeCapRound: true, + dotData: const FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + ColorsManager.primaryColor.withOpacity(0.5), + ColorsManager.primaryColor.withOpacity(0.1), + ], + ), + ), + ), + ], + ), + ), + ) + ], + ), + ), + ); + } + + Widget leftTitleWidgets(double value, TitleMeta meta) { + String text; + switch (value.toInt()) { + case 1: + text = '1K'; + break; + case 3: + text = '3k'; + break; + case 5: + text = '5k'; + break; + default: + return Container(); + } + + return Center(child: BodySmall(text: text)); + } + + Widget bottomTitleWidgets(double value, TitleMeta meta) { + // const style = TextStyle( + // fontWeight: FontWeight.bold, + // fontSize: 16, + // ); + // Widget text; + // switch (value.toInt()) { + // case 2: + // text = const Text('MAR', style: style); + // break; + // case 5: + // text = const Text('JUN', style: style); + // break; + // case 8: + // text = const Text('SEP', style: style); + // break; + // default: + // text = const Text('', style: style); + // break; + // } + + return SideTitleWidget( + axisSide: meta.axisSide, + child: const BodySmall(text: 'Feb'), + ); + } +} diff --git a/lib/features/dashboard/view/widgets/energy_usage_header.dart b/lib/features/dashboard/view/widgets/energy_usage_header.dart new file mode 100644 index 0000000..ddd9a09 --- /dev/null +++ b/lib/features/dashboard/view/widgets/energy_usage_header.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/card_title.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/features/shared_widgets/united_text.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class EnergyUsageHeader extends StatelessWidget { + const EnergyUsageHeader({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CardTitle( + title: StringsManager.energyUsage, + ), + Padding( + padding: EdgeInsets.only(top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodySmall( + text: StringsManager.totalConsumption, + ), + BodySmall( + text: "JAN 2024", + fontSize: 12, + fontColor: Colors.grey, + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + UnitedText( + value: "1200.00", + valueStyle: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + height: 0, + ), + unit: "kWh", + ), + UnitedText( + value: "430", + valueStyle: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + unit: "AED", + unitStyle: TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ) + ], + ), + ], + ), + ) + ], + ); + } +} diff --git a/lib/features/dashboard/view/widgets/live_monitor_tab.dart b/lib/features/dashboard/view/widgets/live_monitor_tab.dart new file mode 100644 index 0000000..71de05a --- /dev/null +++ b/lib/features/dashboard/view/widgets/live_monitor_tab.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/dashboard/view/widgets/live_monitor_widget.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +import 'package:syncrow_app/generated/assets.dart'; + +class LiveMonitorTab extends StatelessWidget { + const LiveMonitorTab({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return const Row( + children: [ + Expanded( + child: LiveMonitorWidget( + image: Assets.assetsIconsActive, + title: StringsManager.active, + value: '10.00w', + ), + ), + Expanded( + child: LiveMonitorWidget( + image: Assets.assetsIconsVoltMeter, + title: StringsManager.current, + value: '12.1 A', + ), + ), + Expanded( + child: LiveMonitorWidget( + image: Assets.assetsIconsFrequency, + title: StringsManager.frequency, + value: '50 Hz', + ), + ), + ], + ); + } +} diff --git a/lib/features/dashboard/view/widgets/live_monitor_widget.dart b/lib/features/dashboard/view/widgets/live_monitor_widget.dart new file mode 100644 index 0000000..87a97b8 --- /dev/null +++ b/lib/features/dashboard/view/widgets/live_monitor_widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class LiveMonitorWidget extends StatelessWidget { + const LiveMonitorWidget({ + super.key, + required this.image, + required this.title, + required this.value, + }); + + final String image; + final String title; + final String value; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + ), + padding: const EdgeInsets.only(left: 10, top: 5, bottom: 5), + margin: const EdgeInsets.only(right: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 23, + width: 20, + child: SvgPicture.asset( + image, + fit: BoxFit.contain, + ), + ), + const SizedBox(width: 5), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + BodySmall( + text: title, + style: const TextStyle(fontSize: 10, color: Colors.grey)), + BodyMedium( + text: value, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/devices/bloc/acs_bloc/acs_bloc.dart b/lib/features/devices/bloc/acs_bloc/acs_bloc.dart new file mode 100644 index 0000000..c8a8350 --- /dev/null +++ b/lib/features/devices/bloc/acs_bloc/acs_bloc.dart @@ -0,0 +1,399 @@ +import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/ac_model.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class ACsBloc extends Bloc { + final String acId; + AcStatusModel deviceStatus = AcStatusModel( + uuid: '', + acSwitch: true, + modeString: 'hot', + tempSet: 300, + currentTemp: 315, + fanSpeedsString: 'low', + childLock: false); + List deviceStatusList = []; + List devicesList = []; + bool allAcsPage = false; + bool allAcsOn = true; + bool allTempSame = true; + int globalTemp = 25; + Timer? _timer; + + ACsBloc({required this.acId}) : super(AcsInitialState()) { + on(_fetchAcsStatus); + on(_changeAcSwitch); + on(_increaseCoolTo); + on(_decreaseCoolTo); + on(_changeLockValue); + on(_changeAcMode); + on(_changeFanSpeed); + on(_changeAllAcSwitch); + on(_increaseAllTemp); + on(_decreaseAllTemp); + on(_onAcUpdated); + } + + void _fetchAcsStatus(AcsInitial event, Emitter emit) async { + emit(AcsLoadingState()); + try { + allAcsPage = event.allAcs; + if (event.allAcs) { + await _getAllAcs(); + emit(GetAllAcsStatusState( + allAcsStatues: deviceStatusList, + allAcs: devicesList, + allOn: allAcsOn, + allTempSame: allTempSame, + temp: globalTemp)); + } else { + var response = await DevicesAPI.getDeviceStatus(acId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = AcStatusModel.fromJson(response['productUuid'], statusModelList); + emit(GetAcStatusState(acStatusModel: deviceStatus)); + Future.delayed(const Duration(milliseconds: 500)); + _listenToChanges(); + } + } catch (e) { + emit(AcsFailedState(errorMessage: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$acId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = event.snapshot.value as Map; + List statusList = []; + + usersMap['status'].forEach((element) { + statusList.add(StatusModel(code: element['code'], value: element['value'])); + }); + + deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList); + add(AcUpdated()); + }); + } catch (_) {} + } + + _onAcUpdated(AcUpdated event, Emitter emit) { + emit(GetAcStatusState(acStatusModel: deviceStatus)); + } + + _getAllAcs() async { + deviceStatusList = []; + devicesList = []; + devicesList = await DevicesAPI.getDeviceByGroupName( + HomeCubit.getInstance().selectedSpace?.id ?? '', 'AC'); + + for (int i = 0; i < devicesList.length; i++) { + var response = await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatusList.add(AcStatusModel.fromJson(response['productUuid'], statusModelList)); + } + _setAllAcsTempsAndSwitches(); + } + + void _changeAcSwitch(AcSwitch event, Emitter emit) async { + final acSwitchValue = !event.acSwitch; + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.acSwitch = acSwitchValue; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.acSwitch = acSwitchValue; + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'switch', value: acSwitchValue); + } + + void _changeAllAcSwitch(ChangeAllSwitch event, Emitter emit) async { + emit(AcsLoadingState()); + if (deviceStatusList.length == devicesList.length) { + for (int i = 0; i < deviceStatusList.length; i++) { + deviceStatusList[i].acSwitch = event.value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + _runDeBouncerForAllAcs(code: 'switch', value: event.value); + } + + void _increaseAllTemp(IncreaseAllTemp event, Emitter emit) async { + emit(AcsLoadingState()); + double tempValue = event.value + 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (deviceStatusList.length == devicesList.length) { + for (int i = 0; i < deviceStatusList.length; i++) { + deviceStatusList[i].tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + _runDeBouncerForAllAcs(code: 'temp_set', value: value); + } + + void _decreaseAllTemp(DecreaseAllTemp event, Emitter emit) async { + emit(AcsLoadingState()); + + double tempValue = event.value - 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (deviceStatusList.length == devicesList.length) { + for (int i = 0; i < deviceStatusList.length; i++) { + deviceStatusList[i].tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + _runDeBouncerForAllAcs(code: 'temp_set', value: value); + } + + void _changeLockValue(ChangeLock event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + final lockValue = !event.lockBool; + deviceStatus.childLock = lockValue; + emit(AcModifyingState(acStatusModel: deviceStatus)); + + await _runDeBouncerForOneDevice(deviceId: acId, code: 'child_lock', value: lockValue); + } + + void _increaseCoolTo(IncreaseCoolToTemp event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + double tempValue = event.value + 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.tempSet = value; + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'temp_set', value: value); + } + + void _decreaseCoolTo(DecreaseCoolToTemp event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + double tempValue = event.value - 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.tempSet = value; + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'temp_set', value: value); + } + + void _changeAcMode(ChangeAcMode event, Emitter emit) async { + final tempMode = tempModesMap[getNextItem(tempModesMap, event.tempModes)]!; + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.modeString = getACModeString(tempMode); + ac.acMode = AcStatusModel.getACMode(getACModeString(tempMode)); + } + } + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.modeString = getACModeString(tempMode); + deviceStatus.acMode = AcStatusModel.getACMode(getACModeString(tempMode)); + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice( + deviceId: event.deviceId, code: 'mode', value: getACModeString(tempMode)); + } + + void _changeFanSpeed(ChangeFanSpeed event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + final fanSpeed = event.fanSpeeds; + + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.fanSpeedsString = getNextFanSpeedKey(fanSpeed); + ac.acFanSpeed = AcStatusModel.getFanSpeed(getNextFanSpeedKey(fanSpeed)); + } + } + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.fanSpeedsString = getNextFanSpeedKey(fanSpeed); + deviceStatus.acFanSpeed = AcStatusModel.getFanSpeed(getNextFanSpeedKey(fanSpeed)); + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice( + deviceId: event.deviceId, code: 'level', value: getNextFanSpeedKey(fanSpeed)); + } + + String getACModeString(TempModes value) { + if (value == TempModes.cold) { + return 'cold'; + } else if (value == TempModes.hot) { + return 'hot'; + } else if (value == TempModes.wind) { + return 'wind'; + } else { + return 'cold'; + } + } + + void _setAllAcsTempsAndSwitches() { + allAcsOn = true; + allTempSame = true; + if (deviceStatusList.isNotEmpty) { + int temp = deviceStatusList[0].tempSet; + deviceStatusList.firstWhere((element) { + if (!element.acSwitch) { + allAcsOn = false; + } + if (element.tempSet != temp) { + allTempSame = false; + } + + return true; + }); + if (allTempSame) { + globalTemp = temp; + } + } + } + + _runDeBouncerForAllAcs({required String code, required dynamic value}) { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + if (deviceStatusList.length == devicesList.length) { + for (int i = 0; i < deviceStatusList.length; i++) { + try { + await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: devicesList[i].uuid, code: code, value: value), + devicesList[i].uuid ?? ''); + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const AcsInitial(allAcs: true)); + } + } + } + }); + } + + _runDeBouncerForOneDevice({ + required String deviceId, + required String code, + required dynamic value, + }) { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: allAcsPage ? deviceId : acId, code: code, value: value), + allAcsPage ? deviceId : acId); + + if (!response['success']) { + add(AcsInitial(allAcs: allAcsPage)); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(AcsInitial(allAcs: allAcsPage)); + } + }); + } + + bool _checkTemperatureValue(double value, Emitter emit) { + if (value >= 20 && value <= 30) { + return true; + } else { + emit(const AcsFailedState(errorMessage: 'The temperature must be between 20 and 30')); + emit(GetAllAcsStatusState( + allAcsStatues: deviceStatusList, + allAcs: devicesList, + allOn: allAcsOn, + allTempSame: allTempSame, + temp: globalTemp)); + return false; + } + } + + _emitAcsStatus(Emitter emit) { + emit(GetAllAcsStatusState( + allAcsStatues: deviceStatusList, + allAcs: devicesList, + allOn: allAcsOn, + allTempSame: allTempSame, + temp: globalTemp)); + } +} diff --git a/lib/features/devices/bloc/acs_bloc/acs_event.dart b/lib/features/devices/bloc/acs_bloc/acs_event.dart new file mode 100644 index 0000000..6a8eb12 --- /dev/null +++ b/lib/features/devices/bloc/acs_bloc/acs_event.dart @@ -0,0 +1,106 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +abstract class AcsEvent extends Equatable { + const AcsEvent(); + + @override + List get props => []; +} + +class AcsLoading extends AcsEvent {} + +class AcSwitch extends AcsEvent { + final bool acSwitch; + final String deviceId; + final String productId; + const AcSwitch({required this.acSwitch, this.deviceId = '', this.productId = ''}); + + @override + List get props => [acSwitch, deviceId, productId]; +} + +class AcUpdated extends AcsEvent {} + +class AcsInitial extends AcsEvent { + final bool allAcs; + const AcsInitial({required this.allAcs}); + @override + List get props => [allAcs]; +} + +class ACsChangeStatus extends AcsEvent {} + +class IncreaseCoolToTemp extends AcsEvent { + final double value; + final String deviceId; + final String productId; + const IncreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''}); + + @override + List get props => [value, deviceId]; +} + +class DecreaseCoolToTemp extends AcsEvent { + final double value; + final String deviceId; + final String productId; + + const DecreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''}); + + @override + List get props => [value, deviceId]; +} + +class ChangeAcMode extends AcsEvent { + final TempModes tempModes; + final String deviceId; + final String productId; + const ChangeAcMode({required this.tempModes, this.deviceId = '', this.productId = ''}); + + @override + List get props => [tempModes, deviceId, productId]; +} + +class ChangeFanSpeed extends AcsEvent { + final FanSpeeds fanSpeeds; + final String deviceId; + final String productId; + + const ChangeFanSpeed({required this.fanSpeeds, this.deviceId = '', this.productId = ''}); + + @override + List get props => [fanSpeeds, deviceId, productId]; +} + +class ChangeLock extends AcsEvent { + final bool lockBool; + const ChangeLock({required this.lockBool}); + + @override + List get props => [lockBool]; +} + +class ChangeAllSwitch extends AcsEvent { + final bool value; + const ChangeAllSwitch({required this.value}); + + @override + List get props => [value]; +} + +class IncreaseAllTemp extends AcsEvent { + final double value; + const IncreaseAllTemp({required this.value}); + + @override + List get props => [value]; +} + +class DecreaseAllTemp extends AcsEvent { + final double value; + const DecreaseAllTemp({required this.value}); + + @override + List get props => [value]; +} diff --git a/lib/features/devices/bloc/acs_bloc/acs_state.dart b/lib/features/devices/bloc/acs_bloc/acs_state.dart new file mode 100644 index 0000000..231da9a --- /dev/null +++ b/lib/features/devices/bloc/acs_bloc/acs_state.dart @@ -0,0 +1,65 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/ac_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; + +abstract class AcsState extends Equatable { + const AcsState(); + + @override + List get props => []; +} + +class AcsInitialState extends AcsState {} + +class AcsLoadingState extends AcsState {} + +class AcChangeLoading extends AcsState { + final AcStatusModel acStatusModel; + const AcChangeLoading({required this.acStatusModel}); + + @override + List get props => [acStatusModel]; +} + +class AcModifyingState extends AcsState { + final AcStatusModel acStatusModel; + const AcModifyingState({required this.acStatusModel}); + + @override + List get props => [acStatusModel]; +} + +class GetAcStatusState extends AcsState { + final AcStatusModel acStatusModel; + const GetAcStatusState({required this.acStatusModel}); + + @override + List get props => [acStatusModel]; +} + +class GetAllAcsStatusState extends AcsState { + final List allAcsStatues; + final List allAcs; + final bool allOn; + final bool allTempSame; + final int temp; + + const GetAllAcsStatusState( + {required this.allAcsStatues, + required this.allAcs, + required this.allOn, + required this.allTempSame, + required this.temp}); + + @override + List get props => [allAcsStatues, allAcs, allAcs, allTempSame, temp]; +} + +class AcsFailedState extends AcsState { + final String errorMessage; + + const AcsFailedState({required this.errorMessage}); + + @override + List get props => [errorMessage]; +} diff --git a/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_bloc.dart b/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_bloc.dart new file mode 100644 index 0000000..536df9b --- /dev/null +++ b/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_bloc.dart @@ -0,0 +1,74 @@ +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart'; +import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart'; +import 'package:syncrow_app/features/devices/model/ceiling_sensor_model.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; + +class CeilingSensorBloc extends Bloc { + final String deviceId; + late DeviceModel deviceModel; + late CeilingSensorModel deviceStatus; + + CeilingSensorBloc({required this.deviceId}) : super(InitialState()) { + on(_fetchCeilingSensorStatus); + on(_changeValue); + on(_onCeilingSensorUpdated); + } + + void _fetchCeilingSensorStatus(InitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + var response = await DevicesAPI.getDeviceStatus(deviceId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = CeilingSensorModel.fromJson(statusModelList); + emit(UpdateState(ceilingSensorModel: deviceStatus)); + _listenToChanges(); + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = event.snapshot.value as Map; + List statusList = []; + + usersMap['status'].forEach((element) { + statusList.add(StatusModel(code: element['code'], value: element['value'])); + }); + + deviceStatus = CeilingSensorModel.fromJson(statusList); + add(CeilingSensorUpdated()); + }); + } catch (_) {} + } + + _onCeilingSensorUpdated(CeilingSensorUpdated event, Emitter emit) { + emit(UpdateState(ceilingSensorModel: deviceStatus)); + } + + void _changeValue(ChangeValueEvent event, Emitter emit) async { + emit(LoadingNewSate(ceilingSensorModel: deviceStatus)); + try { + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: deviceId, code: event.code, value: event.value), deviceId); + + if (response['success'] ?? false) { + deviceStatus.sensitivity = event.value; + } + } catch (_) {} + emit(UpdateState(ceilingSensorModel: deviceStatus)); + } +} diff --git a/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart b/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart new file mode 100644 index 0000000..f245d75 --- /dev/null +++ b/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart @@ -0,0 +1,23 @@ +import 'package:equatable/equatable.dart'; + +abstract class CeilingSensorEvent extends Equatable { + const CeilingSensorEvent(); + + @override + List get props => []; +} + +class LoadingEvent extends CeilingSensorEvent {} + +class InitialEvent extends CeilingSensorEvent {} + +class CeilingSensorUpdated extends CeilingSensorEvent {} + +class ChangeValueEvent extends CeilingSensorEvent { + final int value; + final String code; + const ChangeValueEvent({required this.value, required this.code}); + + @override + List get props => [value, code]; +} diff --git a/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart b/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart new file mode 100644 index 0000000..e48056a --- /dev/null +++ b/lib/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart @@ -0,0 +1,38 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/ceiling_sensor_model.dart'; + +class CeilingSensorState extends Equatable { + const CeilingSensorState(); + + @override + List get props => []; +} + +class InitialState extends CeilingSensorState {} + +class LoadingInitialState extends CeilingSensorState {} + +class UpdateState extends CeilingSensorState { + final CeilingSensorModel ceilingSensorModel; + const UpdateState({required this.ceilingSensorModel}); + + @override + List get props => [ceilingSensorModel]; +} + +class LoadingNewSate extends CeilingSensorState { + final CeilingSensorModel ceilingSensorModel; + const LoadingNewSate({required this.ceilingSensorModel}); + + @override + List get props => [ceilingSensorModel]; +} + +class FailedState extends CeilingSensorState { + final String error; + + const FailedState({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/features/devices/bloc/curtain_bloc/curtain_bloc.dart b/lib/features/devices/bloc/curtain_bloc/curtain_bloc.dart new file mode 100644 index 0000000..1dea664 --- /dev/null +++ b/lib/features/devices/bloc/curtain_bloc/curtain_bloc.dart @@ -0,0 +1,184 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_event.dart'; +import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_state.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class CurtainBloc extends Bloc { + double curtainWidth = 270; + double curtainOpeningSpace = 195; + double blindHeight = 310; + double blindOpeningSpace = 245; + double openPercentage = 0; + bool isMoving = false; + final String curtainId; + + CurtainBloc( + this.curtainId, + ) : super(CurtainInitial()) { + on(_fetchStatus); + on(_onOpenCurtain); + on(_onCloseCurtain); + on(_onPauseCurtain); + } + + Future _onOpenCurtain( + OpenCurtain event, + Emitter emit) async { + isMoving = true; + while (openPercentage < 100.0) { + if (state is CurtainsClosing) { + _pauseCurtain(emit); + break; + } + emit(CurtainsOpening( + curtainWidth: curtainWidth, + blindHeight: blindHeight, + openPercentage: openPercentage, + )); + if (isMoving) { + await Future.delayed(const Duration(milliseconds: 200), () async { + openPercentage += 10.0; + event.deviceType == DeviceType.Curtain + ? curtainWidth -= curtainOpeningSpace / 10 + : blindHeight -= blindOpeningSpace / 10; + if (openPercentage >= 100.0) { + _pauseCurtain(emit); + } + }); + if (openPercentage >=100.0) { + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: curtainId, + code: 'control', + value: 'close', + ), + curtainId, + ); + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: curtainId, + code: 'percent_control', + value: 100, + ), + curtainId, + ); + } + } else { + _pauseCurtain(emit); + break; + } + } + } + + Future _onCloseCurtain( + CloseCurtain event, Emitter emit) async { + isMoving = true; + while (openPercentage > 0.0) { + if (state is CurtainsOpening) { + _pauseCurtain(emit); + break; + } + emit(CurtainsClosing( + curtainWidth: curtainWidth, + blindHeight: blindHeight, + openPercentage: openPercentage, + )); + if (isMoving) { + await Future.delayed(const Duration(milliseconds: 200), () async { + openPercentage -= 10.0; + event.deviceType == DeviceType.Curtain + ? curtainWidth += curtainOpeningSpace / 10 + : blindHeight += blindOpeningSpace / 10; + if (openPercentage <= 0.0) { + _pauseCurtain(emit); + } + }); + if (openPercentage == 0.0) { + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: curtainId, + code: 'percent_control', + value: 0, + ), + curtainId, + ); + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: curtainId, + code: 'control', + value: 'open', + ), + curtainId, + ); + } + } else { + _pauseCurtain(emit); + break; + } + } + } + + Future _onPauseCurtain( + PauseCurtain event, Emitter emit) async { + _pauseCurtain(emit); + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: curtainId, + code: 'control', + value: 'stop', + ), + curtainId, + ); + await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: curtainId, + code: 'percent_control', + value: openPercentage.ceil(), + ), + curtainId, + ); + } + + Future _pauseCurtain(Emitter emit) async { + isMoving = false; + emit(CurtainsPaused( + curtainWidth: curtainWidth, + blindHeight: blindHeight, + openPercentage: openPercentage, + )); + } + + + + + void _fetchStatus(InitCurtain event, Emitter emit) async { + try { + emit(CurtainLoadingState()); + // Fetch the status from the API + var response = await DevicesAPI.getDeviceStatus(curtainId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + // Get the open percentage from the response + openPercentage = double.tryParse(statusModelList[1].value.toString())!; + // Calculate curtain width and blind height based on the open percentage + if (openPercentage != null) { + curtainWidth = 270 - (openPercentage / 100) * curtainOpeningSpace; + blindHeight = 310 - (openPercentage / 100) * blindOpeningSpace; + } + emit(CurtainsOpening( + curtainWidth: curtainWidth, + blindHeight: blindHeight, + openPercentage: openPercentage, + )); + } catch (e) { + emit(FailedState()); + return; + } + } + +} diff --git a/lib/features/devices/bloc/curtain_bloc/curtain_event.dart b/lib/features/devices/bloc/curtain_bloc/curtain_event.dart new file mode 100644 index 0000000..799946e --- /dev/null +++ b/lib/features/devices/bloc/curtain_bloc/curtain_event.dart @@ -0,0 +1,34 @@ + + + +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +abstract class CurtainEvent extends Equatable { + const CurtainEvent(); + + @override + List get props => []; +} + +class OpenCurtain extends CurtainEvent { + final DeviceType deviceType; + + const OpenCurtain(this.deviceType); + + @override + List get props => [deviceType]; +} + +class CloseCurtain extends CurtainEvent { + final DeviceType deviceType; + + const CloseCurtain(this.deviceType); + + @override + List get props => [deviceType]; +} + +class InitCurtain extends CurtainEvent {} +class PauseCurtain extends CurtainEvent {} +class useCurtainEvent extends CurtainEvent {} \ No newline at end of file diff --git a/lib/features/devices/bloc/curtain_bloc/curtain_state.dart b/lib/features/devices/bloc/curtain_bloc/curtain_state.dart new file mode 100644 index 0000000..fd94ab1 --- /dev/null +++ b/lib/features/devices/bloc/curtain_bloc/curtain_state.dart @@ -0,0 +1,76 @@ +// curtain_state.dart +import 'package:equatable/equatable.dart'; + +abstract class CurtainState extends Equatable { + const CurtainState(); + + @override + List get props => []; +} + +class CurtainInitial extends CurtainState {} + +class UpdateCurtain extends CurtainState { + + final double curtainWidth; + final double blindHeight; + final double openPercentage; + + const UpdateCurtain({ + required this.curtainWidth, + required this.blindHeight, + required this.openPercentage, + }); + + @override + List get props => [curtainWidth, blindHeight, openPercentage]; +} + +class FailedState extends CurtainState {} + +class CurtainLoadingState extends CurtainState {} + +class CurtainsOpening extends CurtainState { + final double curtainWidth; + final double blindHeight; + final double openPercentage; + + const CurtainsOpening({ + required this.curtainWidth, + required this.blindHeight, + required this.openPercentage, + }); + + @override + List get props => [curtainWidth, blindHeight, openPercentage]; +} + +class CurtainsClosing extends CurtainState { + final double curtainWidth; + final double blindHeight; + final double openPercentage; + + const CurtainsClosing({ + required this.curtainWidth, + required this.blindHeight, + required this.openPercentage, + }); + + @override + List get props => [curtainWidth, blindHeight, openPercentage]; +} + +class CurtainsPaused extends CurtainState { + final double curtainWidth; + final double blindHeight; + final double openPercentage; + + const CurtainsPaused({ + required this.curtainWidth, + required this.blindHeight, + required this.openPercentage, + }); + + @override + List get props => [curtainWidth, blindHeight, openPercentage]; +} \ No newline at end of file diff --git a/lib/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart b/lib/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart new file mode 100644 index 0000000..a8b1eb1 --- /dev/null +++ b/lib/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_event.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_state.dart'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; + +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/services/api/home_management_api.dart'; + +class DeviceManagerBloc extends Bloc { + DeviceManagerBloc() : super(DeviceManagerState.initial()) { + on(_onFetchAllDevices); + on(_onFetchDevicesByRoomId); + on(_onSelectCategory); + on(_onUnselectAllCategories); + on(_onSelectDevice); + on(_onChangeCategorySwitchValue); + on(_onTurnOnOffDevice); + on(_onClearCategoriesSelection); + on(_getDeviceFunctions); + } + + static List? allCategories; + + Future _onFetchAllDevices(FetchAllDevices event, Emitter emit) async { + emit(state.copyWith(loading: true)); + try { + final allDevices = await HomeManagementAPI.fetchDevicesByUnitId(); + emit(state.copyWith(devices: allDevices, loading: false)); + } catch (e) { + emit(state.copyWith(error: e.toString(), loading: false)); + } + } + + Future _onFetchDevicesByRoomId( + FetchDevicesByRoomId event, Emitter emit) async { + emit(state.copyWith(loading: true)); + try { + final devices = await DevicesAPI.getDevicesByRoomId(event.roomId); + emit(state.copyWith(devices: devices, loading: false)); + } catch (e) { + emit(state.copyWith(error: e.toString(), loading: false)); + } + } + + void _onSelectCategory(SelectCategory event, Emitter emit) { + for (var i = 0; i < allCategories!.length; i++) { + allCategories![i].isSelected = i == event.index; + } + emit(state.copyWith(categoryChanged: true)); + } + + void _onUnselectAllCategories(UnselectAllCategories event, Emitter emit) { + for (var category in allCategories!) { + category.isSelected = false; + } + emit(state.copyWith(categoryChanged: true)); + } + + void _onSelectDevice(SelectDevice event, Emitter emit) { + for (var category in allCategories!) { + if (category.devices != null) { + for (var device in category.devices!) { + if (device.isSelected) { + category.isSelected = false; + emit(state.copyWith(deviceSelected: true)); + return; + } + } + } + } + event.device.isSelected = !event.device.isSelected; + emit(state.copyWith(deviceSelected: true)); + } + + void _onChangeCategorySwitchValue( + ChangeCategorySwitchValue event, Emitter emit) { + var category = event.category; + if (category.devicesStatus != null) { + category.devicesStatus = !category.devicesStatus!; + if (category.devices != null) { + for (var device in category.devices!) { + device.isOnline = category.devicesStatus; + } + } + } else { + category.devicesStatus = true; + if (category.devices != null) { + for (var device in category.devices!) { + device.isOnline = true; + } + } + } + _updateDevicesStatus(category, emit); + } + + void _onTurnOnOffDevice(TurnOnOffDevice event, Emitter emit) { + var device = event.device; + device.isOnline = !device.isOnline!; + DevicesCategoryModel category = allCategories!.firstWhere((category) { + return category.devices != null && category.devices!.contains(device); + }); + _updateDevicesStatus(category, emit); + } + + void _onClearCategoriesSelection( + ClearCategoriesSelection event, Emitter emit) { + for (var category in allCategories!) { + category.isSelected = false; + if (category.devices != null) { + for (var device in category.devices!) { + device.isSelected = false; + } + } + } + Navigator.popUntil(event.context, (route) => route.isFirst); + emit(state.copyWith(categoryChanged: true)); // Set category changed state + } + + void _updateDevicesStatus(DevicesCategoryModel category, Emitter emit) { + if (category.devices != null && category.devices!.isNotEmpty) { + bool? tempStatus = category.devices![0].isOnline; + for (var device in category.devices!) { + if (device.isOnline != tempStatus) { + category.devicesStatus = null; + break; + } + category.devicesStatus = tempStatus; + } + emit(state.copyWith(categoryChanged: true)); + } + } + + Future _getDeviceFunctions( + DeviceFunctionsEvent event, Emitter emit) async { + emit(state.copyWith(functionsLoading: true)); + try { + final deviceFunctions = await DevicesAPI.deviceFunctions(event.deviceId); + + emit(state.copyWith(functionsLoading: false, deviceFunctions: deviceFunctions)); + } catch (e) { + emit(state.copyWith(functionsLoading: false, error: e.toString())); + } + } +} diff --git a/lib/features/devices/bloc/device_manager_bloc/device_manager_event.dart b/lib/features/devices/bloc/device_manager_bloc/device_manager_event.dart new file mode 100644 index 0000000..867027d --- /dev/null +++ b/lib/features/devices/bloc/device_manager_bloc/device_manager_event.dart @@ -0,0 +1,88 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; + +abstract class DeviceManagerEvent extends Equatable { + const DeviceManagerEvent(); + + @override + List get props => []; +} + +class FetchAllDevices extends DeviceManagerEvent {} + +class FetchDevicesByRoomId extends DeviceManagerEvent { + final String roomId; + + const FetchDevicesByRoomId(this.roomId); + + @override + List get props => [roomId]; +} + +class SelectCategory extends DeviceManagerEvent { + final int index; + + const SelectCategory(this.index); + + @override + List get props => [index]; +} + +class UnselectAllCategories extends DeviceManagerEvent {} + +class SelectDevice extends DeviceManagerEvent { + final DeviceModel device; + + const SelectDevice(this.device); + + @override + List get props => [device]; +} + +class ChangeCategorySwitchValue extends DeviceManagerEvent { + final DevicesCategoryModel category; + + const ChangeCategorySwitchValue(this.category); + + @override + List get props => [category]; +} + +class TurnOnOffDevice extends DeviceManagerEvent { + final DeviceModel device; + + const TurnOnOffDevice(this.device); + + @override + List get props => [device]; +} + +class ClearCategoriesSelection extends DeviceManagerEvent { + final BuildContext context; + + const ClearCategoriesSelection(this.context); + + @override + List get props => [context]; +} + +class DeviceControl extends DeviceManagerEvent { + final DeviceControlModel control; + final String deviceId; + + const DeviceControl(this.control, this.deviceId); + + @override + List get props => [control, deviceId]; +} + +class DeviceFunctionsEvent extends DeviceManagerEvent { + final String deviceId; + const DeviceFunctionsEvent(this.deviceId); + + @override + List get props => [deviceId]; +} diff --git a/lib/features/devices/bloc/device_manager_bloc/device_manager_state.dart b/lib/features/devices/bloc/device_manager_bloc/device_manager_state.dart new file mode 100644 index 0000000..35295a4 --- /dev/null +++ b/lib/features/devices/bloc/device_manager_bloc/device_manager_state.dart @@ -0,0 +1,73 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/function_model.dart'; + +class DeviceManagerState extends Equatable { + final bool loading; + final String? error; + final List? allCategories; + final List? devices; + final bool categoryChanged; + final bool deviceSelected; + final bool functionsLoading; + final FunctionModel? deviceFunctions; + + const DeviceManagerState({ + required this.loading, + this.error, + this.allCategories, + this.devices, + this.categoryChanged = false, + this.deviceSelected = false, + this.functionsLoading = false, + this.deviceFunctions, + }); + + factory DeviceManagerState.initial() { + return const DeviceManagerState( + loading: false, + error: null, + allCategories: null, + devices: null, + categoryChanged: false, + deviceSelected: false, + functionsLoading: false, + deviceFunctions: null, + ); + } + + DeviceManagerState copyWith({ + bool? loading, + String? error, + List? allCategories, + List? devices, + bool? categoryChanged, + bool? deviceSelected, + bool? functionsLoading, + FunctionModel? deviceFunctions, + }) { + return DeviceManagerState( + loading: loading ?? this.loading, + error: error ?? this.error, + allCategories: allCategories ?? this.allCategories, + devices: devices ?? this.devices, + categoryChanged: categoryChanged ?? this.categoryChanged, + deviceSelected: deviceSelected ?? this.deviceSelected, + functionsLoading: functionsLoading ?? this.functionsLoading, + deviceFunctions: deviceFunctions ?? this.deviceFunctions, + ); + } + + @override + List get props => [ + loading, + error, + allCategories, + devices, + categoryChanged, + deviceSelected, + functionsLoading, + deviceFunctions, + ]; +} diff --git a/lib/features/devices/bloc/devices_cubit.dart b/lib/features/devices/bloc/devices_cubit.dart new file mode 100644 index 0000000..48e9ca0 --- /dev/null +++ b/lib/features/devices/bloc/devices_cubit.dart @@ -0,0 +1,408 @@ +// ignore_for_file: constant_identifier_names, unused_import + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_list_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/gateway/gateway_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/lights_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/doors_list_view.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/services/api/home_management_api.dart'; +import 'package:syncrow_app/services/api/network_exception.dart'; +import 'package:syncrow_app/services/api/spaces_api.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +part 'devices_state.dart'; + +class DevicesCubit extends Cubit { + DevicesCubit._() : super(DevicesInitial()) { + if (HomeCubit.getInstance().selectedSpace != null && + HomeCubit.getInstance().spaces!.isNotEmpty) { + // fetchGroups(HomeCubit.getInstance().selectedSpace!.id!); + for (var room in HomeCubit.getInstance().selectedSpace!.rooms!) { + fetchDevicesByRoomId(room.id!); + } + fetchGroups(HomeCubit.getInstance().selectedSpace?.id ?? ''); + } + } + + static DevicesCubit? _instance; + static DevicesCubit getInstance() { + return _instance ??= DevicesCubit._(); + } + + @override + Future close() { + _instance = null; + return super.close(); + } + + void emitSafe(DevicesState newState) { + final cubit = this; + if (!cubit.isClosed) { + cubit.emit(newState); + } + } + + static DevicesCubit get(context) => BlocProvider.of(context); + + List? allCategories; + + selectCategory(int index) { + for (var i = 0; i < allCategories!.length; i++) { + if (i == index) { + allCategories![i].isSelected = true; + } else { + allCategories![i].isSelected = false; + } + } + emitSafe(DevicesCategoryChanged()); + } + + unselectAllCategories() { + for (var category in allCategories!) { + category.isSelected = false; + } + emitSafe(DevicesCategoryChanged()); + } + + Widget? get chosenCategoryView { + if (allCategories != null) { + for (var category in allCategories!) { + if (category.isSelected) { + switch (category.type) { + case DeviceType.AC: + return const ACsView(); + case DeviceType.LightBulb: + return const LightsView(); + case DeviceType.DoorLock: + return const DoorsListView(); + case DeviceType.Curtain: + return const CurtainListView(); + // case DeviceType.ThreeGang: + // return const ThreeGangSwitchesView(); + // case DeviceType.Gateway: + // return const GateWayView(); + default: + return null; + } + } + } + } + return null; + } + + // Getter to retrieve all devices from HomeCubit + List get allDevices { + List devices = []; + if (HomeCubit.getInstance().selectedSpace != null && + HomeCubit.getInstance().selectedSpace!.rooms != null) { + for (var room in HomeCubit.getInstance().selectedSpace!.rooms!) { + if (room.devices != null) { + devices.addAll(room.devices!); + } + } + } + return devices; + } + + // DevicesCategoryModel? get chosenCategory { + // for (var category in allCategories!) { + // if (category.isSelected) { + // return category; + // } + // } + // return null; + // } + + selectDevice(DeviceModel device) { + for (var category in allCategories!) { + if (category.devices != null) { + for (var device in category.devices!) { + if (device.isSelected) { + category.isSelected = false; + emitSafe(DeviceSelected()); + return; + } + } + } + } + device.isSelected = !device.isSelected; + emitSafe(DeviceSelected()); + } + + DeviceModel? getSelectedDevice() { + for (var category in allCategories!) { + if (category.devices != null) { + for (var device in category.devices!) { + if (device.isSelected) { + return device; + } + } + } + } + return null; + } + + changeCategorySwitchValue(DevicesCategoryModel category) { + if (category.devicesStatus != null) { + category.devicesStatus = !category.devicesStatus!; + if (category.devices != null) { + for (var device in category.devices!) { + device.isOnline = category.devicesStatus; + } + } + } else { + category.devicesStatus = true; + if (category.devices != null) { + for (var device in category.devices!) { + device.isOnline = true; + } + } + } + updateDevicesStatus(category); + + emitSafe(CategorySwitchChanged()); + } + + turnOnOffDevice(DeviceModel device) { + device.isOnline = !device.isOnline!; + DevicesCategoryModel category = allCategories!.firstWhere((category) { + if (category.devices != null) { + return category.devices!.contains(device); + } else { + return false; + } + }); + updateDevicesStatus(category); + emitSafe(DeviceSwitchChanged()); + } + + updateDevicesStatus(DevicesCategoryModel category) { + if (category.devices != null) { + if (category.devices!.isNotEmpty) { + bool? tempStatus = category.devices![0].isOnline; + for (var ac in category.devices!) { + //check if there any ac have a different status than the initial ==> turn off the universal switch + if (ac.isOnline != tempStatus) { + category.devicesStatus = null; + emitSafe(DeviceSwitchChanged()); + return; + } + category.devicesStatus = tempStatus; + emitSafe(DeviceSwitchChanged()); + } + } else { + category.devicesStatus = null; + emitSafe(DeviceSwitchChanged()); + } + } + } + + turnAllDevicesOff(DevicesCategoryModel category) { + if (category.devices != null) { + if (category.devices!.isNotEmpty) { + for (var device in category.devices!) { + device.isOnline = false; + } + changeCategorySwitchValue(category); + updateDevicesStatus(category); + emitSafe(CategorySwitchChanged()); + } + } + } + + turnAllDevicesOn(DevicesCategoryModel category) { + if (category.devices != null) { + if (category.devices!.isNotEmpty) { + for (var device in category.devices!) { + device.isOnline = true; + } + changeCategorySwitchValue(category); + updateDevicesStatus(category); + emitSafe(CategorySwitchChanged()); + } + } + } + + areAllDevicesOff(DevicesCategoryModel category) { + if (category.devices != null) { + for (var device in category.devices!) { + if (device.isOnline ?? false) { + category.devicesStatus = false; + emitSafe(CategorySwitchChanged()); + return; + } + } + } + } + + clearCategoriesSelection(BuildContext context) { + for (var category in allCategories!) { + category.isSelected = false; + if (category.devices != null) { + for (var device in category.devices!) { + device.isSelected = false; + } + } + } + Navigator.popUntil(context, (route) => route.isFirst); + + emitSafe(DevicesCategoryChanged()); + } + +///////////////////////// API CALLS ////////////////////////// + deviceControl(DeviceControlModel control, String deviceId) async { + emitSafe(DeviceControlLoading( + code: control.code, + )); + try { + var response = await DevicesAPI.controlDevice(control, deviceId); + + if (response['success'] ?? false) { + emitSafe(DeviceControlSuccess(code: control.code)); + //this delay is to give tuya server time to update the status + Future.delayed(const Duration(milliseconds: 400), () { + fetchDevicesStatues( + deviceId, + HomeCubit.getInstance() + .selectedSpace! + .rooms! + .indexOf(HomeCubit.getInstance().selectedRoom!), + code: control.code); + }); + } else { + emitSafe(DeviceControlError('Failed to control the device')); + } + } catch (failure) { + emitSafe(DeviceControlError(failure.toString())); + return; + } + } + + fetchGroups(String spaceId) async { + emitSafe(DevicesCategoriesLoading()); + try { + allCategories = await DevicesAPI.fetchGroups(spaceId); + } catch (e) { + emitSafe(DevicesCategoriesError(e.toString())); + return; + } + if (allCategories!.isNotEmpty && allCategories != null) { + emitSafe(DevicesCategoriesSuccess()); + } else { + emitSafe(DevicesCategoriesError('No Groups found')); + } + } + + fetchDevicesByRoomId(String? roomId) async { + if (roomId == null) return; + + emitSafe(GetDevicesLoading()); + int roomIndex = + HomeCubit.getInstance().selectedSpace!.rooms!.indexWhere((element) => element.id == roomId); + try { + HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices = + await DevicesAPI.getDevicesByRoomId(roomId); + } catch (e) { + emitSafe(GetDevicesError(e.toString())); + return; + } + final devices = HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices; + emitSafe(GetDevicesSuccess(devices)); + + //get status for each device + //TODO get devices status per page via page controller instead of getting all devices status at once + // List devices = HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices!; + // if (devices.isNotEmpty) { + // for (var device in devices) { + // fetchDevicesStatues(device.uuid!, roomIndex); + // } + // } + } + + fetchDevicesStatues(String deviceUuid, int roomIndex, {String? code}) async { + emitSafe(GetDeviceStatusLoading(code: code)); + int deviceIndex = HomeCubit.getInstance() + .selectedSpace! + .rooms![roomIndex] + .devices! + .indexWhere((element) => element.uuid == deviceUuid); + List statuses = []; + try { + var response = await DevicesAPI.getDeviceStatus(deviceUuid); + for (var status in response['status']) { + statuses.add(StatusModel.fromJson(status)); + } + } catch (e) { + emitSafe(GetDeviceStatusError(e.toString())); + return; + } + HomeCubit.getInstance().selectedSpace!.rooms![roomIndex].devices![deviceIndex].status = + statuses; + emitSafe(GetDeviceStatusSuccess(code: code)); + } + + ///Lights + onHorizontalDragUpdate(DeviceModel light, double dx, double screenWidth) { + double newBrightness = (dx / (screenWidth - 15) * 100); + if (newBrightness > 100) { + newBrightness = 100; + } else if (newBrightness < 0) { + newBrightness = 0; + } + // setBrightness(light, newBrightness); + } + + Map lightModes = { + 0: LightMode.Doze, + 1: LightMode.Relax, + 2: LightMode.Reading, + 3: LightMode.Energizing, + }; + +// setLightingMode(DeviceModel light, LightMode mode) { +// light.lightingMode = +// lightModes.entries.firstWhere((element) => element.value == mode).key; +// emitSafe(LightModeChanged(mode)); +// } +// +// toggleLight(DeviceModel light) { +// light.isOnline != null ? light.isOnline = !light.isOnline! : light.isOnline = true; +// emitSafe(LightToggled(light)); +// } +// +// setColor(DeviceModel light, int color) { +// light.color = color; +// emitSafe(LightColorChanged(color)); +// } +// +// int getBrightness(DeviceModel light) { +// return light.brightness.toInt(); +// } + +// setBrightness(DeviceModel light, double value) { +// value = (value / 5).ceil() * 5; +// if (value != light.brightness) { +// light.brightness = value; +// emitSafe(LightBrightnessChanged(value)); +// } +// } + + +} + +enum LightMode { + Doze, + Relax, + Reading, + Energizing, +} diff --git a/lib/features/devices/bloc/devices_state.dart b/lib/features/devices/bloc/devices_state.dart new file mode 100644 index 0000000..cb23e24 --- /dev/null +++ b/lib/features/devices/bloc/devices_state.dart @@ -0,0 +1,92 @@ +part of 'devices_cubit.dart'; + +@immutable +abstract class DevicesState {} + +class DevicesInitial extends DevicesState {} + +class DevicesLoading extends DevicesState {} + +class DevicesSuccess extends DevicesState {} + +class DevicesFailure extends DevicesState {} + +class ChangeIndex extends DevicesState {} + +// Devices + +class GetDeviceStatusLoading extends DevicesState { + final String? code; + + GetDeviceStatusLoading({this.code}); +} + +class GetDeviceStatusSuccess extends DevicesState { + final String? code; + + GetDeviceStatusSuccess({this.code}); +} + +class GetDeviceStatusError extends DevicesState { + final String errorMsg; + + GetDeviceStatusError(this.errorMsg); +} + +class GetDevicesLoading extends DevicesState {} + +class GetDevicesSuccess extends DevicesState { + GetDevicesSuccess(this.devices); + final List? devices; +} + +class GetDevicesError extends DevicesState { + final String errorMsg; + + GetDevicesError(this.errorMsg); +} + +class DevicesCategoryChanged extends DevicesState {} + +class CategorySwitchChanged extends DevicesState {} + +class DeviceSwitchChanged extends DevicesState {} + +class DeviceSelected extends DevicesState {} + +// Device Control +class DeviceControlLoading extends DevicesState { + final String? code; + + DeviceControlLoading({this.code}); +} + +class DeviceControlSuccess extends DevicesState { + final String? code; + + DeviceControlSuccess({this.code}); +} + +class DeviceControlError extends DevicesState { + final String errorMsg; + + DeviceControlError(this.errorMsg); +} + +// Categories +class DevicesCategoriesLoading extends DevicesState {} + +class DevicesCategoriesSuccess extends DevicesState {} + +class DevicesCategoriesError extends DevicesState { + final String errorMsg; + + DevicesCategoriesError(this.errorMsg); +} + +// Curtains +class CurtainsIsOpening extends DevicesState {} + +class CurtainsIsClosing extends DevicesState {} + +class CurtainsStopped extends DevicesState {} diff --git a/lib/features/devices/bloc/gateway_bloc/gateway_bloc.dart b/lib/features/devices/bloc/gateway_bloc/gateway_bloc.dart new file mode 100644 index 0000000..7154485 --- /dev/null +++ b/lib/features/devices/bloc/gateway_bloc/gateway_bloc.dart @@ -0,0 +1,23 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/gateway_bloc/gateway_event.dart'; +import 'package:syncrow_app/features/devices/bloc/gateway_bloc/gateway_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; + +class GatewayBloc extends Bloc { + GatewayBloc() : super(GatewayInitialState()) { + on(_fetchDevices); + } + + void _fetchDevices(GatewayInitial event, Emitter emit) async { + emit(GatewayLoadingState()); + try { + List devicesList = await DevicesAPI.getDevicesByGatewayId(event.gatewayId); + + emit(UpdateGatewayState(list: devicesList)); + } catch (e) { + emit(ErrorState(message: e.toString())); + return; + } + } +} diff --git a/lib/features/devices/bloc/gateway_bloc/gateway_event.dart b/lib/features/devices/bloc/gateway_bloc/gateway_event.dart new file mode 100644 index 0000000..cdcf715 --- /dev/null +++ b/lib/features/devices/bloc/gateway_bloc/gateway_event.dart @@ -0,0 +1,20 @@ +import 'package:equatable/equatable.dart'; + +abstract class GatewayEvent extends Equatable { + const GatewayEvent(); + + @override + List get props => []; +} + +class GatewayInitial extends GatewayEvent { + final String gatewayId; + const GatewayInitial({required this.gatewayId}); + + @override + List get props => [gatewayId]; +} + +class GatewayLoading extends GatewayEvent {} + +class GatewayUpdateState extends GatewayEvent {} diff --git a/lib/features/devices/bloc/gateway_bloc/gateway_state.dart b/lib/features/devices/bloc/gateway_bloc/gateway_state.dart new file mode 100644 index 0000000..29ac0fb --- /dev/null +++ b/lib/features/devices/bloc/gateway_bloc/gateway_state.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; + +abstract class GatewayState extends Equatable { + const GatewayState(); + + @override + List get props => []; +} + +class GatewayInitialState extends GatewayState {} + +class GatewayLoadingState extends GatewayState {} + +class UpdateGatewayState extends GatewayState { + final List list; + const UpdateGatewayState({required this.list}); + + @override + List get props => [list]; +} + +class ErrorState extends GatewayState { + final String message; + const ErrorState({required this.message}); + + @override + List get props => [message]; +} diff --git a/lib/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart b/lib/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart new file mode 100644 index 0000000..236c623 --- /dev/null +++ b/lib/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart @@ -0,0 +1,495 @@ +import 'dart:math'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/devices/model/offline_password_model.dart'; +import 'package:syncrow_app/features/devices/model/offline_temporary_password.dart'; +import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/features/devices/model/create_temporary_password_model.dart'; +import 'package:syncrow_app/features/devices/model/temporary_password_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/hour_picker_dialog.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SmartDoorBloc extends Bloc { + final String deviceId; + late SmartDoorModel deviceStatus; + bool isSavingPassword = false; + + SmartDoorBloc({required this.deviceId}) : super(InitialState()) { + on(_fetchSmartDoorStatus); + on(_doorLockUpdated); + on(getTemporaryPasswords); + on(getOneTimePasswords); + on(getTimeLimitPasswords); + on(_updateLock); + on(savePassword); + on(toggleRepeat); + on(setStartEndTime); + on(changeTime); + on(generate7DigitNumber); + on(selectTimeOfLinePassword); + on(selectTimeOnlinePassword); + on(deletePassword); + on(generateAndSavePasswordTimeLimited); + on(generateAndSavePasswordOneTime); + on(toggleDaySelection); + on(_renamePassword); + } + + TextEditingController passwordController = TextEditingController(); + TextEditingController passwordNameController = TextEditingController(); + String effectiveTime = 'Select Time'; + String passwordId = ''; + int? effectiveTimeTimeStamp; + String expirationTime = 'Select Time'; + int? expirationTimeTimeStamp; + bool repeat = false; + bool isStartEndTime = true; + DateTime? startTime; + DateTime? endTime; + int unlockRequest = 0; + List? temporaryPasswords = []; + List? oneTimePasswords = []; + List? timeLimitPasswords = []; + + Future generate7DigitNumber(GeneratePasswordEvent event, Emitter emit) async { + emit(LoadingInitialState()); + passwordController.clear(); + Random random = Random(); + int min = 1000000; + int max = 9999999; + passwordController.text = (min + random.nextInt(max - min + 1)).toString(); + emit(GeneratePasswordState()); + return passwordController.text; + } + + Future generateAndSavePasswordOneTime( + GenerateAndSavePasswordOneTimeEvent event, Emitter emit) async { + try { + if (isSavingPassword) return; + isSavingPassword = true; + emit(LoadingInitialState()); + var res = await DevicesAPI.generateOneTimePassword(deviceId: deviceId); + ApiResponse pass = ApiResponse.fromJson(res); + passwordController.text = pass.data.offlineTempPassword; + passwordId = pass.data.offlineTempPasswordId; + + Future.delayed(const Duration(seconds: 1), () { + Clipboard.setData(ClipboardData(text: passwordController.text)); + }); + emit(const GeneratePasswordOneTimestate(generated: true)); + } catch (_) { + emit(FailedState(errorMessage: _.toString())); + } finally { + isSavingPassword = false; + } + } + + void _fetchSmartDoorStatus(InitialEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.getDeviceStatus(deviceId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = SmartDoorModel.fromJson(statusModelList); + emit(UpdateState(smartDoorModel: deviceStatus)); + _listenToChanges(); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = event.snapshot.value as Map; + List statusList = []; + + usersMap['status'].forEach((element) { + statusList.add(StatusModel(code: element['code'], value: element['value'])); + }); + + deviceStatus = SmartDoorModel.fromJson(statusList); + add(DoorLockUpdated()); + }); + } catch (_) {} + } + + _doorLockUpdated(DoorLockUpdated event, Emitter emit) { + unlockRequest = deviceStatus.unlockRequest; + emit(UpdateState(smartDoorModel: deviceStatus)); + } + + void _renamePassword(RenamePasswordEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + await DevicesAPI.renamePass( + name: passwordNameController.text, doorLockUuid: deviceId, passwordId: passwordId); + add(InitialOneTimePassword()); + add(InitialTimeLimitPassword()); + emit(UpdateState(smartDoorModel: deviceStatus)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + void getTemporaryPasswords(InitialPasswordsPage event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.getTemporaryPasswords( + deviceId, + ); + if (response is List) { + temporaryPasswords = response.map((item) => TemporaryPassword.fromJson(item)).toList(); + } else if (response is Map && response.containsKey('data')) { + temporaryPasswords = + (response['data'] as List).map((item) => TemporaryPassword.fromJson(item)).toList(); + } + emit(TemporaryPasswordsLoadedState(temporaryPassword: temporaryPasswords!)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + void getOneTimePasswords(InitialOneTimePassword event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.getOneTimePasswords(deviceId); + if (response is List) { + oneTimePasswords = response.map((item) => OfflinePasswordModel.fromJson(item)).toList(); + } + emit(TemporaryPasswordsLoadedState(temporaryPassword: temporaryPasswords!)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + void getTimeLimitPasswords(InitialTimeLimitPassword event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.getTimeLimitPasswords(deviceId); + if (response is List) { + timeLimitPasswords = response.map((item) => OfflinePasswordModel.fromJson(item)).toList(); + } + + emit(TemporaryPasswordsLoadedState(temporaryPassword: temporaryPasswords!)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + changeTime(ChangeTimeEvent event, Emitter emit) { + if (event.isStartEndTime == true) { + startTime = event.val; + } else { + endTime = event.val; + } + } + + bool toggleRepeat(ToggleRepeatEvent event, Emitter emit) { + emit(LoadingInitialState()); + repeat = !repeat; + emit(IsRepeatState(repeat: repeat)); + return repeat; + } + + bool setStartEndTime(SetStartEndTimeEvent event, Emitter emit) { + emit(LoadingInitialState()); + isStartEndTime = event.val; + emit(IsStartEndState(isStartEndTime: isStartEndTime)); + return isStartEndTime; + } + + void _updateLock(UpdateLockEvent event, Emitter emit) async { + emit(LoadingNewSate(smartDoorModel: deviceStatus)); + try { + // final response = await DevicesAPI.controlDevice( + // DeviceControlModel(deviceId: deviceId, code: 'normal_open_switch', value: !event.value), + // deviceId); + + final response = await DevicesAPI.openDoorLock(deviceId); + + if (response) { + deviceStatus.normalOpenSwitch = !event.value; + } + } catch (_) {} + emit(UpdateState(smartDoorModel: deviceStatus)); + } + + Future selectTimeOfLinePassword(SelectTimeEvent event, Emitter emit) async { + emit(ChangeTimeState()); + final DateTime? picked = await showDatePicker( + context: event.context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime(2101), + ); + if (picked != null) { + final TimeOfDay? timePicked = await showHourPicker( + context: event.context, + initialTime: TimeOfDay.now(), + ); + if (timePicked != null) { + final selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + timePicked.hour, + 0, + ); + final selectedTimestamp = DateTime( + selectedDateTime.year, + selectedDateTime.month, + selectedDateTime.day, + selectedDateTime.hour, + selectedDateTime.minute, + ).millisecondsSinceEpoch ~/ + 1000; // Divide by 1000 to remove milliseconds + if (event.isEffective) { + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + } else { + effectiveTime = + selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + effectiveTimeTimeStamp = selectedTimestamp; + } + } else { + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar( + 'Expiration Time cannot be earlier than Effective Time.'); + } else { + expirationTime = + selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + expirationTimeTimeStamp = selectedTimestamp; + } + } + emit(TimeSelectedState()); + } + } + } + + Future selectTimeOnlinePassword( + SelectTimeOnlinePasswordEvent event, Emitter emit) async { + emit(ChangeTimeState()); + final DateTime? picked = await showDatePicker( + context: event.context, + initialDate: DateTime.now(), + firstDate: DateTime(2015, 8), + lastDate: DateTime(2101), + ); + if (picked != null) { + final TimeOfDay? timePicked = await showTimePicker( + context: event.context, + initialTime: TimeOfDay.now(), + builder: (context, child) { + return Theme( + data: ThemeData.light().copyWith( + colorScheme: const ColorScheme.light( + primary: ColorsManager.primaryColor, + onSurface: Colors.black, + ), + buttonTheme: const ButtonThemeData( + colorScheme: ColorScheme.light( + primary: Colors.green, + ), + ), + ), + child: child!, + ); + }, + ); + if (timePicked != null) { + final selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + timePicked.hour, + timePicked.minute, + ); + final selectedTimestamp = DateTime( + selectedDateTime.year, + selectedDateTime.month, + selectedDateTime.day, + selectedDateTime.hour, + selectedDateTime.minute, + ).millisecondsSinceEpoch ~/ + 1000; // Divide by 1000 to remove milliseconds + if (event.isEffective) { + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + } else { + effectiveTime = + selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + effectiveTimeTimeStamp = selectedTimestamp; + } + } else { + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar( + 'Expiration Time cannot be earlier than Effective Time.'); + } else { + expirationTime = + selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + expirationTimeTimeStamp = selectedTimestamp; + } + } + emit(TimeSelectedState()); + } + } + } + + Future savePassword(SavePasswordEvent event, Emitter emit) async { + if (_validateInputs() || isSavingPassword) return; + try { + isSavingPassword = true; + emit(LoadingSaveState()); + await DevicesAPI.createPassword( + deviceId: deviceId, + effectiveTime: effectiveTimeTimeStamp.toString(), + invalidTime: expirationTimeTimeStamp.toString(), + name: passwordNameController.text, + password: passwordController.text, + scheduleList: [ + if (repeat) + Schedule( + effectiveTime: getTimeOnly(startTime), + invalidTime: getTimeOnly(endTime).toString(), + workingDay: selectedDays, + ), + ], + ); + Navigator.of(event.context).pop(true); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + } catch (_) { + } finally { + isSavingPassword = false; + } + } + + Future generateAndSavePasswordTimeLimited( + GenerateAndSavePasswordTimeLimitEvent event, Emitter emit) async { + if (timeLimitValidate() || isSavingPassword) return; + try { + isSavingPassword = true; + emit(LoadingInitialState()); + var res = await DevicesAPI.generateMultiTimePassword( + deviceId: deviceId, + effectiveTime: effectiveTimeTimeStamp.toString(), + invalidTime: expirationTimeTimeStamp.toString(), + ); + ApiResponse pass = ApiResponse.fromJson(res); + passwordController.text = pass.data.offlineTempPassword; + passwordId = pass.data.offlineTempPasswordId; + CustomSnackBar.displaySnackBar('Save Successfully'); + add(InitialTimeLimitPassword()); + Future.delayed(const Duration(seconds: 1), () { + Clipboard.setData(ClipboardData(text: passwordController.text)); + }); + emit(const GeneratePasswordOneTimestate(generated: true)); + } catch (_) { + add(InitialPasswordsPage()); + } finally { + isSavingPassword = false; + } + } + + Future deletePassword(DeletePasswordEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + await DevicesAPI.deletePassword(deviceId: deviceId, passwordId: event.passwordId) + .then((value) async { + add(InitialPasswordsPage()); + }); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + bool _validateInputs() { + if (passwordController.text.length < 7) { + CustomSnackBar.displaySnackBar('Password less than 7'); + return true; + } + + if (passwordController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Password required'); + return true; + } + if (passwordNameController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Password name required'); + return true; + } + + if (effectiveTime == 'Select Time' || effectiveTimeTimeStamp == null) { + CustomSnackBar.displaySnackBar('Select effective time'); + return true; + } + + if (expirationTime == 'Select Time' || expirationTimeTimeStamp == null) { + CustomSnackBar.displaySnackBar('Select expiration time'); + return true; + } + + if (repeat == true && (endTime == null || startTime == null)) { + CustomSnackBar.displaySnackBar('Start Time and End time and the days required '); + return true; + } + return false; + } + + bool timeLimitValidate() { + if (effectiveTime == 'Select Time' || effectiveTimeTimeStamp == null) { + CustomSnackBar.displaySnackBar('Select effective time'); + return true; + } + if (expirationTime == 'Select Time' || expirationTimeTimeStamp == null) { + CustomSnackBar.displaySnackBar('Select expiration time'); + return true; + } + return false; + } + + List> days = [ + {"day": "Sun", "key": "Sun"}, + {"day": "Mon", "key": "Mon"}, + {"day": "Tue", "key": "Tue"}, + {"day": "Wed", "key": "Wed"}, + {"day": "Thu", "key": "Thu"}, + {"day": "Fri", "key": "Fri"}, + {"day": "Sat", "key": "Sat"}, + ]; + + List selectedDays = []; + + Future toggleDaySelection( + ToggleDaySelectionEvent event, + Emitter emit, + ) async { + emit(LoadingInitialState()); + if (selectedDays.contains(event.key)) { + selectedDays.remove(event.key); + } else { + selectedDays.add(event.key); + } + emit(ChangeTimeState()); + } + + String getTimeOnly(DateTime? dateTime) { + if (dateTime == null) return ''; + return DateFormat('HH:mm').format(dateTime); + } +} diff --git a/lib/features/devices/bloc/smart_door_bloc/smart_door_event.dart b/lib/features/devices/bloc/smart_door_bloc/smart_door_event.dart new file mode 100644 index 0000000..b33ca25 --- /dev/null +++ b/lib/features/devices/bloc/smart_door_bloc/smart_door_event.dart @@ -0,0 +1,101 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; + +abstract class SmartDoorEvent extends Equatable { + const SmartDoorEvent(); + + @override + List get props => []; +} + +class InitialEvent extends SmartDoorEvent {} + +class InitialPasswordsPage extends SmartDoorEvent {} + +class InitialOneTimePassword extends SmartDoorEvent {} + +class InitialTimeLimitPassword extends SmartDoorEvent {} + +class DoorLockUpdated extends SmartDoorEvent {} + +class UpdateLockEvent extends SmartDoorEvent { + final bool value; + const UpdateLockEvent({required this.value}); + @override + List get props => [value]; +} + +class SavePasswordEvent extends SmartDoorEvent { + final BuildContext context; + const SavePasswordEvent({required this.context}); + @override + List get props => [context]; +} + +class GenerateAndSavePasswordTimeLimitEvent extends SmartDoorEvent { + final BuildContext context; + const GenerateAndSavePasswordTimeLimitEvent({required this.context}); + @override + List get props => [context]; +} + +class GenerateAndSavePasswordOneTimeEvent extends SmartDoorEvent { + final BuildContext context; + const GenerateAndSavePasswordOneTimeEvent({required this.context}); + @override + List get props => [context]; +} + +class GeneratePasswordEvent extends SmartDoorEvent {} + +class SelectTimeEvent extends SmartDoorEvent { + final BuildContext context; + final bool isEffective; + const SelectTimeEvent({required this.context, required this.isEffective}); + @override + List get props => [context, isEffective]; +} + +class SelectTimeOnlinePasswordEvent extends SmartDoorEvent { + final BuildContext context; + final bool isEffective; + const SelectTimeOnlinePasswordEvent({required this.context, required this.isEffective}); + @override + List get props => [context, isEffective]; +} + +class ToggleRepeatEvent extends SmartDoorEvent {} + +class SetStartEndTimeEvent extends SmartDoorEvent { + final bool val; + const SetStartEndTimeEvent({required this.val}); + @override + List get props => [val]; +} + +class DeletePasswordEvent extends SmartDoorEvent { + final String passwordId; + + const DeletePasswordEvent({required this.passwordId}); + @override + List get props => [passwordId]; +} + +class ToggleDaySelectionEvent extends SmartDoorEvent { + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} + +class ChangeTimeEvent extends SmartDoorEvent { + final dynamic val; + final bool isStartEndTime; + + const ChangeTimeEvent({required this.val, required this.isStartEndTime}); + @override + List get props => [val, isStartEndTime]; +} + +class RenamePasswordEvent extends SmartDoorEvent {} diff --git a/lib/features/devices/bloc/smart_door_bloc/smart_door_state.dart b/lib/features/devices/bloc/smart_door_bloc/smart_door_state.dart new file mode 100644 index 0000000..7741361 --- /dev/null +++ b/lib/features/devices/bloc/smart_door_bloc/smart_door_state.dart @@ -0,0 +1,82 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; +import 'package:syncrow_app/features/devices/model/temporary_password_model.dart'; + +class SmartDoorState extends Equatable { + const SmartDoorState(); + + @override + List get props => []; +} + +class InitialState extends SmartDoorState {} + +class LoadingInitialState extends SmartDoorState {} + +class UpdateState extends SmartDoorState { + final SmartDoorModel smartDoorModel; + const UpdateState({required this.smartDoorModel}); + + @override + List get props => [smartDoorModel]; +} + +class LoadingNewSate extends SmartDoorState { + final SmartDoorModel smartDoorModel; + const LoadingNewSate({required this.smartDoorModel}); + + @override + List get props => [smartDoorModel]; +} + +class FailedState extends SmartDoorState { + final String errorMessage; + + const FailedState({required this.errorMessage}); + + @override + List get props => [errorMessage]; +} + +class GeneratePasswordState extends SmartDoorState { +} + +class TimeSelectedState extends SmartDoorState {} + +class IsRepeatState extends SmartDoorState { + final bool repeat; + const IsRepeatState({required this.repeat}); + + @override + List get props => [repeat]; + +} + +class IsStartEndState extends SmartDoorState { + final bool isStartEndTime; + const IsStartEndState({required this.isStartEndTime}); + + @override + List get props => [isStartEndTime]; +} + +class ChangeStartTimeState extends SmartDoorState {} + +class ChangeEndTimeState extends SmartDoorState {} + +class ChangeTimeState extends SmartDoorState {} + +class SaveState extends SmartDoorState {} + +class LoadingSaveState extends SmartDoorState {} + +class GeneratePasswordOneTimestate extends SmartDoorState { + final bool generated; + const GeneratePasswordOneTimestate({required this.generated}); + List get props => [generated]; +} + +class TemporaryPasswordsLoadedState extends SmartDoorState { + final List temporaryPassword; + const TemporaryPasswordsLoadedState({required this.temporaryPassword}); +} diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart new file mode 100644 index 0000000..53bb034 --- /dev/null +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart @@ -0,0 +1,483 @@ +import 'dart:async'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/features/devices/model/three_gang_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; + +class ThreeGangBloc extends Bloc { + final String threeGangId; + ThreeGangModel deviceStatus = ThreeGangModel( + firstSwitch: false, + secondSwitch: false, + thirdSwitch: false, + firstCountDown: 0, + secondCountDown: 0, + thirdCountDown: 0); + Timer? _timer; + // Timer? _firstSwitchTimer; + // Timer? _secondSwitchTimer; + // Timer? _thirdSwitchTimer; + + bool threeGangGroup = false; + List devicesList = []; + List groupThreeGangList = []; + bool allSwitchesOn = true; + + ThreeGangBloc({required this.threeGangId}) : super(InitialState()) { + on(_fetchThreeGangStatus); + on(_threeGangUpdated); + on(_changeFirstSwitch); + on(_changeSecondSwitch); + on(_changeThirdSwitch); + on(_allOff); + on(_allOn); + on(_changeSliding); + on(_setCounterValue); + on(_getCounterValue); + on(_onTickTimer); + on(_onClose); + on(_groupAllOn); + on(_groupAllOff); + } + + void _fetchThreeGangStatus(InitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + threeGangGroup = event.groupScreen; + if (threeGangGroup) { + devicesList = []; + groupThreeGangList = []; + allSwitchesOn = true; + devicesList = await DevicesAPI.getDeviceByGroupName( + HomeCubit.getInstance().selectedSpace?.id ?? '', '3G'); + + for (int i = 0; i < devicesList.length; i++) { + var response = await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = ThreeGangModel.fromJson(statusModelList); + + groupThreeGangList.add(GroupThreeGangModel( + deviceId: devicesList[i].uuid ?? '', + deviceName: devicesList[i].name ?? '', + firstSwitch: deviceStatus.firstSwitch, + secondSwitch: deviceStatus.secondSwitch, + thirdSwitch: deviceStatus.thirdSwitch)); + } + + if (groupThreeGangList.isNotEmpty) { + groupThreeGangList.firstWhere((element) { + if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + allSwitchesOn = false; + } + return true; + }); + } + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesOn)); + } else { + var response = await DevicesAPI.getDeviceStatus(threeGangId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = ThreeGangModel.fromJson(statusModelList); + emit(UpdateState(threeGangModel: deviceStatus)); + _listenToChanges(); + } + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$threeGangId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) async { + if (_timer != null) { + await Future.delayed(const Duration(seconds: 2)); + } + Map usersMap = event.snapshot.value as Map; + List statusList = []; + + usersMap['status'].forEach((element) { + statusList.add(StatusModel(code: element['code'], value: element['value'])); + }); + + deviceStatus = ThreeGangModel.fromJson(statusList); + if (!isClosed) { + add(ThreeGangUpdated()); + } + }); + } catch (_) {} + } + + _threeGangUpdated(ThreeGangUpdated event, Emitter emit) { + emit(UpdateState(threeGangModel: deviceStatus)); + } + + void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + try { + if (threeGangGroup) { + bool allSwitchesValue = true; + groupThreeGangList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.firstSwitch = !event.value; + } + if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + allSwitchesValue = false; + } + }); + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + } else { + deviceStatus.firstSwitch = !event.value; + emit(UpdateState(threeGangModel: deviceStatus)); + } + + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangGroup ? event.deviceId : threeGangId, + code: 'switch_1', + value: !event.value), + threeGangGroup ? event.deviceId : threeGangId); + + if (!response['success']) { + add(InitialEvent(groupScreen: threeGangGroup)); + } + }); + } catch (_) { + add(InitialEvent(groupScreen: threeGangGroup)); + } + } + + void _changeSecondSwitch( + ChangeSecondSwitchStatusEvent event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + try { + if (threeGangGroup) { + bool allSwitchesValue = true; + groupThreeGangList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.secondSwitch = !event.value; + } + if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + allSwitchesValue = false; + } + }); + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + } else { + deviceStatus.secondSwitch = !event.value; + emit(UpdateState(threeGangModel: deviceStatus)); + } + + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangGroup ? event.deviceId : threeGangId, + code: 'switch_2', + value: !event.value), + threeGangGroup ? event.deviceId : threeGangId); + + if (!response['success']) { + add(InitialEvent(groupScreen: threeGangGroup)); + } + }); + } catch (_) { + add(InitialEvent(groupScreen: threeGangGroup)); + } + } + + void _changeThirdSwitch(ChangeThirdSwitchStatusEvent event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + try { + if (threeGangGroup) { + bool allSwitchesValue = true; + groupThreeGangList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.thirdSwitch = !event.value; + } + if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + allSwitchesValue = false; + } + }); + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + } else { + deviceStatus.thirdSwitch = !event.value; + emit(UpdateState(threeGangModel: deviceStatus)); + } + + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + final response = await DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangGroup ? event.deviceId : threeGangId, + code: 'switch_3', + value: !event.value), + threeGangGroup ? event.deviceId : threeGangId); + + if (!response['success']) { + add(InitialEvent(groupScreen: threeGangGroup)); + } + }); + } catch (_) { + add(InitialEvent(groupScreen: threeGangGroup)); + } + } + + void _allOff(AllOffEvent event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + + try { + deviceStatus.firstSwitch = false; + deviceStatus.secondSwitch = false; + deviceStatus.thirdSwitch = false; + emit(UpdateState(threeGangModel: deviceStatus)); + + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch), + threeGangId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch), + threeGangId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangId, code: 'switch_3', value: deviceStatus.thirdSwitch), + threeGangId), + ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } + + void _allOn(AllOnEvent event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + + try { + deviceStatus.firstSwitch = true; + deviceStatus.secondSwitch = true; + deviceStatus.thirdSwitch = true; + emit(UpdateState(threeGangModel: deviceStatus)); + + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch), + threeGangId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch), + threeGangId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: threeGangId, code: 'switch_3', value: deviceStatus.thirdSwitch), + threeGangId), + ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } + } + + void _groupAllOn(GroupAllOnEvent event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + try { + for (int i = 0; i < groupThreeGangList.length; i++) { + groupThreeGangList[i].firstSwitch = true; + groupThreeGangList[i].secondSwitch = true; + groupThreeGangList[i].thirdSwitch = true; + } + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: true)); + + for (int i = 0; i < groupThreeGangList.length; i++) { + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: true), + groupThreeGangList[i].deviceId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupThreeGangList[i].deviceId, code: 'switch_2', value: true), + groupThreeGangList[i].deviceId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: true), + groupThreeGangList[i].deviceId), + ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + break; + } + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } + + void _groupAllOff(GroupAllOffEvent event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + try { + for (int i = 0; i < groupThreeGangList.length; i++) { + groupThreeGangList[i].firstSwitch = false; + groupThreeGangList[i].secondSwitch = false; + groupThreeGangList[i].thirdSwitch = false; + } + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: false)); + + for (int i = 0; i < groupThreeGangList.length; i++) { + final response = await Future.wait([ + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: false), + groupThreeGangList[i].deviceId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupThreeGangList[i].deviceId, code: 'switch_2', value: false), + groupThreeGangList[i].deviceId), + DevicesAPI.controlDevice( + DeviceControlModel( + deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: false), + groupThreeGangList[i].deviceId), + ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + break; + } + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } + } + + void _changeSliding(ChangeSlidingSegment event, Emitter emit) async { + emit(ChangeSlidingSegmentState(value: event.value)); + } + + void _setCounterValue(SetCounterValue event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + int seconds = 0; + try { + seconds = event.duration.inSeconds; + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: threeGangId, code: event.deviceCode, value: seconds), + threeGangId); + + if (response['success'] ?? false) { + if (event.deviceCode == 'countdown_1') { + deviceStatus.firstCountDown = seconds; + } else if (event.deviceCode == 'countdown_2') { + deviceStatus.secondCountDown = seconds; + } else if (event.deviceCode == 'countdown_3') { + deviceStatus.thirdCountDown = seconds; + } + } else { + emit(const FailedState(error: 'Something went wrong')); + return; + } + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + if (seconds > 0) { + _onStartTimer(seconds); + } else { + _timer?.cancel(); + emit(TimerRunComplete()); + } + } + + void _getCounterValue(GetCounterEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + var response = await DevicesAPI.getDeviceStatus(threeGangId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = ThreeGangModel.fromJson(statusModelList); + + if (event.deviceCode == 'countdown_1') { + deviceStatus.firstCountDown > 0 + ? _onStartTimer(deviceStatus.firstCountDown) + : emit(UpdateTimerState(seconds: deviceStatus.firstCountDown)); + } else if (event.deviceCode == 'countdown_2') { + deviceStatus.secondCountDown > 0 + ? _onStartTimer(deviceStatus.secondCountDown) + : emit(UpdateTimerState(seconds: deviceStatus.secondCountDown)); + } else if (event.deviceCode == 'countdown_3') { + deviceStatus.thirdCountDown > 0 + ? _onStartTimer(deviceStatus.thirdCountDown) + : emit(UpdateTimerState(seconds: deviceStatus.thirdCountDown)); + } + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + void _onClose(OnClose event, Emitter emit) { + _timer?.cancel(); + // _firstSwitchTimer?.cancel(); + // _secondSwitchTimer?.cancel(); + // _thirdSwitchTimer?.cancel(); + } + + void _onStartTimer(int seconds) { + _timer?.cancel(); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + add(TickTimer(seconds - timer.tick)); + }); + } + + void _onTickTimer(TickTimer event, Emitter emit) { + if (event.remainingTime > 0) { + emit(TimerRunInProgress(event.remainingTime)); + } else { + _timer?.cancel(); + emit(TimerRunComplete()); + } + } +} diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart new file mode 100644 index 0000000..5cd3cfb --- /dev/null +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart @@ -0,0 +1,95 @@ +import 'package:equatable/equatable.dart'; + +abstract class ThreeGangEvent extends Equatable { + const ThreeGangEvent(); + + @override + List get props => []; +} + +class LoadingEvent extends ThreeGangEvent {} + +class ThreeGangUpdated extends ThreeGangEvent {} + +class InitialEvent extends ThreeGangEvent { + final bool groupScreen; + const InitialEvent({required this.groupScreen}); + @override + List get props => [groupScreen]; +} + +class ChangeFirstSwitchStatusEvent extends ThreeGangEvent { + final bool value; + final String deviceId; + const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class ChangeSecondSwitchStatusEvent extends ThreeGangEvent { + final bool value; + final String deviceId; + const ChangeSecondSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class ChangeThirdSwitchStatusEvent extends ThreeGangEvent { + final bool value; + final String deviceId; + const ChangeThirdSwitchStatusEvent({required this.value, this.deviceId = ''}); + @override + List get props => [value, deviceId]; +} + +class AllOffEvent extends ThreeGangEvent {} + +class AllOnEvent extends ThreeGangEvent {} + +class GroupAllOnEvent extends ThreeGangEvent {} + +class GroupAllOffEvent extends ThreeGangEvent {} + +class ChangeSlidingSegment extends ThreeGangEvent { + final int value; + const ChangeSlidingSegment({required this.value}); + @override + List get props => [value]; +} + +class GetCounterEvent extends ThreeGangEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class SetCounterValue extends ThreeGangEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends ThreeGangEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends ThreeGangEvent { + final int remainingTime; + + const TickTimer(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends ThreeGangEvent {} + +class OnClose extends ThreeGangEvent {} diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart new file mode 100644 index 0000000..6760dea --- /dev/null +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart'; +import 'package:syncrow_app/features/devices/model/three_gang_model.dart'; + +class ThreeGangState extends Equatable { + const ThreeGangState(); + + @override + List get props => []; +} + +class InitialState extends ThreeGangState {} + +class LoadingInitialState extends ThreeGangState {} + +class UpdateState extends ThreeGangState { + final ThreeGangModel threeGangModel; + const UpdateState({required this.threeGangModel}); + + @override + List get props => [threeGangModel]; +} + +class LoadingNewSate extends ThreeGangState { + final ThreeGangModel threeGangModel; + const LoadingNewSate({required this.threeGangModel}); + + @override + List get props => [threeGangModel]; +} + +class UpdateGroupState extends ThreeGangState { + final List threeGangList; + final bool allSwitches; + + const UpdateGroupState({required this.threeGangList, required this.allSwitches}); + + @override + List get props => [threeGangList, allSwitches]; +} + +class FailedState extends ThreeGangState { + final String error; + + const FailedState({required this.error}); + + @override + List get props => [error]; +} + +class ChangeSlidingSegmentState extends ThreeGangState { + final int value; + + const ChangeSlidingSegmentState({required this.value}); + + @override + List get props => [value]; +} + +class UpdateTimerState extends ThreeGangState { + final int seconds; + const UpdateTimerState({required this.seconds}); + + @override + List get props => [seconds]; +} + +class TimerRunInProgress extends ThreeGangState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class TimerRunComplete extends ThreeGangState {} diff --git a/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_bloc.dart b/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_bloc.dart new file mode 100644 index 0000000..7c01795 --- /dev/null +++ b/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_bloc.dart @@ -0,0 +1,94 @@ +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/wall_sensor_bloc/wall_sensor_state.dart'; +import 'package:syncrow_app/features/devices/bloc/wall_sensor_bloc/wall_sensor_event.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/wall_sensor_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; + +class WallSensorBloc extends Bloc { + final String deviceId; + late DeviceModel deviceModel; + late WallSensorModel deviceStatus; + + WallSensorBloc({required this.deviceId}) : super(InitialState()) { + on(_fetchCeilingSensorStatus); + on(_changeIndicator); + on(_changeValue); + on(_wallSensorUpdated); + } + + void _fetchCeilingSensorStatus(InitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + var response = await DevicesAPI.getDeviceStatus(deviceId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = WallSensorModel.fromJson(statusModelList); + emit(UpdateState(wallSensorModel: deviceStatus)); + _listenToChanges(); + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + _listenToChanges() { + try { + DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); + Stream stream = ref.onValue; + + stream.listen((DatabaseEvent event) { + Map usersMap = event.snapshot.value as Map; + List statusList = []; + + usersMap['status'].forEach((element) { + statusList.add(StatusModel(code: element['code'], value: element['value'])); + }); + + deviceStatus = WallSensorModel.fromJson(statusList); + add(WallSensorUpdatedEvent()); + }); + } catch (_) {} + } + + _wallSensorUpdated(WallSensorUpdatedEvent event, Emitter emit) { + emit(UpdateState(wallSensorModel: deviceStatus)); + } + + void _changeIndicator(ChangeIndicatorEvent event, Emitter emit) async { + emit(LoadingNewSate(wallSensorModel: deviceStatus)); + try { + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: deviceId, code: 'indicator', value: !event.value), deviceId); + + if (response['success'] ?? false) { + deviceStatus.indicator = !event.value; + } + } catch (_) {} + emit(UpdateState(wallSensorModel: deviceStatus)); + } + + void _changeValue(ChangeValueEvent event, Emitter emit) async { + emit(LoadingNewSate(wallSensorModel: deviceStatus)); + try { + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: deviceId, code: event.code, value: event.value), deviceId); + + if (response['success'] ?? false) { + if (event.code == 'far_detection') { + deviceStatus.farDetection = event.value; + } else if (event.code == 'motionless_sensitivity') { + deviceStatus.motionlessSensitivity = event.value; + } else if (event.code == 'motion_sensitivity_value') { + deviceStatus.motionSensitivity = event.value; + } + } + } catch (_) {} + emit(UpdateState(wallSensorModel: deviceStatus)); + } +} diff --git a/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_event.dart b/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_event.dart new file mode 100644 index 0000000..415c137 --- /dev/null +++ b/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_event.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; + +abstract class WallSensorEvent extends Equatable { + const WallSensorEvent(); + + @override + List get props => []; +} + +class LoadingEvent extends WallSensorEvent {} + +class InitialEvent extends WallSensorEvent {} + +class WallSensorUpdatedEvent extends WallSensorEvent {} + +class ChangeIndicatorEvent extends WallSensorEvent { + final bool value; + const ChangeIndicatorEvent({required this.value}); + + @override + List get props => [value]; +} + +class ChangeValueEvent extends WallSensorEvent { + final int value; + final String code; + const ChangeValueEvent({required this.value, required this.code}); + + @override + List get props => [value, code]; +} diff --git a/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_state.dart b/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_state.dart new file mode 100644 index 0000000..76ce10f --- /dev/null +++ b/lib/features/devices/bloc/wall_sensor_bloc/wall_sensor_state.dart @@ -0,0 +1,38 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/wall_sensor_model.dart'; + +class WallSensorState extends Equatable { + const WallSensorState(); + + @override + List get props => []; +} + +class InitialState extends WallSensorState {} + +class LoadingInitialState extends WallSensorState {} + +class UpdateState extends WallSensorState { + final WallSensorModel wallSensorModel; + const UpdateState({required this.wallSensorModel}); + + @override + List get props => [wallSensorModel]; +} + +class LoadingNewSate extends WallSensorState { + final WallSensorModel wallSensorModel; + const LoadingNewSate({required this.wallSensorModel}); + + @override + List get props => [wallSensorModel]; +} + +class FailedState extends WallSensorState { + final String error; + + const FailedState({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/features/devices/model/ac_model.dart b/lib/features/devices/model/ac_model.dart new file mode 100644 index 0000000..9f9e06f --- /dev/null +++ b/lib/features/devices/model/ac_model.dart @@ -0,0 +1,84 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class AcStatusModel { + String uuid; + bool acSwitch; + String modeString; + int tempSet; + int currentTemp; + String fanSpeedsString; + bool childLock; + late TempModes acMode; + late FanSpeeds acFanSpeed; + + AcStatusModel( + {required this.uuid, + required this.acSwitch, + required this.modeString, + required this.tempSet, + required this.currentTemp, + required this.fanSpeedsString, + required this.childLock}) { + acMode = getACMode(modeString); + acFanSpeed = getFanSpeed(fanSpeedsString); + } + + factory AcStatusModel.fromJson(String id, List jsonList) { + late bool _acSwitch; + late String _mode; + late int _tempSet; + late int _currentTemp; + late String _fanSpeeds; + late bool _childLock; + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'switch') { + _acSwitch = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'mode') { + _mode = jsonList[i].value ?? TempModes.cold; + } else if (jsonList[i].code == 'temp_set') { + _tempSet = jsonList[i].value ?? 210; + } else if (jsonList[i].code == 'temp_current') { + _currentTemp = jsonList[i].value ?? 210; + } else if (jsonList[i].code == 'level') { + _fanSpeeds = jsonList[i].value ?? 210; + } else if (jsonList[i].code == 'child_lock') { + _childLock = jsonList[i].value ?? false; + } + } + return AcStatusModel( + uuid: id, + acSwitch: _acSwitch, + modeString: _mode, + tempSet: _tempSet, + currentTemp: _currentTemp, + fanSpeedsString: _fanSpeeds, + childLock: _childLock); + } + + static TempModes getACMode(String value) { + if (value == 'cold') { + return TempModes.cold; + } else if (value == 'hot') { + return TempModes.hot; + } else if (value == 'wind') { + return TempModes.wind; + } else { + return TempModes.cold; + } + } + + static FanSpeeds getFanSpeed(String value) { + if (value == 'low') { + return FanSpeeds.low; + } else if (value == 'middle') { + return FanSpeeds.middle; + } else if (value == 'high') { + return FanSpeeds.high; + } else if (value == 'auto') { + return FanSpeeds.auto; + } else { + return FanSpeeds.auto; + } + } +} diff --git a/lib/features/devices/model/ceiling_sensor_model.dart b/lib/features/devices/model/ceiling_sensor_model.dart new file mode 100644 index 0000000..ac6e1b0 --- /dev/null +++ b/lib/features/devices/model/ceiling_sensor_model.dart @@ -0,0 +1,50 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class CeilingSensorModel { + String presenceState; + int sensitivity; + String checkingResult; + int presenceRange; + int sportsPara; + String bodyMovement; + + CeilingSensorModel( + {required this.presenceState, + required this.sensitivity, + required this.checkingResult, + required this.presenceRange, + required this.sportsPara, + required this.bodyMovement}); + + factory CeilingSensorModel.fromJson(List jsonList) { + late String _presenceState; + late int _sensitivity; + late String _checkingResult; + int _presenceRange = 1; + int _sportsPara = 1; + String _bodyMovement = 'none'; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'presence_state') { + _presenceState = jsonList[i].value ?? 'none'; + } else if (jsonList[i].code == 'sensitivity') { + _sensitivity = jsonList[i].value ?? 1; + } else if (jsonList[i].code == 'checking_result') { + _checkingResult = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'presence_range') { + _presenceRange = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'sports_para') { + _sportsPara = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'body_movement') { + _bodyMovement = jsonList[i].value ?? ''; + } + } + return CeilingSensorModel( + presenceState: _presenceState, + sensitivity: _sensitivity, + checkingResult: _checkingResult, + presenceRange: _presenceRange, + sportsPara: _sportsPara, + bodyMovement: _bodyMovement); + } +} diff --git a/lib/features/devices/model/create_temporary_password_model.dart b/lib/features/devices/model/create_temporary_password_model.dart new file mode 100644 index 0000000..fe94079 --- /dev/null +++ b/lib/features/devices/model/create_temporary_password_model.dart @@ -0,0 +1,68 @@ + + + +class CreateTemporaryPasswordModel{ + final String name; + late final String password; + late final String effectiveTime; + late final String invalidTime; + List? scheduleList; + + CreateTemporaryPasswordModel({ + required this.name, + required this.password, + required this.effectiveTime, + required this.invalidTime, + this.scheduleList, + }); + + factory CreateTemporaryPasswordModel.fromJson(Map json) { + return CreateTemporaryPasswordModel( + name: json['name'], + password: json['password'], + effectiveTime: json['effectiveTime'], + invalidTime: json['invalidTime'], + scheduleList: (json['scheduleList'] as List) + .map((i) => Schedule.fromJson(i)) + .toList(), + ); + } + + Map toJson() { + return { + 'name': name, + 'password': password, + 'effectiveTime': effectiveTime, + 'invalidTime': invalidTime, + 'scheduleList': scheduleList!.map((i) => i.toJson()).toList(), + }; + } +} + +class Schedule { + final String effectiveTime; + final String invalidTime; + final List workingDay; + + Schedule({ + required this.effectiveTime, + required this.invalidTime, + required this.workingDay, + }); + + factory Schedule.fromJson(Map json) { + return Schedule( + effectiveTime: json['effectiveTime'], + invalidTime: json['invalidTime'], + workingDay: List.from(json['workingDay']), + ); + } + + Map toJson() { + return { + 'effectiveTime': effectiveTime, + 'invalidTime': invalidTime, + 'workingDay': workingDay, + }; + } +} diff --git a/lib/features/devices/model/device_category_model.dart b/lib/features/devices/model/device_category_model.dart new file mode 100644 index 0000000..99ed743 --- /dev/null +++ b/lib/features/devices/model/device_category_model.dart @@ -0,0 +1,64 @@ +// ignore_for_file: constant_identifier_names + +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class DevicesCategoryModel { + int? id; + final String? name; + final String? icon; + + bool? devicesStatus; + final List? devices; + + final DeviceType? type; + bool isSelected; + + DevicesCategoryModel( + {this.isSelected = false, + required this.type, + required this.name, + required this.icon, + required this.devices}) { + //sets the initial status of the devices + if (devices != null) { + if (devices!.isNotEmpty) { + bool tempStatus = devices!.first.isOnline ?? false; + for (var device in devices!) { + if (device.isOnline != tempStatus) { + devicesStatus = false; + break; + } + } + devicesStatus = tempStatus; + } + } else { + devicesStatus = false; + } + } + + DevicesCategoryModel.fromJson(Map json) + : name = json['groupName'], + // id = json['groupId'], + type = devicesTypesMap[json['groupName']] ?? DeviceType.Other, + icon = deviceTypeIconMap[devicesTypesMap[json['groupName']] ?? DeviceType.Other] ?? '', + devices = [], + isSelected = false; + + static List fromJsonList(List jsonList) { + return jsonList.map((item) => DevicesCategoryModel.fromJson(item)).toList(); + } +} + +Map deviceTypeIconMap = { + DeviceType.AC: Assets.assetsIconsAC, + DeviceType.LightBulb: Assets.assetsIconsLight, + DeviceType.DoorLock: Assets.assetsIconsDoorLock, + DeviceType.Curtain: Assets.assetsIconsCurtain, + DeviceType.Gateway: Assets.assetsIconsGateway, + DeviceType.CeilingSensor: Assets.assetsIconsSensors, + DeviceType.WallSensor: Assets.assetsIconsSensors, + DeviceType.ThreeGang: Assets.assetsIconsGang, + DeviceType.Other: Assets.assetsIconsAC, +}; diff --git a/lib/features/devices/model/device_control_model.dart b/lib/features/devices/model/device_control_model.dart new file mode 100644 index 0000000..ca3eea8 --- /dev/null +++ b/lib/features/devices/model/device_control_model.dart @@ -0,0 +1,27 @@ +class DeviceControlModel { + String? deviceId; + String? code; + dynamic value; + + DeviceControlModel({ + required this.deviceId, + required this.code, + required this.value, + }); + + factory DeviceControlModel.fromJson(Map json) { + return DeviceControlModel( + deviceId: json['deviceUuid'], + code: json['code'], + value: json['value'], + ); + } + + Map toJson() { + return { + // 'deviceUuid': deviceId, + 'code': code, + 'value': value, + }; + } +} diff --git a/lib/features/devices/model/device_model.dart b/lib/features/devices/model/device_model.dart new file mode 100644 index 0000000..82fac11 --- /dev/null +++ b/lib/features/devices/model/device_model.dart @@ -0,0 +1,99 @@ +import 'package:syncrow_app/features/devices/model/function_model.dart'; +import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class DeviceModel { + int? activeTime; + // String? id; + String? localKey; + String? model; + String? name; + String? icon; + String? type; + bool? isOnline; + List status = []; + String? productName; + String? timeZone; + int? updateTime; + String? uuid; + String? productUuid; + DeviceType? productType; + bool isSelected = false; + late List functions; + DeviceModel( + {this.activeTime, + this.productUuid, + this.localKey, + this.model, + this.name, + this.isOnline, + required this.status, + this.productName, + this.timeZone, + this.updateTime, + this.uuid, + this.productType, + this.icon, + this.type}) { + functions = getFunctions(productType!); + } + + factory DeviceModel.fromJson(Map json) { + String tempIcon = ''; + DeviceType type = devicesTypesMap[json['productType']] ?? DeviceType.Other; + + if (type == DeviceType.LightBulb) { + tempIcon = Assets.assetsIconsLight; + } else if (type == DeviceType.CeilingSensor || + type == DeviceType.WallSensor) { + tempIcon = Assets.assetsIconsSensors; + } else if (type == DeviceType.AC) { + tempIcon = Assets.assetsIconsAC; + } else if (type == DeviceType.DoorLock) { + tempIcon = Assets.assetsIconsDoorLock; + } else if (type == DeviceType.Curtain) { + tempIcon = Assets.assetsIconsCurtain; + } else if (type == DeviceType.ThreeGang) { + tempIcon = Assets.assetsIcons3GangSwitch; + } else if (type == DeviceType.Gateway) { + tempIcon = Assets.assetsIconsGateway; + } else { + tempIcon = Assets.assetsIconsLogo; + } + return DeviceModel( + icon: tempIcon, + activeTime: json['activeTime'], + // id: json['id'], + localKey: json['localKey'], + model: json['model'], + name: json['name'], + isOnline: json['online'], + productName: json['productName'], + timeZone: json['timeZone'], + updateTime: json['updateTime'], + uuid: json['uuid'], + productType: type, + type: json['productType'], + status: [], + productUuid: json['productUuid']); + } + + Map toJson() { + return { + 'activeTime': activeTime, + 'localKey': localKey, + 'model': model, + 'name': name, + 'online': isOnline, + 'productName': productName, + 'timeZone': timeZone, + 'updateTime': updateTime, + 'uuid': uuid, + 'productType': productType, + }; + } + + List getFunctions(DeviceType type) => + devicesFunctionsMap[productType] ?? []; +} diff --git a/lib/features/devices/model/function_model.dart b/lib/features/devices/model/function_model.dart new file mode 100644 index 0000000..24a5d93 --- /dev/null +++ b/lib/features/devices/model/function_model.dart @@ -0,0 +1,67 @@ +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class FunctionModel { + String? code; + FunctionType? type; + dynamic values; + + FunctionModel({ + required this.code, + required this.type, + required this.values, + }); + + factory FunctionModel.fromJson(Map json) { + return FunctionModel( + code: json['code'], + type: json['type'], + values: json['values'], + ); + } + + Map toJson() { + return { + 'code': code, + 'type': type, + 'values': values, + }; + } +} + +//"values": "{\"unit\":\"\",\"min\":1,\"max\":10,\"scale\":0,\"step\":1}", + +class ValueModel { + String? unit; + int? min; + int? max; + int? scale; + int? step; + + ValueModel({ + required this.unit, + required this.min, + required this.max, + required this.scale, + required this.step, + }); + + factory ValueModel.fromJson(Map json) { + return ValueModel( + unit: json['unit'], + min: json['min'], + max: json['max'], + scale: json['scale'], + step: json['step'], + ); + } + + Map toJson() { + return { + 'unit': unit, + 'min': min, + 'max': max, + 'scale': scale, + 'step': step, + }; + } +} diff --git a/lib/features/devices/model/group_three_gang_model.dart b/lib/features/devices/model/group_three_gang_model.dart new file mode 100644 index 0000000..8c77e0f --- /dev/null +++ b/lib/features/devices/model/group_three_gang_model.dart @@ -0,0 +1,15 @@ +class GroupThreeGangModel { + final String deviceId; + final String deviceName; + bool firstSwitch; + bool secondSwitch; + bool thirdSwitch; + + GroupThreeGangModel({ + required this.deviceId, + required this.deviceName, + required this.firstSwitch, + required this.secondSwitch, + required this.thirdSwitch, + }); +} diff --git a/lib/features/devices/model/offline_password_model.dart b/lib/features/devices/model/offline_password_model.dart new file mode 100644 index 0000000..17d8faa --- /dev/null +++ b/lib/features/devices/model/offline_password_model.dart @@ -0,0 +1,63 @@ + + +class OfflinePasswordModel { + final dynamic gmtCreate; + final dynamic gmtExpired; + final dynamic gmtStart; + final bool hasClearPwd; + final String optUid; + final String pwd; + final dynamic pwdId; + final String pwdName; + final String pwdTypeCode; + final String revokedPwdName; + final dynamic status; + + OfflinePasswordModel({ + required this.gmtCreate, + required this.gmtExpired, + required this.gmtStart, + required this.hasClearPwd, + required this.optUid, + required this.pwd, + required this.pwdId, + required this.pwdName, + required this.pwdTypeCode, + required this.revokedPwdName, + required this.status, + }); + + // Factory method to create a Password from a JSON map + factory OfflinePasswordModel.fromJson(Map json) { + return OfflinePasswordModel( + gmtCreate: json['gmtCreate'], + gmtExpired: json['gmtExpired'], + gmtStart: json['gmtStart'], + hasClearPwd: json['hasClearPwd'], + optUid: json['optUid'], + pwd: json['pwd'], + pwdId: json['pwdId'], + pwdName: json['pwdName'], + pwdTypeCode: json['pwdTypeCode'], + revokedPwdName: json['revokedPwdName'], + status: json['status'], + ); + } + + // Method to convert a Password object to a JSON map + Map toJson() { + return { + 'gmtCreate': gmtCreate, + 'gmtExpired': gmtExpired, + 'gmtStart': gmtStart, + 'hasClearPwd': hasClearPwd, + 'optUid': optUid, + 'pwd': pwd, + 'pwdId': pwdId, + 'pwdName': pwdName, + 'pwdTypeCode': pwdTypeCode, + 'revokedPwdName': revokedPwdName, + 'status': status, + }; + } +} \ No newline at end of file diff --git a/lib/features/devices/model/offline_temporary_password.dart b/lib/features/devices/model/offline_temporary_password.dart new file mode 100644 index 0000000..33ed4fe --- /dev/null +++ b/lib/features/devices/model/offline_temporary_password.dart @@ -0,0 +1,69 @@ +class OfflineTemporaryPassword { + dynamic effectiveTime; + dynamic invalidTime; + dynamic offlineTempPassword; + dynamic offlineTempPasswordId; + dynamic offlineTempPasswordName; + + OfflineTemporaryPassword({ + required this.effectiveTime, + required this.invalidTime, + required this.offlineTempPassword, + required this.offlineTempPasswordId, + required this.offlineTempPasswordName, + }); + + factory OfflineTemporaryPassword.fromJson(Map json) { + return OfflineTemporaryPassword( + effectiveTime: json['effective_time'], + invalidTime: json['invalid_time'], + offlineTempPassword: json['offline_temp_password'], + offlineTempPasswordId: json['offline_temp_password_id'], + offlineTempPasswordName: json['offline_temp_password_name'], + ); + } + + Map toJson() { + return { + 'effective_time': effectiveTime, + 'invalid_time': invalidTime, + 'offline_temp_password': offlineTempPassword, + 'offline_temp_password_id': offlineTempPasswordId, + 'offline_temp_password_name': offlineTempPasswordName, + }; + } +} + +class ApiResponse { + int statusCode; + bool success; + String message; + OfflineTemporaryPassword data; + + ApiResponse({ + required this.statusCode, + required this.success, + required this.message, + required this.data, + }); + + factory ApiResponse.fromJson(Map json) { + return ApiResponse( + statusCode: json['statusCode'], + success: json['success'], + message: json['message'], + data: OfflineTemporaryPassword.fromJson(json['data']['result']), + ); + } + + Map toJson() { + return { + 'statusCode': statusCode, + 'success': success, + 'message': message, + 'data': { + 'result': data.toJson(), + }, + }; + } +} diff --git a/lib/features/devices/model/room_model.dart b/lib/features/devices/model/room_model.dart new file mode 100644 index 0000000..7a00b43 --- /dev/null +++ b/lib/features/devices/model/room_model.dart @@ -0,0 +1,40 @@ +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class RoomModel { + final String? id; + final String? name; + final SpaceType type; + List? devices; + + RoomModel({ + required this.id, + required this.name, + required this.type, + required this.devices, + }); + + Map toJson() { + return { + 'id': id, + 'name': name, + 'type': type, + 'devices': devices, + }; + } + + factory RoomModel.fromJson(Map json) { + List devices = []; + if (json['devices'] != null) { + for (var device in json['devices']) { + devices.add(DeviceModel.fromJson(device)); + } + } + return RoomModel( + id: json['uuid'], + name: json['name'], + type: spaceTypesMap[json['type']]!, + devices: [], + ); + } +} diff --git a/lib/features/devices/model/smart_door_model.dart b/lib/features/devices/model/smart_door_model.dart new file mode 100644 index 0000000..2403941 --- /dev/null +++ b/lib/features/devices/model/smart_door_model.dart @@ -0,0 +1,116 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class SmartDoorModel { + int unlockFingerprint; + int unlockPassword; + int unlockTemporary; + int unlockCard; + String unlockAlarm; + int unlockRequest; + int residualElectricity; + bool reverseLock; + int unlockApp; + bool hijack; + bool doorbell; + String unlockOfflinePd; + String unlockOfflineClear; + String unlockDoubleKit; + String remoteNoPdSetkey; + String remoteNoDpKey; + bool normalOpenSwitch; + + SmartDoorModel( + {required this.unlockFingerprint, + required this.unlockPassword, + required this.unlockTemporary, + required this.unlockCard, + required this.unlockAlarm, + required this.unlockRequest, + required this.residualElectricity, + required this.reverseLock, + required this.unlockApp, + required this.hijack, + required this.doorbell, + required this.unlockOfflinePd, + required this.unlockOfflineClear, + required this.unlockDoubleKit, + required this.remoteNoPdSetkey, + required this.remoteNoDpKey, + required this.normalOpenSwitch}); + + factory SmartDoorModel.fromJson(List jsonList) { + late int _unlockFingerprint; + late int _unlockPassword; + late int _unlockTemporary; + late int _unlockCard; + late String _unlockAlarm; + late int _unlockRequest; + late int _residualElectricity; + late bool _reverseLock; + late int _unlockApp; + late bool _hijack; + late bool _doorbell; + late String _unlockOfflinePd; + late String _unlockOfflineClear; + late String _unlockDoubleKit; + late String _remoteNoPdSetkey; + late String _remoteNoDpKey; + late bool _normalOpenSwitch; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'unlock_fingerprint') { + _unlockFingerprint = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'unlock_password') { + _unlockPassword = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'unlock_temporary') { + _unlockTemporary = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'unlock_card') { + _unlockCard = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'alarm_lock') { + _unlockAlarm = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'unlock_request') { + _unlockRequest = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'residual_electricity') { + _residualElectricity = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'reverse_lock') { + _reverseLock = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'unlock_app') { + _unlockApp = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'hijack') { + _hijack = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'doorbell') { + _doorbell = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'unlock_offline_pd') { + _unlockOfflinePd = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'unlock_offline_clear') { + _unlockOfflineClear = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'unlock_double_kit') { + _unlockDoubleKit = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'remote_no_pd_setkey') { + _remoteNoPdSetkey = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'remote_no_dp_key') { + _remoteNoDpKey = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'normal_open_switch') { + _normalOpenSwitch = jsonList[i].value ?? false; + } + } + return SmartDoorModel( + unlockFingerprint: _unlockFingerprint, + unlockPassword: _unlockPassword, + unlockTemporary: _unlockTemporary, + unlockCard: _unlockCard, + unlockAlarm: _unlockAlarm, + unlockRequest: _unlockRequest, + residualElectricity: _residualElectricity, + reverseLock: _reverseLock, + unlockApp: _unlockApp, + hijack: _hijack, + doorbell: _doorbell, + unlockOfflinePd: _unlockOfflinePd, + unlockOfflineClear: _unlockOfflineClear, + unlockDoubleKit: _unlockDoubleKit, + remoteNoPdSetkey: _remoteNoPdSetkey, + remoteNoDpKey: _remoteNoDpKey, + normalOpenSwitch: _normalOpenSwitch); + } +} diff --git a/lib/features/devices/model/status_model.dart b/lib/features/devices/model/status_model.dart new file mode 100644 index 0000000..0d22625 --- /dev/null +++ b/lib/features/devices/model/status_model.dart @@ -0,0 +1,23 @@ +class StatusModel { + String? code; + dynamic value; + + StatusModel({ + required this.code, + required this.value, + }); + + factory StatusModel.fromJson(Map json) { + return StatusModel( + code: json['code'], + value: json['value'], + ); + } + + Map toJson() { + return { + 'code': code, + 'value': value, + }; + } +} diff --git a/lib/features/devices/model/temporary_password_model.dart b/lib/features/devices/model/temporary_password_model.dart new file mode 100644 index 0000000..40cda2c --- /dev/null +++ b/lib/features/devices/model/temporary_password_model.dart @@ -0,0 +1,37 @@ +class TemporaryPassword { + final int effectiveTime; + final int id; + final int invalidTime; + final String name; + final int phase; + final String phone; + final int sn; + final String timeZone; + final int type; + + TemporaryPassword({ + required this.effectiveTime, + required this.id, + required this.invalidTime, + required this.name, + required this.phase, + required this.phone, + required this.sn, + required this.timeZone, + required this.type, + }); + + factory TemporaryPassword.fromJson(Map json) { + return TemporaryPassword( + effectiveTime: json['effectiveTime'], + id: json['id'], + invalidTime: json['invalidTime'], + name: json['name'], + phase: json['phase'], + phone: json['phone'] ?? '', + sn: json['sn'], + timeZone: json['timeZone'] ?? '', + type: json['type'], + ); + } +} diff --git a/lib/features/devices/model/three_gang_model.dart b/lib/features/devices/model/three_gang_model.dart new file mode 100644 index 0000000..c862823 --- /dev/null +++ b/lib/features/devices/model/three_gang_model.dart @@ -0,0 +1,50 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class ThreeGangModel { + bool firstSwitch; + bool secondSwitch; + bool thirdSwitch; + int firstCountDown; + int secondCountDown; + int thirdCountDown; + + ThreeGangModel( + {required this.firstSwitch, + required this.secondSwitch, + required this.thirdSwitch, + required this.firstCountDown, + required this.secondCountDown, + required this.thirdCountDown}); + + factory ThreeGangModel.fromJson(List jsonList) { + late bool _firstSwitch; + late bool _secondSwitch; + late bool _thirdSwitch; + late int _firstCount; + late int _secondCount; + late int _thirdCount; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'switch_1') { + _firstSwitch = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'switch_2') { + _secondSwitch = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'switch_3') { + _thirdSwitch = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'countdown_1') { + _firstCount = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'countdown_2') { + _secondCount = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'countdown_3') { + _thirdCount = jsonList[i].value ?? 0; + } + } + return ThreeGangModel( + firstSwitch: _firstSwitch, + secondSwitch: _secondSwitch, + thirdSwitch: _thirdSwitch, + firstCountDown: _firstCount, + secondCountDown: _secondCount, + thirdCountDown: _thirdCount); + } +} diff --git a/lib/features/devices/model/wall_sensor_model.dart b/lib/features/devices/model/wall_sensor_model.dart new file mode 100644 index 0000000..78dc9be --- /dev/null +++ b/lib/features/devices/model/wall_sensor_model.dart @@ -0,0 +1,63 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class WallSensorModel { + String presenceState; + int farDetection; + int presenceTime; + int motionSensitivity; + int motionlessSensitivity; + int currentDistance; + int illuminance; + bool indicator; + + WallSensorModel({ + required this.presenceState, + required this.farDetection, + required this.presenceTime, + required this.motionSensitivity, + required this.motionlessSensitivity, + required this.currentDistance, + required this.illuminance, + required this.indicator, + }); + + factory WallSensorModel.fromJson(List jsonList) { + late String _presenceState; + late int _farDetection; + late int _presenceTime; + late int _motionSensitivity; + late int _motionlessSensitivity; + late int _currentDistance; + late int _illuminance; + late bool _indicator; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'presence_state') { + _presenceState = jsonList[i].value ?? 'none'; + } else if (jsonList[i].code == 'far_detection') { + _farDetection = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'presence_time') { + _presenceTime = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'motion_sensitivity_value') { + _motionSensitivity = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'motionless_sensitivity') { + _motionlessSensitivity = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'dis_current') { + _currentDistance = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'illuminance_value') { + _illuminance = jsonList[i].value ?? 0; + } else if (jsonList[i].code == 'indicator') { + _indicator = jsonList[i].value ?? false; + } + } + return WallSensorModel( + presenceState: _presenceState, + farDetection: _farDetection, + presenceTime: _presenceTime, + motionSensitivity: _motionSensitivity, + motionlessSensitivity: _motionlessSensitivity, + currentDistance: _currentDistance, + illuminance: _illuminance, + indicator: _indicator); + } +} diff --git a/lib/features/devices/view/widgets/ACs/ac_interface.dart b/lib/features/devices/view/widgets/ACs/ac_interface.dart new file mode 100644 index 0000000..02dee8d --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/ac_interface.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/ac_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface_controls.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class AcInterface extends StatelessWidget { + const AcInterface({super.key, required this.ac}); + + final DeviceModel ac; + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is AcsFailedState) { + CustomSnackBar.displaySnackBar(state.errorMessage); + } + }, + builder: (context, state) { + AcStatusModel statusModel = AcStatusModel( + uuid: ac.uuid ?? '', + acSwitch: true, + modeString: 'hot', + tempSet: 300, + currentTemp: 315, + fanSpeedsString: 'low', + childLock: false); + + if (state is GetAcStatusState) { + statusModel = state.acStatusModel; + } + if (state is AcChangeLoading) { + statusModel = state.acStatusModel; + } + if (state is AcModifyingState) { + statusModel = state.acStatusModel; + } + return + // Scaffold( + // backgroundColor: ColorsManager.backgroundColor, + // extendBodyBehindAppBar: true, + // extendBody: true, + // body: + ListView( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 20, + ), + DefaultContainer( + height: 65, + padding: const EdgeInsets.all(15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyLarge(text: statusModel.acSwitch ? 'On' : 'Off'), + GestureDetector( + onTap: () { + BlocProvider.of(context) + .add(AcSwitch(acSwitch: statusModel.acSwitch)); + }, + child: SvgPicture.asset(Assets.acSwitchIcon)) + ], + ), + ), + const SizedBox( + height: 10, + ), + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 380, + ), + child: AcInterfaceTempUnit( + acDevice: ac, + ), + ), + const SizedBox( + height: 10, + ), + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 130, + ), + child: AcInterfaceControls( + deviceModel: ac, + deviceStatus: statusModel, + ), + ), + ], + ), + ], + ); + // ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/ACs/ac_interface_controls.dart b/lib/features/devices/view/widgets/ACs/ac_interface_controls.dart new file mode 100644 index 0000000..0c04cd6 --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/ac_interface_controls.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/ac_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_mode_control_unit.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class AcInterfaceControls extends StatelessWidget { + const AcInterfaceControls({super.key, required this.deviceModel, required this.deviceStatus}); + + final DeviceModel deviceModel; + final AcStatusModel deviceStatus; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + String lockIconName = + deviceStatus.childLock ? Assets.assetsIconsLock : Assets.assetsIconsUnLock; + + return Column( + children: [ + ACModeControlUnit( + acStatus: deviceStatus, + deviceId: deviceModel.uuid ?? '', + ), + const SizedBox(height: 10), + Row( + children: [ + Flexible( + child: GestureDetector( + onTap: () {}, + child: DefaultContainer( + height: 55, + child: Center( + child: SvgPicture.asset(Assets.assetsIconsAutomatedClock), + ), + ), + ), + ), + const SizedBox(width: 10), + Flexible( + child: GestureDetector( + onTap: () { + BlocProvider.of(context) + .add(ChangeLock(lockBool: deviceStatus.childLock)); + }, + child: DefaultContainer( + height: 55, + child: Center( + child: SvgPicture.asset(lockIconName), + ), + ), + ), + ), + ], + ) + ], + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart b/lib/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart new file mode 100644 index 0000000..3ae38f8 --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/ac_interface_temp_unit.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:sleek_circular_slider/sleek_circular_slider.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class AcInterfaceTempUnit extends StatelessWidget { + const AcInterfaceTempUnit({ + super.key, + required this.acDevice, + }); + + final DeviceModel acDevice; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return DefaultContainer( + child: Column( + children: [ + Expanded( + flex: 8, + child: SizedBox( + width: 300, + child: SleekCircularSlider( + appearance: CircularSliderAppearance( + customWidths: CustomSliderWidths( + progressBarWidth: 15, + trackWidth: 15, + shadowWidth: 0, + ), + customColors: CustomSliderColors( + progressBarColor: ColorsManager.primaryColor.withOpacity(.6), + trackColor: ColorsManager.greyColor, + dotColor: Colors.transparent, + ), + infoProperties: InfoProperties( + bottomLabelText: 'CURRENT TEMP', + bottomLabelStyle: context.bodyLarge.copyWith( + color: Colors.grey, + fontWeight: FontsManager.regular, + ), + modifier: (double value) { + double temp = state is GetAcStatusState + ? state.acStatusModel.currentTemp / 10 + : state is AcModifyingState + ? state.acStatusModel.currentTemp / 10 + : state is AcChangeLoading + ? state.acStatusModel.currentTemp / 10 + : 25; + return '$temp°C'; + }, + mainLabelStyle: context.titleLarge.copyWith( + height: 0, + letterSpacing: -3, + color: Colors.black, + fontSize: 60, + shadows: [ + Shadow( + color: Colors.black.withOpacity(0.5), + offset: const Offset(0, 3), + blurRadius: 6, + ), + ], + ), + ), + ), + min: 20, + max: 30, + initialValue: state is GetAcStatusState + ? state.acStatusModel.tempSet / 10 + : state is AcModifyingState + ? state.acStatusModel.tempSet / 10 + : state is AcChangeLoading + ? state.acStatusModel.tempSet / 10 + : 25, + ), + ), + ), + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox.square( + dimension: 24, + child: InkWell( + onTap: () { + if (state is AcChangeLoading) { + return; + } + + double tempValue = 0; + if (state is AcModifyingState) { + tempValue = state.acStatusModel.tempSet / 10; + } else if (state is GetAcStatusState) { + tempValue = state.acStatusModel.tempSet / 10; + } + + if (tempValue > 20) { + BlocProvider.of(context) + .add(DecreaseCoolToTemp(value: tempValue)); + } + }, + child: SvgPicture.asset( + Assets.assetsIconsMinus, + ), + ), + ), + Column( + children: [ + BodyLarge( + // text: "${DeviceModel.coolTo}° C", + text: state is GetAcStatusState + ? '${state.acStatusModel.tempSet / 10}° C' + : state is AcModifyingState + ? '${state.acStatusModel.tempSet / 10}° C' + : state is AcChangeLoading + ? '${state.acStatusModel.tempSet / 10}° C' + : '', + style: context.bodyLarge.copyWith( + color: state is AcsLoadingState + ? Colors.grey + : ColorsManager.primaryColor.withOpacity(0.6), + fontWeight: FontsManager.bold, + fontSize: 30, + height: 0), + ), + const BodyMedium( + text: 'COOL TO', + fontSize: 18, + ) + ], + ), + SizedBox.square( + dimension: 24, + child: InkWell( + onTap: () { + if (state is AcChangeLoading) { + return; + } + double tempValue = 0; + if (state is AcModifyingState) { + tempValue = state.acStatusModel.tempSet / 10; + } else if (state is GetAcStatusState) { + tempValue = state.acStatusModel.tempSet / 10; + } else if (state is AcChangeLoading) { + tempValue = state.acStatusModel.tempSet / 10; + } + + if (tempValue < 30) { + BlocProvider.of(context) + .add(IncreaseCoolToTemp(value: tempValue)); + } + }, + child: SvgPicture.asset( + Assets.assetsIconsPlus, + height: 24, + width: 24, + ), + ), + ), + ], + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/ACs/ac_mode_control_unit.dart b/lib/features/devices/view/widgets/ACs/ac_mode_control_unit.dart new file mode 100644 index 0000000..24f1737 --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/ac_mode_control_unit.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/ac_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class ACModeControlUnit extends StatelessWidget { + const ACModeControlUnit( + {super.key, required this.acStatus, required this.deviceId, this.productId = ''}); + + final AcStatusModel acStatus; + final String deviceId; + final String? productId; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Row( + children: [ + Flexible( + child: GestureDetector( + onTap: () { + if (state is! AcChangeLoading && state is! AcsLoadingState) { + BlocProvider.of(context).add(ChangeFanSpeed( + fanSpeeds: acStatus.acFanSpeed, + deviceId: deviceId, + productId: productId ?? '')); + } + // else if (state is AcModifyingState) { + // BlocProvider.of(context) + // .add(ChangeFanSpeed(fanSpeeds: state.acStatusModel.acFanSpeed)); + // } + }, + child: DefaultContainer( + height: 55, + child: Center(child: SvgPicture.asset(fanSpeedsIconMap[acStatus.acFanSpeed]!)), + ), + ), + ), + const SizedBox(width: 10), + Flexible( + child: GestureDetector( + onTap: () { + // if (state is GetAcStatusState) { + // BlocProvider.of(context) + // .add(ChangeAcMode(tempModes: state.acStatusModel.acMode)); + // } else if (state is AcModifyingState) { + // BlocProvider.of(context) + // .add(ChangeAcMode(tempModes: state.acStatusModel.acMode)); + // } + if (state is! AcChangeLoading && state is! AcsLoadingState) { + BlocProvider.of(context).add(ChangeAcMode( + tempModes: acStatus.acMode, + deviceId: deviceId, + productId: productId ?? '')); + } + }, + child: DefaultContainer( + height: 55, + child: Center( + child: SvgPicture.asset(tempModesIconMap[acStatus.acMode]!), + ), + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/ACs/ac_temp_widget.dart b/lib/features/devices/view/widgets/ACs/ac_temp_widget.dart new file mode 100644 index 0000000..d56ba22 --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/ac_temp_widget.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class ACTempWidget extends StatelessWidget { + const ACTempWidget({ + required this.deviceModel, + required this.temp, + super.key, + }); + + final DeviceModel deviceModel; + final int temp; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return DefaultContainer( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox.square( + dimension: 24, + child: InkWell( + onTap: () { + double tempC = temp / 10; + BlocProvider.of(context).add(DecreaseCoolToTemp( + value: tempC, + deviceId: deviceModel.uuid ?? '', + productId: deviceModel.productUuid ?? '')); + }, + child: SvgPicture.asset( + Assets.assetsIconsMinus, + ), + ), + ), + BodyLarge( + text: '${temp / 10} °C', + style: context.bodyLarge.copyWith( + color: ColorsManager.primaryColor.withOpacity(0.6), + fontSize: 23, + ), + ), + SizedBox.square( + dimension: 24, + child: InkWell( + onTap: () { + double tempC = temp / 10; + BlocProvider.of(context).add(IncreaseCoolToTemp( + value: tempC, + deviceId: deviceModel.uuid ?? '', + productId: deviceModel.productUuid ?? '')); + }, + child: SvgPicture.asset( + Assets.assetsIconsPlus, + height: 24, + width: 24, + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/ACs/acs_list.dart b/lib/features/devices/view/widgets/ACs/acs_list.dart new file mode 100644 index 0000000..e58f679 --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/acs_list.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/ac_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_mode_control_unit.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_temp_widget.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/universal_ac_temp.dart'; +import 'package:syncrow_app/features/devices/view/widgets/universal_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class ACsList extends StatelessWidget { + const ACsList({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is AcsFailedState) { + CustomSnackBar.displaySnackBar(state.errorMessage); + } + }, + builder: (context, state) { + List devicesStatuesList = []; + List devicesList = []; + bool allOn = false; + bool allTempSame = false; + int temperature = 20; + if (state is GetAllAcsStatusState) { + devicesStatuesList = state.allAcsStatues; + devicesList = state.allAcs; + allOn = state.allOn; + allTempSame = state.allTempSame; + temperature = state.temp; + } + return SingleChildScrollView( + child: + // state is AcChangeLoading || state is AcsLoadingState + // ? const Center( + // child: + // DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), + // ) + // : + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // universal AC controller + const SizedBox(height: 10), + const BodySmall(text: "All ACs"), + const SizedBox(height: 5), + UniversalSwitch( + allOn: allOn, + ), + const SizedBox(height: 10), + UniversalACTemp( + allTempSame: allTempSame, + temp: temperature, + ), + const SizedBox(height: 10), + + // other ACs controls + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0), + itemCount: devicesList.length, + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 10, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + BodySmall(text: devicesList[index].name ?? ''), + ], + ), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: devicesStatuesList[index].acSwitch, + action: () { + BlocProvider.of(context).add(AcSwitch( + acSwitch: devicesStatuesList[index].acSwitch, + deviceId: devicesList[index].uuid ?? '', + productId: devicesList[index].productUuid ?? '')); + }, + ), + const SizedBox(height: 10), + ACTempWidget( + deviceModel: devicesList[index], + temp: devicesStatuesList[index].tempSet, + ), + const SizedBox(height: 10), + ACModeControlUnit( + acStatus: devicesStatuesList[index], + deviceId: devicesList[index].uuid ?? '', + productId: devicesList[index].productUuid ?? '', + ), + const SizedBox(height: 10), + ], + ); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/ACs/acs_view.dart b/lib/features/devices/view/widgets/ACs/acs_view.dart new file mode 100644 index 0000000..be84084 --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/acs_view.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +import '../device_appbar.dart'; + +class ACsView extends StatelessWidget { + final DeviceModel? deviceModel; + const ACsView({this.deviceModel, super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ACsBloc(acId: deviceModel?.uuid ?? '') + ..add(AcsInitial(allAcs: deviceModel != null ? false : true)), + child: BlocBuilder( + builder: (context, state) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: deviceModel != null + ? DeviceAppbar( + deviceName: deviceModel!.name!, + deviceUuid: deviceModel!.uuid!, + ) + : AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: deviceModel?.name ?? "ACs", + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + padding: const EdgeInsets.all(Constants.defaultPadding), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: state is AcsLoadingState + ? const Center( + child: DefaultContainer( + width: 50, height: 50, child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + BlocProvider.of(context) + .add(AcsInitial(allAcs: deviceModel != null ? false : true)); + }, + child: Container( + padding: const EdgeInsets.only(top: 40), + alignment: AlignmentDirectional.center, + child: deviceModel != null + ? AcInterface(ac: deviceModel!) + : const ACsList(), + ), + ), + ), + )); + }, + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/ACs/category_view_app_bar.dart b/lib/features/devices/view/widgets/ACs/category_view_app_bar.dart new file mode 100644 index 0000000..4a4e01f --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/category_view_app_bar.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/display_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class CategoryViewAppBar extends StatelessWidget implements PreferredSizeWidget { + final String title; + const CategoryViewAppBar({ + required this.title, + super.key, + }); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + toolbarHeight: Constants.appBarHeight, + centerTitle: true, + title: DisplayMedium( + // text: DevicesCubit.getInstance().chosenCategory!.name!, + text: title, + style: context.displayMedium + .copyWith(color: ColorsManager.primaryColor, fontWeight: FontWeight.bold, fontSize: 14), + ), + leading: IconButton( + icon: const Icon( + Icons.arrow_back_ios, + color: ColorsManager.textPrimaryColor, + ), + onPressed: () { + Navigator.of(context).pop(); + // DevicesCubit.getInstance().clearCategoriesSelection(context); + }, + ), + ); + } + + @override + Size get preferredSize => Size.fromHeight(Constants.appBarHeight); +} diff --git a/lib/features/devices/view/widgets/ACs/universal_ac_temp.dart b/lib/features/devices/view/widgets/ACs/universal_ac_temp.dart new file mode 100644 index 0000000..37779b6 --- /dev/null +++ b/lib/features/devices/view/widgets/ACs/universal_ac_temp.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class UniversalACTemp extends StatelessWidget { + const UniversalACTemp({super.key, required this.temp, required this.allTempSame}); + final int temp; + final bool allTempSame; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return DefaultContainer( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox.square( + dimension: 24, + child: InkWell( + onTap: () { + double temperature = temp / 10; + BlocProvider.of(context).add(DecreaseAllTemp(value: temperature)); + }, + child: SvgPicture.asset( + Assets.assetsIconsMinus, + ), + ), + ), + BodyLarge( + text: '${allTempSame ? temp / 10 : '-'} °C', + style: context.bodyLarge.copyWith( + color: ColorsManager.primaryColor.withOpacity(0.6), + fontSize: 23, + ), + ), + SizedBox.square( + dimension: 24, + child: InkWell( + onTap: () { + double temperature = temp / 10; + BlocProvider.of(context).add(IncreaseAllTemp(value: temperature)); + }, + child: SvgPicture.asset( + Assets.assetsIconsPlus, + height: 24, + width: 24, + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart b/lib/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart new file mode 100644 index 0000000..564779f --- /dev/null +++ b/lib/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart @@ -0,0 +1,403 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart'; +import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart'; +import 'package:syncrow_app/features/devices/model/ceiling_sensor_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/misc_string_helpers.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class CeilingSensorInterface extends StatelessWidget { + const CeilingSensorInterface({super.key, required this.ceilingSensor}); + final DeviceModel ceilingSensor; + @override + Widget build(BuildContext context) { + // String state = ceilingSensor.status + // .firstWhere((element) => element.code == "presence_state") + // .value + // .toString(); + + return BlocProvider( + create: (context) => + CeilingSensorBloc(deviceId: ceilingSensor.uuid ?? '')..add(InitialEvent()), + child: BlocBuilder(builder: (context, state) { + CeilingSensorModel ceilingSensorModel = CeilingSensorModel( + presenceState: 'none', + sensitivity: 1, + checkingResult: '', + presenceRange: 1, + sportsPara: 1, + bodyMovement: 'none'); + if (state is UpdateState) { + ceilingSensorModel = state.ceilingSensorModel; + } else if (state is LoadingNewSate) { + ceilingSensorModel = state.ceilingSensorModel; + } + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: DeviceAppbar( + deviceName: ceilingSensor.name!, + deviceUuid: ceilingSensor.uuid!, + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + padding: const EdgeInsets.all(Constants.defaultPadding), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: state is LoadingInitialState + ? const Center( + child: RefreshProgressIndicator(), + ) + : SafeArea( + child: RefreshIndicator( + onRefresh: () async { + BlocProvider.of(context).add(InitialEvent()); + }, + child: Container( + child: ListView( + shrinkWrap: true, + children: [ + Container( + height: MediaQuery.of(context).size.height, + child: Column( + children: [ + Expanded( + child: Column( + children: [ + // InkWell( + // onTap: () { + // if ((ceilingSensor.isOnline ?? false) == false) { + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar( + // content: Text( + // 'Device is offline', + // ), + // backgroundColor: Colors.red, + // ), + // ); + // return; + // } + // String controlCode = 'sensitivity'; + // showDialog( + // context: context, + // builder: (context) => ParameterControlDialog( + // title: 'Sensitivity', + // sensor: ceilingSensor, + // controlCode: controlCode, + // value: ceilingSensor.status + // .firstWhere((element) => element.code == controlCode) + // .value as int, + // min: ceilingSensor.functions + // .firstWhere((element) => element.code == controlCode) + // .values + // ?.min ?? + // 0, + // max: ceilingSensor.functions + // .firstWhere((element) => element.code == controlCode) + // .values + // ?.max ?? + // 0, + // ), + // ); + // }, + // child: + // ), + + SvgPicture.asset( + ceilingSensorModel.presenceState.toLowerCase() == + 'motion' + ? Assets + .assetsIconsPresenceSensorAssetsPresenceSensorMotion + : Assets.assetsIconsPresenceSensorAssetsPresence, + width: 100, + height: 100, + // colorFilter: ColorFilter.mode( + // (ceilingSensor.isOnline ?? false) + // ? ColorsManager.primaryColor + // : Colors.grey.withOpacity(0.9), + // BlendMode.srcIn, + // ), + ), + const SizedBox( + height: 10, + ), + BodyMedium( + text: StringHelpers.toTitleCase( + ceilingSensorModel.presenceState), + // (ceilingSensor.isOnline ?? false) + // ? StringHelpers.toTitleCase(ceilingSensor.status + // .firstWhere((element) => + // element.code == 'presence_state') + // .value + // .toString()) + // : "Offline", + style: context.bodyMedium.copyWith( + fontWeight: FontsManager.bold, + ), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + children: [ + DefaultContainer( + padding: const EdgeInsets.symmetric( + vertical: 20, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BodySmall(text: 'Sports Para'), + BodyLarge( + text: ceilingSensorModel.sportsPara + .toString(), + style: context.bodyLarge.copyWith( + fontWeight: FontsManager.bold, + ), + ), + ], + ), + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10), + child: Container( + width: 1, + height: 45, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BodySmall( + text: 'Detection Range', + textOverflow: TextOverflow.ellipsis, + ), + BodyLarge( + text: + '${ceilingSensorModel.presenceRange}M', + textOverflow: TextOverflow.ellipsis, + style: context.bodyLarge.copyWith( + fontWeight: FontsManager.bold, + ), + ), + ], + ), + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10), + child: Container( + width: 1, + height: 45, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BodySmall( + text: 'Movement', + textOverflow: TextOverflow.ellipsis, + ), + BodyLarge( + text: ceilingSensorModel.bodyMovement, + textOverflow: TextOverflow.ellipsis, + style: context.bodyLarge.copyWith( + fontWeight: FontsManager.bold, + ), + ), + ], + ), + ), + ], + )), + const SizedBox( + height: 15, + ), + for (int index = 0; + index < ceilingSensorButtons().length; + index++) + DefaultContainer( + margin: const EdgeInsets.only(bottom: 5), + padding: const EdgeInsets.symmetric( + vertical: 12, horizontal: 20), + onTap: () async { + if (ceilingSensorButtons()[index]['title'] == + 'Sensitivity') { + final result = await showDialog( + context: context, + builder: (context) { + return ParameterControlDialog( + title: ceilingSensorButtons()[index] + ['title'] + .toString(), + sensor: ceilingSensor, + value: ceilingSensorModel.sensitivity, + min: 0, + max: 10, + ); + }, + ); + if (result != null) { + BlocProvider.of(context).add( + ChangeValueEvent( + value: result, code: 'sensitivity')); + } + } + + // if (ceilingSensorButtons[index]['page'] != null) { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => + // ceilingSensorButtons[index]['page'] as Widget, + // ), + // ); + // } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + ceilingSensorButtons()[index]['icon'] + as String, + // width: 30, + // height: 50, + ), + const SizedBox( + width: 25, + ), + BodyMedium( + text: ceilingSensorButtons()[index]['title'] + as String, + style: context.bodyMedium.copyWith( + fontWeight: FontsManager.bold, + ), + ), + ], + ), + if (ceilingSensorButtons()[index]['withArrow'] == + true) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(ceilingSensorButtons( + sensitivity_val: ceilingSensorModel + .sensitivity + .toString())[index]['val'] + .toString()), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ))), + ), + ); + }), + ); + } + + dynamic ceilingSensorButtons({ + sensitivity_val = 0, + spaceType = 'Office', + maximumDistanceVal = '3.9m', + nobodyTimeVal = '1hr', + }) => + [ + { + 'title': 'Space Type', + 'icon': Assets.spaceTypeIcon, + 'page': null, + 'withArrow': true, + 'val': spaceType + }, + { + 'title': 'Sensitivity', + 'icon': Assets.sensitivityIcon, + 'page': null, + 'withArrow': true, + 'val': sensitivity_val + }, + { + 'title': 'Maximum Distance', + 'icon': Assets.maximumDistanceIcon, + 'page': null, + 'withArrow': true, + 'val': maximumDistanceVal, + }, + { + 'title': 'Nobody Time', + 'icon': Assets.assetsIconsPresenceSensorAssetsEmpty, + 'page': null, + 'withArrow': true, + 'val': nobodyTimeVal, + }, + { + 'title': 'Induction History', + 'icon': Assets.assetsIconsPresenceSensorAssetsInductionRecording, + 'page': null, + 'withArrow': false, + }, + { + 'title': 'Help Description', + 'icon': Assets.assetsIconsPresenceSensorAssetsHelpDescription, + 'page': null, + 'withArrow': false, + }, + ]; +} diff --git a/lib/features/devices/view/widgets/ceiling_sensor/parameter_control_dialog.dart b/lib/features/devices/view/widgets/ceiling_sensor/parameter_control_dialog.dart new file mode 100644 index 0000000..d9f011e --- /dev/null +++ b/lib/features/devices/view/widgets/ceiling_sensor/parameter_control_dialog.dart @@ -0,0 +1,171 @@ +part of '../wall_sensor/wall_sensor_interface.dart'; + +class ParameterControlDialog extends StatefulWidget { + final String title; + final DeviceModel sensor; + final int value; + final int min; + final int max; + + const ParameterControlDialog({ + super.key, + required this.title, + required this.sensor, + required this.value, + required this.min, + required this.max, + }); + + @override + ParameterControlDialogState createState() => ParameterControlDialogState(); +} + +class ParameterControlDialogState extends State { + late int _value; + + @override + void initState() { + super.initState(); + _value = widget.value; + } + + @override + Widget build(BuildContext context) { + return Dialog( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + BodyMedium( + text: widget.title, + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontsManager.extraBold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 50, + ), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + child: TitleMedium( + text: _value.toString(), + style: context.titleMedium.copyWith( + color: Colors.black, + fontWeight: FontsManager.bold, + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: () { + setState(() { + _value = (_value - 1).clamp(widget.min, widget.max); + }); + }, + icon: const Icon( + Icons.remove, + color: Colors.grey, + ), + ), + Slider( + value: _value.toDouble(), + onChanged: (value) { + setState(() { + _value = value.toInt(); + }); + }, + min: widget.min.toDouble(), + max: widget.max.toDouble(), + label: _value.toString(), + inactiveColor: ColorsManager.greyColor, + ), + IconButton( + onPressed: () { + setState(() { + _value = (_value + 1).clamp(widget.min, widget.max); + }); + }, + icon: const Icon( + Icons.add, + color: Colors.grey, + ), + ), + ], + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Center( + child: BodyMedium( + text: 'Cancel', + style: context.bodyMedium.copyWith(color: ColorsManager.greyColor), + ), + ), + ), + Container( + height: 50, + width: 1, + color: ColorsManager.greyColor, + ), + InkWell( + onTap: () { + Navigator.pop(context, _value); + if (widget.sensor.isOnline == null) { + CustomSnackBar.displaySnackBar('The device is offline'); + return; + } + if (!widget.sensor.isOnline!) { + CustomSnackBar.displaySnackBar('The device is offline'); + return; + } + + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: widget.sensor.uuid, + // code: widget.controlCode, + // value: widget.value), + // widget.sensor.uuid ?? ''); + }, + child: Center( + child: BodyMedium( + text: 'Confirm', + style: + context.bodyMedium.copyWith(color: ColorsManager.primaryColorWithOpacity), + ), + ), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/curtains/curtain_buttons.dart b/lib/features/devices/view/widgets/curtains/curtain_buttons.dart new file mode 100644 index 0000000..d1e0a06 --- /dev/null +++ b/lib/features/devices/view/widgets/curtains/curtain_buttons.dart @@ -0,0 +1,75 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_event.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class CurtainButtons extends StatelessWidget { + const CurtainButtons({super.key, required this.curtain}); + final DeviceModel curtain; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildButton( + onTap: () => context.read().add(OpenCurtain(curtain.productType!)), + iconPath: Assets.assetsIconsCurtainsIconOpenCurtain, + ), + _buildButton( + onTap: () => context.read().add(PauseCurtain()), + iconPath: Assets.assetsImagesPause, + isSvg: false, + ), + _buildButton( + onTap: () => context.read().add(CloseCurtain(curtain.productType!)), + iconPath: Assets.assetsIconsCurtainsIconCloseCurtain, + ), + ], + ); + } + + Widget _buildButton({ + required VoidCallback onTap, + required String iconPath, + bool isSvg = true, + }) { + return Stack( + alignment: Alignment.center, + children: [ + DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(3, 3), + ), + ], + ), + child: InkWell( + overlayColor: MaterialStateProperty.all(Colors.transparent), + onTap: onTap, + child: const SizedBox.square(dimension: 60), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 5, bottom: 5), + child: InkWell( + overlayColor: MaterialStateProperty.all(Colors.transparent), + onTap: onTap, + child: isSvg + ? SvgPicture.asset(iconPath, width: 110, height: 110) + : Image.asset(iconPath, width: 60, height: 60), + ), + ), + ], + ); + } +} diff --git a/lib/features/devices/view/widgets/curtains/curtain_list.dart b/lib/features/devices/view/widgets/curtains/curtain_list.dart new file mode 100644 index 0000000..bf3900d --- /dev/null +++ b/lib/features/devices/view/widgets/curtains/curtain_list.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/universal_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class CurtainList extends StatelessWidget { + const CurtainList({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // universal AC controller + const SizedBox(height: 10), + //TODO: move to strings manager + const BodySmall(text: "All Curtains"), + const SizedBox(height: 5), + // UniversalSwitch( + // category: DevicesCubit.get(context).chosenCategory!, + // ), + + // other ACs controls + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0), + // itemCount: + // DevicesCubit.get(context).chosenCategory!.devices!.length, + itemBuilder: (context, index) { + // DeviceModel curtain = + // DevicesCubit.get(context).chosenCategory!.devices![index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + // BodySmall( + // text: DevicesCubit.get(context) + // .chosenCategory! + // .devices![index] + // .name ?? + // ""), + const SizedBox(height: 5), + // DevicesDefaultSwitch( + // model: curtain, + // ), + ], + ); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/curtains/curtain_list_view.dart b/lib/features/devices/view/widgets/curtains/curtain_list_view.dart new file mode 100644 index 0000000..c47c862 --- /dev/null +++ b/lib/features/devices/view/widgets/curtains/curtain_list_view.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/category_view_app_bar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; + +class CurtainListView extends StatelessWidget { + const CurtainListView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return const DefaultScaffold( + appBar: CategoryViewAppBar( + title: '', + ), + child: CurtainList(), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/curtains/curtain_view.dart b/lib/features/devices/view/widgets/curtains/curtain_view.dart new file mode 100644 index 0000000..153bce8 --- /dev/null +++ b/lib/features/devices/view/widgets/curtains/curtain_view.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_event.dart'; +import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_buttons.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class CurtainView extends StatelessWidget { + const CurtainView({super.key, required this.curtain}); + final DeviceModel curtain; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => CurtainBloc(curtain.uuid!)..add(InitCurtain()), + child: BlocBuilder( + builder: (context, state) { + double curtainWidth = 270; + double blindHeight = 310; + if (state is CurtainsOpening) { + curtainWidth = state.curtainWidth; + blindHeight = state.blindHeight; + } else if (state is CurtainsClosing) { + curtainWidth = state.curtainWidth; + blindHeight = state.blindHeight; + } else if (state is CurtainsPaused) { + curtainWidth = state.curtainWidth; + blindHeight = state.blindHeight; + } + return DefaultScaffold( + title: curtain.name, + child: Column( + children: [ + Stack( + alignment: Alignment.centerLeft, + children: [ + Container( + height: 340, + width: 365, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesWindow, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(40), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.linear, + height: 310, + width: curtainWidth, + child: Stack( + children: List.generate( + 10, (index) { + double spacing = curtainWidth / 9; + double leftMostPosition = index * spacing; + return AnimatedPositioned( + duration: const Duration(milliseconds: 200), + curve: Curves.linear, + left: leftMostPosition, + child: SizedBox( + height: 320, + width: 32, + child: SvgPicture.asset( + Assets.assetsIconsCurtainsIconVerticalBlade, + fit: BoxFit.fill, + ), + ), + ); + }, + ), + ), + ), + ), + Positioned( + top: 27, + left: 43, + child: SvgPicture.asset( + Assets.assetsIconsCurtainsIconCurtainHolder, + ), + ), + ], + ), + const SizedBox(height: 80), + CurtainButtons(curtain: curtain), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/device_appbar.dart b/lib/features/devices/view/widgets/device_appbar.dart new file mode 100644 index 0000000..7de34d7 --- /dev/null +++ b/lib/features/devices/view/widgets/device_appbar.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/view/widgets/popup_menu_widget.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class DeviceAppbar extends StatelessWidget implements PreferredSizeWidget { + final String deviceName; + final String deviceUuid; + final double appBarHeight = 56.0; + final void Function()? onPressed; + const DeviceAppbar({super.key, required this.deviceName, required this.deviceUuid,this.onPressed}); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: deviceName ?? "", + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + actions: [ + IconButton(onPressed: () { + showPopupMenu(context: context, items: [ + PopupMenuItem( + onTap: () async { + HomeCubit.getInstance().updateDevice(deviceUuid); + }, + value: 'Update', + child: const Text('Update'), + ) + ]); + }, icon: Icon(Icons.edit)) + ], + ); + } + + @override + Size get preferredSize => Size.fromHeight(appBarHeight); +} diff --git a/lib/features/devices/view/widgets/devices_mode_tab.dart b/lib/features/devices/view/widgets/devices_mode_tab.dart new file mode 100644 index 0000000..12d82ae --- /dev/null +++ b/lib/features/devices/view/widgets/devices_mode_tab.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class DevicesModeTab extends StatelessWidget { + const DevicesModeTab({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(10), + margin: const EdgeInsets.only(right: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 30, + width: 25, + child: SvgPicture.asset( + Assets.assetsIconsWinter, + fit: BoxFit.contain, + ), + ), + const SizedBox(width: 5), + const BodySmall( + text: StringsManager.winter, + fontWeight: FontWeight.bold, + fontColor: Colors.grey, + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(10), + margin: const EdgeInsets.only(right: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 30, + width: 25, + child: SvgPicture.asset( + Assets.assetsIconsSummer, + fit: BoxFit.contain, + ), + ), + const SizedBox(width: 5), + const BodySmall( + text: StringsManager.summer, + fontWeight: FontWeight.bold, + fontColor: Colors.grey, + ), + ], + ), + ), + const Expanded(child: SizedBox.shrink()) + ], + ); + } +} diff --git a/lib/features/devices/view/widgets/devices_view_body.dart b/lib/features/devices/view/widgets/devices_view_body.dart new file mode 100644 index 0000000..9ee89a6 --- /dev/null +++ b/lib/features/devices/view/widgets/devices_view_body.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/view/widgets/room_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/rooms_slider.dart'; +import 'package:syncrow_app/features/devices/view/widgets/wizard_page.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'package:syncrow_app/features/scene/view/scene_view.dart'; +import 'package:syncrow_app/features/shared_widgets/create_unit.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; +class DevicesViewPage extends StatelessWidget { + const DevicesViewPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SceneBloc(), // Initialize your SceneBloc here + child: DevicesViewBody(), + ); + } +} + +class DevicesViewBody extends StatelessWidget { + const DevicesViewBody({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is DevicesLoading || + state is GetDevicesLoading || + state is DevicesCategoriesLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return HomeCubit.getInstance().spaces?.isEmpty ?? true + ? const CreateUnitWidget() + : Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + TitleMedium( + text: StringsManager.devices, + style: context.titleMedium.copyWith( + fontSize: 25, + ), + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).size.height*0.1, + child: const SceneView(pageType: true,) + ), + const SizedBox( + height: 20, + ), + const RoomsSlider(), + const SizedBox( + height: 10, + ), + + Expanded( + child: PageView( + controller: HomeCubit.getInstance().devicesPageController, + onPageChanged: (index) { + HomeCubit.getInstance().devicesPageChanged(index); + }, + children: [ + WizardPage( + groupsList: DevicesCubit.getInstance().allCategories ?? [], + ), + if (HomeCubit.getInstance().selectedSpace != null) + if (HomeCubit.getInstance().selectedSpace!.rooms != null) + ...HomeCubit.getInstance().selectedSpace!.rooms!.map( + (room) { + return RoomPage( + room: room, + ); + }, + ) + ], + ), + ), + HomeCubit.getInstance().selectedSpace != null + ? Padding( + padding: const EdgeInsets.symmetric( + vertical: 7, + ), + child: SmoothPageIndicator( + controller: HomeCubit.getInstance().devicesPageController, + count: HomeCubit.getInstance().selectedSpace!.rooms!.length + 1, + effect: const WormEffect( + paintStyle: PaintingStyle.stroke, + dotHeight: 8, + dotWidth: 8, + ), + ), + ) + : const Center( + child: BodyLarge(text: 'No Home Found'), + ), + ], + ); + } + }, + ); + } +} \ No newline at end of file diff --git a/lib/features/devices/view/widgets/devices_view_header.dart b/lib/features/devices/view/widgets/devices_view_header.dart new file mode 100644 index 0000000..ed24896 --- /dev/null +++ b/lib/features/devices/view/widgets/devices_view_header.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/view/widgets/devices_mode_tab.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class DevicesViewHeader extends StatelessWidget { + const DevicesViewHeader({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleMedium( + text: StringsManager.devices, + style: context.titleMedium.copyWith( + fontSize: 25, + ), + ), + const DevicesModeTab(), + const SizedBox( + height: 20, + ), + ], + ); + } +} diff --git a/lib/features/devices/view/widgets/gateway/gateway_view.dart b/lib/features/devices/view/widgets/gateway/gateway_view.dart new file mode 100644 index 0000000..62ce1ed --- /dev/null +++ b/lib/features/devices/view/widgets/gateway/gateway_view.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/gateway_bloc/gateway_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/gateway_bloc/gateway_event.dart'; +import 'package:syncrow_app/features/devices/bloc/gateway_bloc/gateway_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/room_page_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class GateWayView extends StatelessWidget { + final DeviceModel gatewayObj; + + const GateWayView({required this.gatewayObj, super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => GatewayBloc()..add(GatewayInitial(gatewayId: gatewayObj.uuid ?? '')), + child: BlocBuilder(builder: (context, state) { + List devicesList = []; + if (state is UpdateGatewayState) { + devicesList = state.list; + } + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar:DeviceAppbar( + deviceName: 'Gateway', + deviceUuid: gatewayObj.uuid!,), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + padding: const EdgeInsets.all(Constants.defaultPadding), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: RefreshIndicator( + onRefresh:()async { + BlocProvider.of(context).add(GatewayInitial(gatewayId: gatewayObj.uuid ?? '')); + }, + child: ListView( + + children: [ + Container( + height: MediaQuery.of(context).size.height, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: MediaQuery.sizeOf(context).height * 0.3, + width: MediaQuery.sizeOf(context).width, + alignment: AlignmentDirectional.center, + child: Stack(children: [ + SvgPicture.asset( + Assets.gatewayIcon, + width: 125, + height: 125, + ), + Positioned( + right: 30, + top: 15, + child: Container( + width: 12, + height: 12, + decoration: const BoxDecoration( + color: ColorsManager.lightGreen, + shape: BoxShape.circle, + ), + ), + ), + ]), + ), + if (devicesList.isEmpty && state is UpdateGatewayState) + Container( + width: MediaQuery.sizeOf(context).width, + alignment: AlignmentDirectional.center, + child: const BodyMedium( + text: 'No devices found', + fontSize: 15, + fontWeight: FontWeight.w700, + ), + ), + if (devicesList.isNotEmpty) + const BodyMedium( + text: 'Zigbee Devices', + fontSize: 15, + fontWeight: FontWeight.w700, + ), + const SizedBox( + height: 10, + ), + state is GatewayLoadingState + ? Center( + child: Container( + margin: const EdgeInsets.only(top: 20), + height: 50, + width: 50, + child: const RefreshProgressIndicator()), + ) + : Expanded( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.5, + ), + padding: const EdgeInsets.only(top: 10), + shrinkWrap: true, + itemCount: devicesList.length, + itemBuilder: (context, index) { + return RoomPageSwitch(device: devicesList[index]); + }, + ), + ) + ], + ), + ), + ), + ], + ), + ), + ) + )); + })); + } +} diff --git a/lib/features/devices/view/widgets/hour_picker_dialog.dart b/lib/features/devices/view/widgets/hour_picker_dialog.dart new file mode 100644 index 0000000..63e95e2 --- /dev/null +++ b/lib/features/devices/view/widgets/hour_picker_dialog.dart @@ -0,0 +1,92 @@ + + +import 'package:flutter/material.dart'; + +class HourPickerDialog extends StatefulWidget { + final TimeOfDay initialTime; + HourPickerDialog({required this.initialTime}); + + @override + _HourPickerDialogState createState() => _HourPickerDialogState(); +} + +class _HourPickerDialogState extends State { + late int _selectedHour; + bool _isPm = false; + + @override + void initState() { + super.initState(); + _selectedHour = widget.initialTime.hour > 12 ? widget.initialTime.hour - 12 : widget.initialTime.hour; + _isPm = widget.initialTime.period == DayPeriod.pm; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Select Hour'), + content: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DropdownButton( + value: _selectedHour, + items: List.generate(12, (index) { + int displayHour = index + 1; + return DropdownMenuItem( + value: displayHour, + child: Text(displayHour.toString()), + ); + }), + onChanged: (value) { + setState(() { + _selectedHour = value!; + }); + }, + ), + SizedBox(width: 16.0), + DropdownButton( + value: _isPm, + items: [ + DropdownMenuItem( + value: false, + child: Text('AM'), + ), + DropdownMenuItem( + value: true, + child: Text('PM'), + ), + ], + onChanged: (value) { + setState(() { + _isPm = value!; + }); + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: Text('Cancel'), + ), + TextButton( + onPressed: () { + int hour = _isPm ? _selectedHour + 12 : _selectedHour; + Navigator.of(context).pop(TimeOfDay(hour: hour, minute: 0)); + }, + child: Text('OK'), + ), + ], + ); + } +} + +Future showHourPicker({ + required BuildContext context, + required TimeOfDay initialTime, +}) { + return showDialog( + context: context, + builder: (context) => HourPickerDialog(initialTime: initialTime), + ); +} diff --git a/lib/features/devices/view/widgets/lights/light_brightness.dart b/lib/features/devices/view/widgets/lights/light_brightness.dart new file mode 100644 index 0000000..1409ab3 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_brightness.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class LightBrightness extends StatelessWidget { + const LightBrightness({ + super.key, + required this.light, + }); + + final DeviceModel light; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return GestureDetector( + // onHorizontalDragUpdate: (details) => DevicesCubit.getInstance().get(context) + // .onHorizontalDragUpdate(light, details.localPosition.dx, + // MediaQuery.of(context).size.width - 15), + child: Stack( + alignment: Alignment.center, + children: [ + const DefaultContainer( + height: 60, + child: SizedBox.expand(), + ), + AnimatedPositioned( + left: 0, + duration: const Duration(milliseconds: 0), + child: Container( + height: 60, + // width: (MediaQuery.of(context).size.width - 30) * + // light.brightness / + // 100, + decoration: BoxDecoration( + color: ColorsManager.primaryColor.withOpacity(0.6), + borderRadius: + // light.brightness != 100 ? + const BorderRadius.only( + topLeft: Radius.circular(20), + bottomLeft: Radius.circular(20), + ) + // : BorderRadius.circular(20), + ), + ), + ), + BodyLarge( + // text: "${light.brightness}%", + text: '100%', + style: context.bodyLarge.copyWith( + color: Colors.black, + fontSize: 23, + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/light_interface.dart b/lib/features/devices/view/widgets/lights/light_interface.dart new file mode 100644 index 0000000..2b34853 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_interface.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface_contols.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface_switch.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface_timer.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class LightInterface extends StatelessWidget { + const LightInterface({super.key, required this.light}); + + final DeviceModel light; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: light.name ?? "", + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + body: Padding( + padding: const EdgeInsets.only(top: 70, right: 20, left: 20), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + LightInterfaceSwitch(light: light), + LightInterfaceContols(light: light), + const LightInterfaceTimer(), + ], + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/light_interface_contols.dart b/lib/features/devices/view/widgets/lights/light_interface_contols.dart new file mode 100644 index 0000000..e41b1be --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_interface_contols.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface_modes.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface_recent_color.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface_slider.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; + +class LightInterfaceContols extends StatelessWidget { + const LightInterfaceContols({ + super.key, + required this.light, + }); + + final DeviceModel light; + + @override + Widget build(BuildContext context) { + return DefaultContainer( + boxConstraints: const BoxConstraints(maxHeight: 320), + padding: const EdgeInsets.all(25), + margin: const EdgeInsets.symmetric(vertical: 10), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + LightInterfaceSlider(light: light), + LightInterfaceRecentColor(light: light), + LightInterfaceModes(light: light) + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/light_interface_modes.dart b/lib/features/devices/view/widgets/lights/light_interface_modes.dart new file mode 100644 index 0000000..c020771 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_interface_modes.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class LightInterfaceModes extends StatelessWidget { + const LightInterfaceModes({ + super.key, + required this.light, + }); + + final DeviceModel light; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyMedium( + text: StringsManager.lightingModes, + style: context.bodyMedium.copyWith(color: Colors.grey), + ), + const SizedBox(height: 10), + Wrap( + spacing: 25, + children: List.generate( + DevicesCubit.getInstance().lightModes.length, + (index) => InkWell( + // onTap: () => DevicesCubit.getInstance().setLightingMode( + // light, DevicesCubit.getInstance().lightModes[index]!), + child: Column( + children: [ + Container( + width: 40, + height: 40, + decoration: ShapeDecoration( + shape: const CircleBorder(), + color: switch (index) { + 0 => ColorsManager.dozeColor, + 1 => ColorsManager.relaxColor, + 2 => ColorsManager.readingColor, + 3 => ColorsManager.energizingColor, + _ => ColorsManager.primaryColor, + }, + ), + ), + BodyMedium( + text: switch (index) { + 0 => StringsManager.doze, + 1 => StringsManager.relax, + 2 => StringsManager.reading, + 3 => StringsManager.energizing, + _ => "", + }, + style: context.bodyMedium.copyWith( + color: Colors.grey, + fontSize: 10, + ), + ) + ], + ), + ), + ), + ), + const SizedBox(height: 10) + ], + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/light_interface_recent_color.dart b/lib/features/devices/view/widgets/lights/light_interface_recent_color.dart new file mode 100644 index 0000000..21d6163 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_interface_recent_color.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class LightInterfaceRecentColor extends StatelessWidget { + const LightInterfaceRecentColor({ + super.key, + required this.light, + }); + + final DeviceModel light; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyMedium( + text: StringsManager.recentlyUsed, + style: context.bodyMedium.copyWith(color: Colors.grey), + ), + const SizedBox(height: 10), + FittedBox( + child: Row( + children: [ + ...List.generate( + // light.recentColors.length > 4 ? 4 : light.recentColors.length, + 4, + (index) => InkWell( + // onTap: () { + // DevicesCubit.getInstance() + // .setColor(light, light.recentColors[index]); + // }, + child: Container( + margin: const EdgeInsets.only(right: 10), + width: 40, + height: 40, + decoration: const ShapeDecoration( + shape: CircleBorder(), + // color: Color(light.recentColors[index]), + ), + ), + ), + ), + Container( + margin: const EdgeInsets.only(right: 10), + width: 1, + height: 30, + color: ColorsManager.greyColor, + ), + SvgPicture.asset( + Assets.assetsIconsKalvin, + height: 40, + width: 40, + ), + const SizedBox( + width: 10, + ), + SvgPicture.asset( + Assets.assetsIconsColorWheel, + height: 40, + width: 40, + ), + ], + ), + ), + const SizedBox(height: 10), + ], + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/light_interface_slider.dart b/lib/features/devices/view/widgets/lights/light_interface_slider.dart new file mode 100644 index 0000000..e3ef363 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_interface_slider.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/united_text.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class LightInterfaceSlider extends StatelessWidget { + const LightInterfaceSlider({ + super.key, + required this.light, + }); + + final DeviceModel light; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyMedium( + text: StringsManager.dimmerAndColor, + style: context.bodyMedium.copyWith(color: Colors.grey), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: UnitedText( + // value: light.brightness.toString(), + value: '100', + unit: "%", + valueStyle: context.bodyMedium.copyWith( + color: Colors.grey, + fontSize: 26, + ), + unitStyle: context.bodyMedium.copyWith( + color: Colors.grey, + fontSize: 16, + ), + ), + ), + Expanded( + flex: 2, + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + thumbColor: ColorsManager.primaryColor, + rangeThumbShape: const RoundRangeSliderThumbShape( + enabledThumbRadius: 9, + ), + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 9, + ), + activeTrackColor: ColorsManager.greyColor, + inactiveTrackColor: ColorsManager.greyColor, + trackHeight: 5, + ), + child: Slider( + // value: light.brightness, + value: 100, + onChanged: (value) { + // DevicesCubit.getInstance().setBrightness(light, value); + }, + min: 0, + max: 100, + ), + ), + ), + ], + ), + const SizedBox(height: 10), + ], + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/light_interface_switch.dart b/lib/features/devices/view/widgets/lights/light_interface_switch.dart new file mode 100644 index 0000000..7defc52 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_interface_switch.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class LightInterfaceSwitch extends StatelessWidget { + const LightInterfaceSwitch({ + super.key, + required this.light, + }); + + final DeviceModel light; + + @override + Widget build(BuildContext context) { + return DefaultContainer( + boxConstraints: const BoxConstraints( + maxHeight: 65, + ), + margin: const EdgeInsets.symmetric(vertical: 10), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyLarge( + text: light.isOnline ?? false + ? StringsManager.on + : StringsManager.off, + style: + context.bodyLarge.copyWith(color: Colors.grey, fontSize: 24), + ), + Container( + width: 35, + decoration: ShapeDecoration( + color: light.isOnline ?? false + ? ColorsManager.primaryColorWithOpacity + : Colors.grey, + shape: const CircleBorder(), + ), + child: Center( + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.all(0), + ), + iconSize: MaterialStateProperty.all(25), + ), + onPressed: () { + // DevicesCubit.getInstance().toggleLight(light); + }, + icon: const Icon( + Icons.power_settings_new, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/light_interface_timer.dart b/lib/features/devices/view/widgets/lights/light_interface_timer.dart new file mode 100644 index 0000000..1b3902d --- /dev/null +++ b/lib/features/devices/view/widgets/lights/light_interface_timer.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; + +class LightInterfaceTimer extends StatelessWidget { + const LightInterfaceTimer({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + boxConstraints: const BoxConstraints(maxHeight: 65, minHeight: 65), + margin: const EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 10), + child: Row( + children: [ + BodyLarge( + text: StringsManager.timer, + style: context.bodyLarge.copyWith(color: Colors.grey), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/lights_list.dart b/lib/features/devices/view/widgets/lights/lights_list.dart new file mode 100644 index 0000000..aafc372 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/lights_list.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_brightness.dart'; +import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class LightsList extends StatelessWidget { + const LightsList({ + super.key, + required this.lights, + }); + + final List lights; + + @override + Widget build(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0), + itemCount: lights.length, + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + BodySmall(text: lights[index].name ?? ""), + IconButton( + onPressed: () { + DevicesCubit.getInstance().selectDevice(lights[index]); + }, + icon: const Icon( + Icons.arrow_forward_ios, + ), + style: ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.all(0), + ), + iconSize: MaterialStateProperty.all(15), + alignment: Alignment.bottomRight, + ), + ), + ], + ), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: false, + action: () {}, + ), + const SizedBox(height: 10), + LightBrightness(light: lights[index]), + ], + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/lights_view.dart b/lib/features/devices/view/widgets/lights/lights_view.dart new file mode 100644 index 0000000..ad0eb45 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/lights_view.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/category_view_app_bar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/lights_view_list.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class LightsView extends StatelessWidget { + const LightsView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return BlocBuilder( + builder: (context, state) { + DeviceModel? selectedLight; + if (DevicesCubit.getInstance().getSelectedDevice() is DeviceModel) { + selectedLight = DevicesCubit.getInstance().getSelectedDevice() as DeviceModel; + } + List lights = []; + if (DevicesCubit.getInstance().allCategories![1].devices != null) { + for (var device in DevicesCubit.getInstance().allCategories![1].devices!) { + lights.add(device); + } + } + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: SafeArea( + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: const CategoryViewAppBar( + title: '', + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: selectedLight != null + ? LightInterface(light: selectedLight) + : LightsViewList(lights: lights), + ), + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/lights/lights_view_list.dart b/lib/features/devices/view/widgets/lights/lights_view_list.dart new file mode 100644 index 0000000..98e9757 --- /dev/null +++ b/lib/features/devices/view/widgets/lights/lights_view_list.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/lights_list.dart'; +import 'package:syncrow_app/features/devices/view/widgets/universal_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class LightsViewList extends StatelessWidget { + const LightsViewList({ + super.key, + required this.lights, + }); + + final List lights; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + top: Constants.appBarHeight, + right: Constants.defaultPadding, + left: Constants.defaultPadding, + ), + child: SizedBox.expand( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const BodySmall(text: "All Lights"), + UniversalSwitch( + allOn: false, + ), + LightsList(lights: lights), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/name_time_widget.dart b/lib/features/devices/view/widgets/name_time_widget.dart new file mode 100644 index 0000000..5adbe2e --- /dev/null +++ b/lib/features/devices/view/widgets/name_time_widget.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class NameTimeWidget extends StatelessWidget { + const NameTimeWidget({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10.0), + child: const BodyMedium( + text: 'Password Name', + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 2.6, + child: TextFormField( + controller: BlocProvider.of(context) + .passwordNameController, + decoration: const InputDecoration( + hintText: 'Enter The Name', + hintStyle: TextStyle( + fontSize: 14, color: ColorsManager.textGray)), + )), + ], + ), + Column( + children: [ + const Divider( + color: ColorsManager.graysColor, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Effective Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + + BlocProvider.of(context).add(SelectTimeOnlinePasswordEvent(context: context, isEffective: true)); + }, + child: Text( + BlocProvider.of(context).effectiveTime, + style: TextStyle(fontSize: 14, + color: BlocProvider.of(context).effectiveTime == + 'Select Time' + ? ColorsManager.textGray + : null), + ), + )), + ],), + ), + const Divider( + color: ColorsManager.graysColor, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Expiration Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + BlocProvider.of(context).add( + SelectTimeOnlinePasswordEvent( + context: context, isEffective: false)); + }, + child: Text( + BlocProvider.of(context).expirationTime, + style: TextStyle( + fontSize: 14, + color: BlocProvider.of(context).expirationTime == 'Select Time' + ? ColorsManager.textGray + : null), + ), + ), + ), + ], + ), + ), + ], + ), + ], + )); + } +} diff --git a/lib/features/devices/view/widgets/no_devices_view.dart b/lib/features/devices/view/widgets/no_devices_view.dart new file mode 100644 index 0000000..b21bab1 --- /dev/null +++ b/lib/features/devices/view/widgets/no_devices_view.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class NoDevicesView extends StatelessWidget { + const NoDevicesView({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + Assets.assetsImagesBoxEmpty, + opacity: const AlwaysStoppedAnimation(0.5), + scale: 1, + width: 140, + ), + const SizedBox(height: 15), + const Text( + 'No Devices', + style: TextStyle( + color: Colors.grey, + fontSize: 18, + ), + ), + const SizedBox(height: 15), + const DefaultButton( + child: Text( + 'Add Device', + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/offline_one_time_password_page.dart b/lib/features/devices/view/widgets/offline_one_time_password_page.dart new file mode 100644 index 0000000..6c560cd --- /dev/null +++ b/lib/features/devices/view/widgets/offline_one_time_password_page.dart @@ -0,0 +1,224 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/repeat_widget.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/door_lock_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class OfflineOneTimePasswordPage extends StatelessWidget { + final String? deviceId; + final String? type; + const OfflineOneTimePasswordPage({super.key, this.deviceId, this.type}); + @override + Widget build(BuildContext context) { + bool isRepeat = false; + bool generated = false; + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!), + child: BlocConsumer(listener: (context, state) { + if (state is FailedState) { + CustomSnackBar.displaySnackBar( + state.errorMessage + ); + } + if (state is IsRepeatState){ + isRepeat = state.repeat; + } + if (state is GeneratePasswordOneTimestate ){ + generated = state.generated; + } + }, builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Create One-Time Password', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + leading: IconButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + icon: const Icon(Icons.arrow_back) + ), + ), + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: 'Save the password immediately. The password is not displayed in the app.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + text: '7-Digit Password', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children:smartDoorBloc.passwordController.text.isEmpty? + List.generate(10, (index) { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 4.0,vertical: 15), + child: Icon( + Icons.circle, + size: 20.0, + color: Colors.black, + ), + ); + }) :[ + Expanded( + child: Row( + children: [ + + Expanded( + child: BodyLarge( + style: const TextStyle( + color: ColorsManager.primaryColor, + fontWeight: FontWeight.bold, + letterSpacing: 8.0 , + fontSize: 25, + wordSpacing: 2), + textAlign: TextAlign.center, + text: smartDoorBloc.passwordController.text, + fontSize: 23, + ),), + + IconButton( + onPressed: () async { + await Clipboard.setData(ClipboardData( + text: smartDoorBloc.passwordController.text)); + }, + icon: const Icon(Icons.copy) + ), + ], + ), + ), + ], + )), + const SizedBox( + width: 10, + ), + + ], + ), + if(smartDoorBloc.passwordController.text.isNotEmpty) + Column( + children: [ + const Divider( + color: ColorsManager.graysColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10.0), + child: const BodyMedium( + text: 'Password Name', + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 2.6, + child: TextFormField( + controller: BlocProvider.of(context).passwordNameController, + decoration: const InputDecoration( + hintText: 'Enter The Name', + hintStyle: TextStyle( + fontSize: 14, color: ColorsManager.textGray)), + )), + ], + ), + + ], + ), + ], + ), + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + textAlign: TextAlign.center, + text: 'Save the password immediately. The password is not displayed in the app.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + + // NameTimeWidget(type:type!), + const SizedBox( + height: 20, + ), + Center( + child: SizedBox( + width: MediaQuery.of(context).size.width/1.5, + child: DoorLockButton( + isDone: generated, + isLoading: smartDoorBloc.isSavingPassword , + borderRadius: 30, + backgroundColor:ColorsManager.primaryColor , + onPressed: () async { + if(generated==false){ + smartDoorBloc.add(GenerateAndSavePasswordOneTimeEvent(context: context)); + }else{ + if(smartDoorBloc.passwordNameController.text.isNotEmpty){ + smartDoorBloc.add(RenamePasswordEvent()); + } + Navigator.of(context).pop(true); + } + }, + child: const BodyMedium( + text: 'Obtain Password', + fontWeight: FontWeight.bold, + fontColor: Colors.white, + ),), + ), + ), + const SizedBox( + height: 20, + ), + isRepeat? const RepeatWidget():const SizedBox(), + const SizedBox( + height: 40, + ) + ], + ), + ), + ); + })); + } +} diff --git a/lib/features/devices/view/widgets/popup_menu_widget.dart b/lib/features/devices/view/widgets/popup_menu_widget.dart new file mode 100644 index 0000000..bafa005 --- /dev/null +++ b/lib/features/devices/view/widgets/popup_menu_widget.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +void showPopupMenu( + {required BuildContext context, List>? items}) async { + await showMenu( + context: context, + position: RelativeRect.fromLTRB( MediaQuery.of(context).size.width/2 , 100, 0, 0), + items: items!, + elevation: 8.0, + ); +} diff --git a/lib/features/devices/view/widgets/room_page.dart b/lib/features/devices/view/widgets/room_page.dart new file mode 100644 index 0000000..68b2ac1 --- /dev/null +++ b/lib/features/devices/view/widgets/room_page.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/room_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/room_page_switch.dart'; + +class RoomPage extends StatelessWidget { + const RoomPage({super.key, required this.room}); + + final RoomModel room; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.5, + ), + padding: const EdgeInsets.only(top: 10), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: room.devices!.length, + itemBuilder: (context, index) { + return RoomPageSwitch(device: room.devices![index]); + }, + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/room_page_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart new file mode 100644 index 0000000..980de66 --- /dev/null +++ b/lib/features/devices/view/widgets/room_page_switch.dart @@ -0,0 +1,154 @@ +/// This widget represents a switch for a device on the room page. +/// +/// This widget displays the icon and name of the device, along with a switch +/// to control its state. Tapping on the widget opens the device interface. +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/gateway/gateway_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_interface.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_interface.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class RoomPageSwitch extends StatelessWidget { + const RoomPageSwitch({ + super.key, + required this.device, + }); + + final DeviceModel device; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + showDeviceInterface(device, context); + }, + child: DefaultContainer( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + device.icon!, + fit: BoxFit.contain, + ), + // CustomSwitch( + // device: device, + // ), + ], + ), + Flexible( + child: FittedBox( + child: Text( + device.type ?? "", + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.bodyLarge.copyWith( + fontWeight: FontWeight.bold, + fontSize: 20, + color: Colors.grey, + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +/// Shows the device interface based on the product type of the device. +/// +/// The [device] parameter represents the device model. +void showDeviceInterface(DeviceModel device, BuildContext context) { + switch (device.productType) { + case DeviceType.AC: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => ACsView(deviceModel: device))); + // navigateToInterface(ACsView(deviceModel: device), context); + break; + case DeviceType.WallSensor: + // navigateToInterface(WallMountedInterface(wallSensor: device), context); + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + WallMountedInterface(deviceModel: device))); + break; + case DeviceType.CeilingSensor: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + CeilingSensorInterface(ceilingSensor: device))); + // navigateToInterface(CeilingSensorInterface(ceilingSensor: device), context); + break; + case DeviceType.Curtain: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + CurtainView(curtain: device,))); + break; + case DeviceType.Blind: + break; + case DeviceType.DoorLock: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => DoorInterface(doorLock: device))); + // navigateToInterface(DoorInterface(doorlock: device), context); + break; + case DeviceType.Gateway: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => GateWayView(gatewayObj: device))); + break; + case DeviceType.LightBulb: + navigateToInterface(LightInterface(light: device), context); + case DeviceType.ThreeGang: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + ThreeGangInterface(gangSwitch: device))); + // navigateToInterface(ThreeGangInterface(gangSwitch: device), context); + break; + default: + } +} + +/// Navigates to the specified device interface. +/// +/// The [interface] parameter represents the widget interface. +void navigateToInterface(Widget interface, context) { + Navigator.push( + context, + CustomPageRoute( + builder: (context) => BlocProvider( + create: (context) => DevicesCubit.getInstance(), + child: interface, + ), + ), + ); +} diff --git a/lib/features/devices/view/widgets/rooms_slider.dart b/lib/features/devices/view/widgets/rooms_slider.dart new file mode 100644 index 0000000..02d85f0 --- /dev/null +++ b/lib/features/devices/view/widgets/rooms_slider.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class RoomsSlider extends StatelessWidget { + const RoomsSlider({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return SizedBox( + height: 40, + child: PageView( + controller: HomeCubit.getInstance().roomsPageController, + onPageChanged: (index) { + HomeCubit.getInstance().roomSliderPageChanged(index); + }, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: InkWell( + onTap: () { + HomeCubit.getInstance().unselectRoom(); + }, + child: TitleMedium( + text: StringsManager.wizard, + style: context.titleMedium.copyWith( + fontSize: 25, + color: ColorsManager.textPrimaryColor, + ), + ), + ), + ), + if (HomeCubit.getInstance().selectedSpace != null) + if (HomeCubit.getInstance().selectedSpace!.rooms != null) + ...HomeCubit.getInstance().selectedSpace!.rooms!.map( + (room) => InkWell( + onTap: () { + HomeCubit.getInstance().roomSliderPageChanged( + HomeCubit.getInstance().selectedSpace!.rooms!.indexOf(room)); + }, + child: TitleMedium( + text: room.name!, + style: context.titleMedium.copyWith( + fontSize: 25, + color: HomeCubit.getInstance().selectedRoom == room + ? ColorsManager.textPrimaryColor + : ColorsManager.textPrimaryColor.withOpacity(.2), + ), + ), + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/scene_listview.dart b/lib/features/devices/view/widgets/scene_listview.dart new file mode 100644 index 0000000..63b770f --- /dev/null +++ b/lib/features/devices/view/widgets/scene_listview.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/model/scenes_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; + +class SceneListview extends StatelessWidget { + final List scenes; + final String? loadingSceneId; + const SceneListview({ + required this.scenes, + required this.loadingSceneId, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: scenes.length, + itemBuilder: (context, index) { + final scene = scenes[index]; + return Container( + padding: const EdgeInsets.only(right: 10), + child: DefaultContainer( + onTap: () { + Navigator.pushNamed( + context, + Routes.sceneTasksRoute, + arguments: SceneSettingsRouteArguments( + sceneType: CreateSceneEnum.tabToRun.name, + sceneId: scene.id, + sceneName: scene.name, + ), + ); + context + .read() + .add(const SmartSceneClearEvent()); + + BlocProvider.of(context).add( + FetchSceneTasksEvent( + sceneId: scene.id, isAutomation: false)); + + /// the state to set the scene type must be after the fetch + BlocProvider.of(context) + .add(const SceneTypeEvent(CreateSceneEnum.tabToRun)); + }, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.asset( + height: 32, + width: 32, + Assets.assetsIconsLogo, + fit: BoxFit.fill, + ), + ), + Expanded( + child: BodyMedium( + text: scene.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ], + ), + ), + )); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/add_member_interface.dart b/lib/features/devices/view/widgets/smart_door/add_member_interface.dart new file mode 100644 index 0000000..3622ad9 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/add_member_interface.dart @@ -0,0 +1,231 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class AddMemberInterface extends StatelessWidget { + const AddMemberInterface({super.key, required this.memberRole}); + final MemberRole memberRole; + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Add Member', + actions: [ + Padding( + padding: const EdgeInsets.only(right: 10), + child: TextButton( + onPressed: () {}, + child: const BodyMedium( + text: 'SAVE', + fontWeight: FontWeight.bold, + ), + ), + ), + ], + child: Column( + children: [ + SizedBox.square( + dimension: 120, + child: CircleAvatar( + backgroundColor: Colors.white, + child: SizedBox.square( + dimension: 117, + child: CircleAvatar( + backgroundColor: Colors.grey, + child: Icon( + memberRole == MemberRole.FamilyMember + ? Icons.family_restroom + : Icons.person, + size: 25, + color: Colors.white, + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Row( + children: [ + const Expanded( + child: SizedBox.shrink(), + ), + BodyLarge( + text: memberRole == MemberRole.FamilyMember + ? 'Family Member' + : 'Guest', + fontWeight: FontWeight.bold, + ), + Expanded( + child: Row( + children: [ + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.edit_outlined, + size: 20, + ), + ), + ], + ), + ), + ], + ), + ), + memberRole == MemberRole.FamilyMember + ? DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Region '), + Flexible( + child: TextField( + textAlign: TextAlign.end, + decoration: InputDecoration( + hintText: 'United Arab Emirates', + hintStyle: context.bodyMedium + .copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + Container( + height: 1, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Account '), + Flexible( + child: TextField( + textAlign: TextAlign.end, + decoration: InputDecoration( + hintText: 'Account Name', + hintStyle: context.bodyMedium + .copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + Container( + height: 1, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Family Role'), + Flexible( + child: TextField( + textAlign: TextAlign.end, + decoration: InputDecoration( + hintText: 'Common Member', + hintStyle: context.bodyMedium + .copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + ], + ), + ) + : DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Unlock with Mobile phone '), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: false, + onChanged: (value) {}, + applyTheme: true, + ), + ), + ], + ), + ), + ], + ), + Container( + height: 1, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Set Validity Period '), + Flexible( + child: TextField( + textAlign: TextAlign.end, + decoration: InputDecoration( + hintText: 'Start and End Date', + hintStyle: context.bodyMedium + .copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + Container( + height: 1, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Effictive Period '), + Flexible( + child: TextField( + textAlign: TextAlign.end, + decoration: InputDecoration( + hintText: 'Start and End Time', + hintStyle: context.bodyMedium + .copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/add_member_view.dart b/lib/features/devices/view/widgets/smart_door/add_member_view.dart new file mode 100644 index 0000000..cd039f2 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/add_member_view.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/add_member_interface.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class AddMemberView extends StatelessWidget { + const AddMemberView({super.key}); + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Add Member', + child: Column( + children: [ + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: 'Family Members', fontWeight: FontWeight.bold), + const Padding( + padding: EdgeInsets.symmetric(vertical: 13), + child: BodySmall( + text: + 'After adding:\n\n\t\t1.The user will join the family.\n\n\t\t2.The user will be able to control and use all devices in the family.', + fontColor: ColorsManager.greyColor, + fontSize: 10, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + ColorsManager.primaryColorWithOpacity, + ), + ), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const AddMemberInterface( + memberRole: MemberRole.FamilyMember, + ), + ), + ); + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 50), + child: BodyMedium( + text: 'ADD', + fontColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ) + ], + ), + ), + const SizedBox( + height: 10, + ), + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: 'Other Member', fontWeight: FontWeight.bold), + const Padding( + padding: EdgeInsets.symmetric(vertical: 13), + child: BodySmall( + text: + 'After adding:\n\n\t\t1.The user only appears in the member list of the current lock and can only view their own unlocking records.\n\n\t\t2.The user cannot obtain the permissions to operate or manage the lock.\n\n\t\t3.To grant the user operation permissions on the lock, you must use the device sharing functions.', + fontColor: ColorsManager.greyColor, + fontSize: 10, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const AddMemberInterface( + memberRole: MemberRole.OtherMember, + ), + ), + ); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + ColorsManager.primaryColorWithOpacity, + ), + ), + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 50), + child: BodyMedium( + text: 'ADD', + fontColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart b/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart new file mode 100644 index 0000000..7d2b807 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart @@ -0,0 +1,197 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/devices/view/widgets/name_time_widget.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/repeat_widget.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class CreateTemporaryPassword extends StatelessWidget { + final String? deviceId; + final String? type; + const CreateTemporaryPassword({super.key, this.deviceId, this.type}); + @override + Widget build(BuildContext context) { + bool isRepeat = false; + bool generated = false; + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!), + child: BlocConsumer(listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + if (state is IsRepeatState){ + isRepeat = state.repeat; + } + if (state is GeneratePasswordOneTimestate ){ + generated = state.generated; + } + }, builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Create Password', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + leading: IconButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + icon: const Icon(Icons.arrow_back) + ), + actions: + type == 'Online Password'?[ + TextButton( + onPressed: () { + smartDoorBloc.add(SavePasswordEvent(context: context)); + }, + child: const Text('Save') + ) + ]:null, + ), + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: 'Save the password immediately. The password is not displayed in the app.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + text: '7-Digit Password', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: type == 'Online Password'?0:25), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + flex: 2, + child: PinCodeTextField( + onCompleted: (value) { + if (value.split('').every((char) => char == '1')) { + smartDoorBloc.passwordController.clear(); + CustomSnackBar.displaySnackBar('All characters cannot be 1.'); + } + }, + autoDisposeControllers: false, + keyboardType: TextInputType.phone, + length: 7, + // enabled:type == 'Online Password'? true:false, + obscureText: false, + animationType: AnimationType.fade, + pinTheme: PinTheme( + shape: PinCodeFieldShape.underline, + fieldHeight: 45, + fieldWidth: 20, + activeFillColor: Colors.white, + disabledColor: Colors.grey, + activeColor: Colors.grey, + errorBorderColor: Colors.grey, + inactiveColor: Colors.grey, + inactiveFillColor: Colors.grey, + selectedColor: Colors.grey), + animationDuration: const Duration(milliseconds: 300), + backgroundColor: Colors.white, + enableActiveFill: false, + controller: smartDoorBloc.passwordController, + appContext: context, + )), + const SizedBox( + width: 10, + ), + if(type == 'Online Password') + Flexible( + child: InkWell( + onTap: () { + smartDoorBloc.add(GeneratePasswordEvent()); + }, + child: const BodyMedium( + text: 'Generate Randomly', + fontWeight: FontWeight.bold, + fontColor: ColorsManager.primaryColor, + )), + ) + ], + ), + ), + ), + if(smartDoorBloc.passwordController.text.isNotEmpty) + TextButton( + onPressed: () async { + await Clipboard.setData(ClipboardData( + text: smartDoorBloc.passwordController.text)); + }, + child: const Text('Copy') + ), + const SizedBox( + height: 20, + ), + NameTimeWidget(), + const SizedBox( + height: 20, + ), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Repeat', + fontWeight: FontWeight.normal, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: smartDoorBloc.repeat, + onChanged: (value) { + smartDoorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + )), + ), + ) , + const SizedBox( + height: 20, + ), + isRepeat? const RepeatWidget():const SizedBox(), + const SizedBox( + height: 40, + ) + ], + ), + ), + ); + })); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/door_button.dart b/lib/features/devices/view/widgets/smart_door/door_button.dart new file mode 100644 index 0000000..92e924d --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/door_button.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DoorLockButton extends StatefulWidget { + const DoorLockButton({ + super.key, + required this.doorLock, + required this.smartDoorModel, + }); + + final DeviceModel doorLock; + final SmartDoorModel smartDoorModel; + @override + State createState() => _DoorLockButtonState(smartDoorModel: smartDoorModel); +} + +class _DoorLockButtonState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _animation; + SmartDoorModel smartDoorModel; + _DoorLockButtonState({required this.smartDoorModel}); + + @override + void initState() { + super.initState(); + + _animationController = AnimationController( + vsync: this, + value: context.read().unlockRequest > 0 ? 1 : 0, + duration: Duration(seconds: context.read().unlockRequest), + ); + if (context.read().unlockRequest > 0) { + _animationController.reverse(); + } + + _animation = Tween(begin: 0, end: 1).animate(_animationController) + ..addListener(() { + setState(() {}); + }); + } + + @override + void didUpdateWidget(DoorLockButton oldWidget) { + super.didUpdateWidget(oldWidget); + + if (_animationController.status == AnimationStatus.dismissed) { + if (context.read().unlockRequest > 0) { + _animationController.value = 1; + _animationController.duration = + Duration(seconds: context.read().unlockRequest); + _animationController.reverse(); + } + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + right: context.width * 0.25 / 2, + left: context.width * 0.25 / 2, + bottom: context.width * 0.2 / 2, + ), + child: InkWell( + overlayColor: + WidgetStateProperty.all(ColorsManager.primaryColorWithOpacity.withOpacity(0.1)), + borderRadius: BorderRadius.circular(999), + onTapDown: (details) { + // if (_animationController.status == AnimationStatus.dismissed) { + // _animationController.forward(); + // } else if (_animationController.status == AnimationStatus.completed) { + // _animationController.reverse(); + // } else if (_animationController.status == AnimationStatus.forward) { + // _animationController.reverse(); + // } else if (_animationController.status == AnimationStatus.reverse) { + // _animationController.forward(); + // } + if (context.read().unlockRequest > 0) { + BlocProvider.of(context) + .add(UpdateLockEvent(value: smartDoorModel.normalOpenSwitch)); + } + }, + onTapUp: (details) { + // if (_animationController.status == AnimationStatus.forward) { + // _animationController.reverse(); + // } else if (_animationController.status == AnimationStatus.reverse) { + // _animationController.forward(); + // } + }, + child: Container( + width: context.width * 06, + height: context.width * 0.6, + margin: const EdgeInsets.all(10), + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey, + blurRadius: 18, + // offset: Offset(6, 7), + blurStyle: BlurStyle.outer, + ), + ], + color: Color(0xFFEBECED), + borderRadius: BorderRadius.all(Radius.circular(999)), + ), + child: Padding( + padding: const EdgeInsets.all(25), + child: Stack( + alignment: Alignment.center, + children: [ + Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(999), + boxShadow: [ + BoxShadow( + color: Colors.white.withOpacity(0.5), + blurRadius: 30, + offset: const Offset(-5, -5), + blurStyle: BlurStyle.outer, + ), + BoxShadow( + color: Colors.black.withOpacity(0.14), + blurRadius: 25, + offset: const Offset(5, 5), + blurStyle: BlurStyle.outer, + ), + BoxShadow( + color: Colors.black.withOpacity(0.14), + blurRadius: 30, + offset: const Offset(5, 5), + blurStyle: BlurStyle.inner, + ), + ], + ), + child: Center( + child: SvgPicture.asset( + smartDoorModel.normalOpenSwitch + ? Assets.doorUnlockIcon + : Assets.assetsIconsDoorlockAssetsLockIcon, + ), + ), + ), + SizedBox.expand( + child: CircularProgressIndicator( + value: _animation.value, + strokeWidth: 15, + backgroundColor: Colors.transparent, + valueColor: const AlwaysStoppedAnimation(ColorsManager.primaryColor), + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/door_dialog.dart b/lib/features/devices/view/widgets/smart_door/door_dialog.dart new file mode 100644 index 0000000..b1a672d --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/door_dialog.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_app/features/devices/model/offline_password_model.dart'; +import 'package:syncrow_app/features/devices/model/temporary_password_model.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class DoorDialog extends StatefulWidget { + final String? title; + final TemporaryPassword? temporaryPassword; + final OfflinePasswordModel? offline; + + const DoorDialog({ + super.key, + this.title, + this.offline, + this.temporaryPassword, + }); + + @override + DoorDialogState createState() => DoorDialogState(); +} + +class DoorDialogState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final effectiveTime = widget.temporaryPassword?.effectiveTime ??int.parse( widget.offline?.gmtStart); + final invalidTime = widget.temporaryPassword?.invalidTime ?? int.parse(widget.offline?.gmtExpired); + + final DateTime effectiveDateTime = + DateTime.fromMillisecondsSinceEpoch(effectiveTime! * 1000, isUtc: false); + String formattedDateEffectiveTime = DateFormat('yyyy-MM-dd').format(effectiveDateTime); + String formattedTimeEffectiveTime = DateFormat('hh:mm a').format(effectiveDateTime); + + final DateTime expiredDateTime = + DateTime.fromMillisecondsSinceEpoch(invalidTime! * 1000, isUtc: false); + String formattedDateExpiredDateTime = DateFormat('yyyy-MM-dd').format(expiredDateTime); + String formattedTimeExpiredDateTime = DateFormat('hh:mm a').format(expiredDateTime); + return Dialog( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + BodyMedium( + text: widget.title ?? '', + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontsManager.extraBold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 50, + ), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Effective Date:'), + Text(formattedDateEffectiveTime), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Effective Time:'), + Text(formattedTimeEffectiveTime), + ], + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Expired Date:'), + Text(formattedDateExpiredDateTime), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Expired Time:'), + Text(formattedTimeExpiredDateTime), + ], + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + widget.temporaryPassword?.effectiveTime!=null? + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Center( + child: BodyMedium( + text: 'Cancel', + style: context.bodyMedium.copyWith(color: ColorsManager.greyColor), + ), + ), + ), + Container( + height: 50, + width: 1, + color: ColorsManager.greyColor, + ), + InkWell( + onTap: () { + Navigator.pop(context, 'delete'); + }, + child: Center( + child: BodyMedium( + text: 'Delete Password', + style: context.bodyMedium.copyWith(color: ColorsManager.primaryColorWithOpacity), + ), + ), + ), + ], + ): + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + height: 50, + child: InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Center( + child: BodyMedium( + text: 'Cancel', + style: context.bodyMedium.copyWith(color: ColorsManager.greyColor), + ), + ), + ), + + ), + + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/door_grid.dart b/lib/features/devices/view/widgets/smart_door/door_grid.dart new file mode 100644 index 0000000..fc5c290 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/door_grid.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/members_management_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/smart_linkage_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/temporary_password_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/unlocking_records_view.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class DoorLockGrid extends StatelessWidget { + String uuid; + DoorLockGrid({ + super.key,required this.uuid}); + + @override + Widget build(BuildContext context) { + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + childAspectRatio: 1.75 / 1, + ), + itemCount: 4, + itemBuilder: (context, index) => DefaultContainer( + onTap: () { + //TODO: remove checking after adding the pages + doorLockButtons()[index]['page'] != null + ? Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => doorLockButtons(val: uuid)[index]['page'] as Widget, + ), + ) + : null; + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 46, maxWidth: 50), + child: SvgPicture.asset( + doorLockButtons()[index]['image'] as String, + ), + ), + const SizedBox( + height: 15, + ), + Flexible( + child: FittedBox( + child: BodySmall( + text: doorLockButtons()[index]['title'] as String, + // doorLockButtons.keys.elementAt(index), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ); + } +} + +List> doorLockButtons({val}) => [ + { + 'title': 'Unlocking Records', + 'image': Assets.assetsIconsDoorlockAssetsUnlockingRecords, + 'page': const UnlockingRecordsView(), + }, + { + 'title': 'Members Management', + 'image': Assets.assetsIconsDoorlockAssetsMembersManagement, + 'page': const MembersManagementView(), + }, + { + 'title': 'Temporary Password', + 'image': Assets.assetsIconsDoorlockAssetsTemporaryPassword, + 'page': TemporaryPasswordPage(deviceId:val) , + }, + { + 'title': 'Smart Linkage', + 'image': Assets.assetsIconsDoorlockAssetsSmartLinkage, + 'page': const SmartLinkgeView() + }, +]; diff --git a/lib/features/devices/view/widgets/smart_door/door_interface.dart b/lib/features/devices/view/widgets/smart_door/door_interface.dart new file mode 100644 index 0000000..b65ef92 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/door_interface.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_button.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_grid.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_status_bar.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class DoorInterface extends StatelessWidget { + const DoorInterface({super.key, required this.doorLock}); + + final DeviceModel doorLock; + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SmartDoorBloc(deviceId: doorLock.uuid ?? '')..add(InitialEvent()), + child: BlocConsumer(listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + }, builder: (context, state) { + SmartDoorModel smartDoorModel = SmartDoorModel( + unlockFingerprint: 0, + unlockPassword: 0, + unlockTemporary: 0, + unlockCard: 0, + unlockAlarm: '', + unlockRequest: 0, + residualElectricity: 0, + reverseLock: false, + unlockApp: 0, + hijack: false, + doorbell: false, + unlockOfflinePd: '', + unlockOfflineClear: '', + unlockDoubleKit: '', + remoteNoPdSetkey: '', + remoteNoDpKey: '', + normalOpenSwitch: false); + + if (state is UpdateState) { + smartDoorModel = state.smartDoorModel; + } else if (state is LoadingNewSate) { + smartDoorModel = state.smartDoorModel; + } + + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: DeviceAppbar( + deviceName: doorLock.name!, + deviceUuid: doorLock.uuid!, + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.only( + top: Constants.appBarHeight, + left: Constants.defaultPadding, + right: Constants.defaultPadding, + ), + child: state is LoadingInitialState + ? const Center( + child: RefreshProgressIndicator(), + ) + : RefreshIndicator( + onRefresh: () async { + BlocProvider.of(context).add(InitialEvent()); + }, + child: ListView( + children: [ + DoorLockStatusBar( + smartDoorModel: smartDoorModel, + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + DoorLockButton( + doorLock: doorLock, + smartDoorModel: smartDoorModel, + ), + DoorLockGrid( + uuid: doorLock.uuid!, + ), + ], + ) + ], + )), + ), + ), + ), + )); + }), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/door_status_bar.dart b/lib/features/devices/view/widgets/smart_door/door_status_bar.dart new file mode 100644 index 0000000..9d2e8c2 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/door_status_bar.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class DoorLockStatusBar extends StatelessWidget { + const DoorLockStatusBar({ + required this.smartDoorModel, + super.key, + }); + + final SmartDoorModel smartDoorModel; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset(Assets.assetsIconsWifi), + Transform.rotate( + angle: 1.5708, // 90 degrees in radians (π/2 or 1.5708) + child: Icon( + _getBatteryIcon(smartDoorModel.residualElectricity), + color: _getBatteryColor(smartDoorModel.residualElectricity), + size: 30, + ), + ), + ], + ); + } + + IconData _getBatteryIcon(int batteryLevel) { + // if (batteryState == BatteryState.charging) { + // return Icons.battery_charging_full; + // } else + if (batteryLevel >= 80) { + return Icons.battery_full; + } else if (batteryLevel >= 60) { + return Icons.battery_4_bar; + } else if (batteryLevel >= 40) { + return Icons.battery_3_bar; + } else if (batteryLevel >= 20) { + return Icons.battery_2_bar; + } else { + return Icons.battery_alert; + } + } + + Color _getBatteryColor(int batteryLevel) { + if (batteryLevel >= 80) { + return Colors.green; + } else if (batteryLevel >= 40) { + return Colors.yellowAccent; + } else { + return Colors.red; + } + } +} diff --git a/lib/features/devices/view/widgets/smart_door/doors_list_view.dart b/lib/features/devices/view/widgets/smart_door/doors_list_view.dart new file mode 100644 index 0000000..a4e2069 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/doors_list_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class DoorsListView extends StatelessWidget { + const DoorsListView({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/members_management_view.dart b/lib/features/devices/view/widgets/smart_door/members_management_view.dart new file mode 100644 index 0000000..3266b6e --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/members_management_view.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/add_member_view.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; +import 'package:syncrow_app/utils/helpers/misc_string_helpers.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class MembersManagementView extends StatefulWidget { + const MembersManagementView({super.key}); + + @override + State createState() => _MembersManagementViewState(); +} + +class _MembersManagementViewState extends State { + bool showFamilyMembers = true; + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Manage Members', + actions: [ + Padding( + padding: const EdgeInsets.only(right: 10), + child: IconButton( + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + builder: (context) => const AddMemberView(), + ), + ); + }, + icon: const Icon( + Icons.add, + size: 30, + ), + ), + ), + ], + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () { + setState(() { + showFamilyMembers = true; + }); + }, + child: BodyMedium( + text: 'Family Members', + fontWeight: + showFamilyMembers ? FontWeight.bold : FontWeight.normal, + ), + ), + const SizedBox(width: 10), + InkWell( + onTap: () { + setState(() { + showFamilyMembers = false; + }); + }, + child: BodyMedium( + text: 'Other Members', + fontWeight: !showFamilyMembers + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: BodySmall( + text: + 'Administrators can set passwords for themselves or other users. Family members can set passwords only when they are granted the permission to set passwords.', + style: context.bodySmall.copyWith(fontSize: 8), + ), + ), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Column( + mainAxisSize: MainAxisSize.min, + children: List.generate( + members.length, + (index) { + if (showFamilyMembers) { + if (members[index]['role'] == MemberRole.FamilyMember) { + return Column( + children: [ + Row( + children: [ + const SizedBox.square( + dimension: 35, + child: CircleAvatar( + backgroundColor: Colors.grey, + ), + ), + const SizedBox( + width: 10, + ), + BodyMedium( + text: StringHelpers.toTitleCase( + members[index]['name'] as String)), + const Spacer(), + const BodyMedium( + text: 'Family Member', + fontColor: Colors.grey, + ) + ], + ), + index != members.length - 1 + ? Container( + margin: const EdgeInsets.only( + left: 45, top: 8, bottom: 8), + height: 1, + color: ColorsManager.greyColor, + ) + : const SizedBox(), + ], + ); + } else { + return const SizedBox(); + } + } else { + if (members[index]['role'] == MemberRole.OtherMember) { + return Column( + children: [ + Row( + children: [ + const SizedBox.square( + dimension: 35, + child: CircleAvatar( + backgroundColor: Colors.grey, + ), + ), + const SizedBox( + width: 10, + ), + BodyMedium( + text: StringHelpers.toTitleCase( + members[index]['name'] as String)), + const Spacer(), + const BodyMedium( + text: 'Other Member', + fontColor: Colors.grey, + ) + ], + ), + index != members.length - 1 + ? Container( + margin: const EdgeInsets.only( + left: 45, top: 8, bottom: 8), + height: 1, + color: ColorsManager.greyColor, + ) + : const SizedBox(), + ], + ); + } else { + return const SizedBox(); + } + } + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/offline_timeLimit_password_page.dart b/lib/features/devices/view/widgets/smart_door/offline_timeLimit_password_page.dart new file mode 100644 index 0000000..9a55e82 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/offline_timeLimit_password_page.dart @@ -0,0 +1,288 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/repeat_widget.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/door_lock_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { + final String? deviceId; + final String? type; + const CreateOfflineTimeLimitPasswordPage({super.key, this.deviceId, this.type}); + @override + Widget build(BuildContext context) { + bool isRepeat = false; + bool generated = false; + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!), + child: BlocConsumer( + listener: (context, state) { + if (state is FailedState) { + CustomSnackBar.displaySnackBar( + state.errorMessage + ); + } + if (state is IsRepeatState) { + isRepeat = state.repeat; + } + if (state is GeneratePasswordOneTimestate) { + generated = state.generated; + } + }, builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Create Time-Limited Password', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + leading: IconButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + icon: const Icon(Icons.arrow_back)), + ), + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: + 'Save the password immediately. The password is not displayed in the app.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox( + height: 20, + ), + + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 15), + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: smartDoorBloc.passwordController.text.isEmpty ? + List.generate(10, (index) { + return const Padding( + padding: EdgeInsets.symmetric( + horizontal: 4.0, + vertical: 15), + child: Icon( + Icons.circle, + size: 20.0, + color: Colors.black, + ), + ); + }) : [ + Expanded( + child: Row( + children: [ + Expanded( + child: BodyLarge( + style: const TextStyle( + color: ColorsManager.primaryColor, + fontWeight: FontWeight.bold, + letterSpacing: 8.0, + fontSize: 25, + wordSpacing: 2), + textAlign: TextAlign.center, + text: smartDoorBloc.passwordController.text, + fontSize: 25, + ), + ), + IconButton( + onPressed: () async { + await Clipboard.setData( + ClipboardData(text: smartDoorBloc.passwordController.text) + ); + }, + icon: const Icon(Icons.copy)), + ], + ), + ), + ], + )), + const SizedBox( + width: 10, + ), + ], + ), + DefaultContainer( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10.0), + child: const BodyMedium( + text: 'Password Name', + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 2.6, + child: TextFormField( + controller: BlocProvider.of(context).passwordNameController, + decoration: + const InputDecoration( + hintText: 'Enter The Name', + hintStyle: TextStyle( + fontSize: 14, + color: ColorsManager.textGray) + ), + )), + ], + ), + Column( + children: [ + const Divider(color: ColorsManager.graysColor,), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Effective Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + BlocProvider.of(context).add(SelectTimeEvent(context: context, isEffective: true)); + }, + child: Text( + BlocProvider.of(context).effectiveTime, + style: TextStyle( + fontSize: 14, + color: BlocProvider.of(context).effectiveTime == + 'Select Time' ? ColorsManager.textGray : null), + ), + )),], + ), + ), + const Divider( + color: ColorsManager.graysColor, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row(mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Expiration Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + BlocProvider.of(context).add(SelectTimeEvent( + context: context, + isEffective: false)); + }, + child: Text( + BlocProvider.of(context).expirationTime, + style: TextStyle( + fontSize: 14, + color: BlocProvider.of(context) + .expirationTime == 'Select Time' ? ColorsManager + .textGray : null), + ), + ), + ), + ], + ), + ), + ], + ), + ], + )), + ], + ), + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + textAlign: TextAlign.center, + text: 'Use the time-limited password at least once within 24 hours after the password takes effect. Otherwise, the password becomes invalid.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + // NameTimeWidget(type:type!), + const SizedBox( + height: 20, + ), + Center( + child: SizedBox( + width: MediaQuery.of(context).size.width / 1.5, + child: DoorLockButton( + isDone: generated, + isLoading: smartDoorBloc.isSavingPassword, + borderRadius: 30, + backgroundColor: ColorsManager.primaryColor, + onPressed: () async { + if (generated == false) { + smartDoorBloc.add(GenerateAndSavePasswordTimeLimitEvent(context: context)); + } else { + if(smartDoorBloc.passwordNameController.text.isNotEmpty){ + smartDoorBloc.add(RenamePasswordEvent()); + } + Navigator.of(context).pop(true); + } + }, + child: const BodyMedium( + text: 'Obtain Password', + fontWeight: FontWeight.bold, + fontColor: Colors.white, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + isRepeat ? const RepeatWidget() : const SizedBox(), + const SizedBox( + height: 40, + ) + ], + ), + ), + ); + })); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/onetime_password_page.dart b/lib/features/devices/view/widgets/smart_door/onetime_password_page.dart new file mode 100644 index 0000000..ba54fc5 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/onetime_password_page.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/devices/view/widgets/offline_one_time_password_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_dialog.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class OnetimePasswordPage extends StatelessWidget { + final String? deviceId; + const OnetimePasswordPage({super.key, this.deviceId,}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!)..add(InitialOneTimePassword( )), + child: BlocConsumer( + listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + }, + builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + title: 'Passwords', + actions: [ + IconButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => OfflineOneTimePasswordPage(deviceId: deviceId, ) + )).then((result) { + if(result!=null){ + smartDoorBloc.add(InitialOneTimePassword()); + smartDoorBloc.add(InitialOneTimePassword()); + } + }); + }, + icon: const Icon(Icons.add) + ) + ], + child: Builder( + builder: (context) { + return state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Center( + child: smartDoorBloc.oneTimePasswords!.isNotEmpty + ? ListView.builder( + itemCount: smartDoorBloc.oneTimePasswords!.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(5.0), + child: DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset(Assets.timeLimitedPasswordIcon), + title: BodyMedium( + text: 'Password Name: ${smartDoorBloc.oneTimePasswords![index].pwdName}', + fontWeight: FontWeight.normal, + ), + onTap: () async { + final result = await showDialog( + context: context, + builder: (context) { + return DoorDialog( + title: 'Password Information', + offline: smartDoorBloc.oneTimePasswords![index], + ); + }, + ); + + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ), + ); + }, + ) : Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.noValidPasswords), + const SizedBox( + height: 10, + ), + const BodyMedium(text: 'No Valid Passwords') + ], + ), + ); + }, + )); + }) + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/repeat_widget.dart b/lib/features/devices/view/widgets/smart_door/repeat_widget.dart new file mode 100644 index 0000000..b35b320 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/repeat_widget.dart @@ -0,0 +1,125 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class RepeatWidget extends StatelessWidget { + const RepeatWidget({ + super.key, + }); + + @override + Widget build(BuildContext context) { + + return BlocBuilder( + builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), + child: Column(children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + smartDoorBloc.add(const SetStartEndTimeEvent(val: true)); + }, + child: BodyMedium( + text: 'Start', + fontColor: smartDoorBloc.isStartEndTime == false + ? Colors.black + : Colors.blue, + fontSize: 18, + ), + ), + InkWell( + onTap: () { + smartDoorBloc.add(const SetStartEndTimeEvent(val: false)); + }, + child: BodyMedium( + text: 'End', + fontColor: smartDoorBloc.isStartEndTime + ? Colors.black + : Colors.blue, + fontSize: 18, + ), + ) + ], + ), + ), + const Divider( + color: ColorsManager.graysColor, + ), + smartDoorBloc.isStartEndTime + ? Container( + height: 110, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + initialDateTime:smartDoorBloc.startTime, + onDateTimeChanged: (startTime) { + smartDoorBloc.add(ChangeTimeEvent(val: startTime, isStartEndTime: true)); + }, + ) + ):SizedBox( + height: 110, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + initialDateTime:smartDoorBloc.endTime, + onDateTimeChanged: (endTime) { + smartDoorBloc.add(ChangeTimeEvent(val: endTime, isStartEndTime: false)); + }, + ) + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox(height: 20), + SizedBox( + height: MediaQuery.of(context).size.height *0.10, + child: ListView( + scrollDirection: Axis.horizontal, + children: smartDoorBloc.days.map((day) { + bool isSelected = smartDoorBloc.selectedDays.contains(day['key']); + return Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () { + smartDoorBloc.add(ToggleDaySelectionEvent(key:day['key']!)); + }, + child: Container( + width: 70, + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), + decoration: BoxDecoration( + border: Border.all( + color: isSelected? + Colors.black:ColorsManager.grayColor + ), + color: Colors.transparent, + borderRadius: BorderRadius.circular(55), + ), + child: Center( + child: Text( + day['day']!, + style: TextStyle( + fontSize: 18, + color: isSelected ? Colors.black : ColorsManager.grayColor, + ), + ), + ), + ), + ), + ); + }).toList(), + ) + ) + ])); + }); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/smart_linkage_view.dart b/lib/features/devices/view/widgets/smart_door/smart_linkage_view.dart new file mode 100644 index 0000000..444fb35 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/smart_linkage_view.dart @@ -0,0 +1,110 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SmartLinkgeView extends StatelessWidget { + const SmartLinkgeView({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Scene', + child: Column( + children: [ + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Row( + children: [ + SizedBox( + width: 45, + height: 40, + child: SvgPicture.asset( + Assets.assetsIconsLinkageIconsFamilyHome), + ), + const SizedBox(width: 15), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyLarge( + text: 'Family go home', + fontWeight: FontWeight.bold, + ), + BodySmall( + text: 'Trigger linkage when family member go home.', + ) + ], + ), + ), + const SizedBox(width: 10), + Transform.scale( + scale: 1, + child: CupertinoSwitch( + value: false, + onChanged: (value) {}, + applyTheme: true, + ), + ), + ], + ), + ), + const SizedBox(height: 10), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Row( + children: [ + SizedBox( + width: 45, + height: 40, + child: SvgPicture.asset( + Assets.assetsIconsLinkageIconsDoorLockAlarm), + ), + const SizedBox(width: 15), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyLarge( + text: 'Door Lock Alarm', + fontWeight: FontWeight.bold, + ), + BodySmall( + text: 'When door lock alarms, trigger linkage.', + ) + ], + ), + ), + const SizedBox(width: 10), + Transform.scale( + scale: 1, + child: CupertinoSwitch( + value: false, + onChanged: (value) {}, + applyTheme: true, + ), + ), + ], + ), + ), + const Spacer(), + DefaultContainer( + onTap: () {}, + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 10), + color: ColorsManager.primaryColorWithOpacity, + child: const BodyMedium( + text: 'Custom linkage Type', + fontColor: Colors.white, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart b/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart new file mode 100644 index 0000000..3544fe0 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart @@ -0,0 +1,130 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/onetime_password_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/timelimited_password_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/view_temporary_password.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class TemporaryPasswordPage extends StatelessWidget { + final String? deviceId; + const TemporaryPasswordPage({super.key,this.deviceId}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Temporary Password', + child: Column( + children: [ + const SizedBox(height: 10), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyLarge( + text: 'Online Password', + fontWeight: FontWeight.normal, + fontColor: Color(0xfff999999), + ), + const BodyMedium( + text: 'We recommend that you use the online password when the Wi-Fi network is stable.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox(height: 10), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child:ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + Assets.timeLimitedPasswordIcon), + title: const BodyMedium( + text: 'Time-Limited Password', + fontWeight: FontWeight.normal, + ), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => + ViewTemporaryPassword( + deviceId:deviceId, + type:'Online Password'), + )); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ), + ], + ), + const SizedBox(height: 10), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyLarge( + text: 'Offline Password', + fontWeight: FontWeight.normal, + fontColor: Color(0xfff999999), + ), + const BodyMedium( + text: 'WWe recommend that you use the offline password when the Wi-Fi is disconnected or in an unstable Wi-Fi environment.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox(height: 10), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Column( + children: [ + ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset(Assets.oneTimePassword), + title: + const BodyMedium( + text: 'One-Time Password', + fontWeight: FontWeight.normal, + ), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => OnetimePasswordPage(deviceId:deviceId,),)); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + const Divider(color:ColorsManager.graysColor,), + ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + Assets.timeLimitedPassword), + title: const BodyMedium( + text: 'Time-Limited Password', + fontWeight: FontWeight.normal, + ), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => TimeLimitedPasswordPage(deviceId:deviceId,),)); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ],) + ), + ], + ), + const Spacer(), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/timelimited_password_page.dart b/lib/features/devices/view/widgets/smart_door/timelimited_password_page.dart new file mode 100644 index 0000000..fce92b9 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/timelimited_password_page.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/offline_timeLimit_password_page.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'door_dialog.dart'; + +class TimeLimitedPasswordPage extends StatelessWidget { + final String? deviceId; + const TimeLimitedPasswordPage({super.key, this.deviceId}); + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!)..add(InitialTimeLimitPassword()), + child: BlocConsumer( + listener: (context, state) { + if (state is FailedState) { + CustomSnackBar.displaySnackBar( + state.errorMessage + ); + } + }, + builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + title: 'Passwords', + actions: [ + IconButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => CreateOfflineTimeLimitPasswordPage(deviceId: deviceId,) + )).then((result) { + smartDoorBloc.add(InitialTimeLimitPassword()); + smartDoorBloc.add(InitialTimeLimitPassword()); + }); + }, + icon: const Icon(Icons.add) + ) + ], + child: Builder( + builder: (context) { + return state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Center( + child: smartDoorBloc.timeLimitPasswords!.isNotEmpty + ? ListView.builder( + itemCount: smartDoorBloc.timeLimitPasswords!.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(5.0), + child: DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset(Assets.timeLimitedPasswordIcon), + title: BodyMedium( + text: 'Password Name: ${smartDoorBloc.timeLimitPasswords![index].pwdName}', + fontWeight: FontWeight.normal, + ), + onTap: () async { + final result = await showDialog( + context: context, + builder: (context) { + return DoorDialog( + title: 'Password Information', + offline: smartDoorBloc.timeLimitPasswords![index], + ); + }, + ); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ), + ); + }, + ) : Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.noValidPasswords), + const SizedBox( + height: 10, + ), + const BodyMedium(text: 'No Valid Passwords') + ], + ), + ); + }, + )); + }) + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/unlocking_records_view.dart b/lib/features/devices/view/widgets/smart_door/unlocking_records_view.dart new file mode 100644 index 0000000..2a20a38 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/unlocking_records_view.dart @@ -0,0 +1,316 @@ +// ignore_for_file: constant_identifier_names + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class UnlockingRecordsView extends StatelessWidget { + const UnlockingRecordsView({super.key}); + + @override + Widget build(BuildContext context) { + List>> sortedRecords = + sortRecordsByDate(unlockingRecords); + return DefaultScaffold( + title: 'Unlocking Records', + child: SingleChildScrollView( + child: Column( + children: [ + for (var records in sortedRecords) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: BodyMedium( + fontWeight: FontWeight.bold, + fontColor: Colors.grey, + text: + '${records[0]['day']} ${formatDate(DateTime.parse(records[0]['date'].toString()))}'), + ), + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + child: Column( + children: [ + for (int i = 0; i < records.length; i++) + Column( + children: [ + Row( + children: [ + SizedBox.square( + dimension: 30, + child: records[i]['method'] == + UnlockingMethod.Fingerprint + ? SvgPicture.asset(Assets + .assetsIconsUnlockingMethodsIconsFingerprint) + : records[i]['method'] == + UnlockingMethod.Remote + ? SvgPicture.asset(Assets + .assetsIconsUnlockingMethodsIconsRemote) + : SvgPicture.asset(Assets + .assetsIconsUnlockingMethodsIconsFace), + ), + const SizedBox( + width: 13, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + BodyMedium( + text: records[i]['title'], + ), + BodySmall( + fontColor: Colors.grey, + text: formatTime( + records[i]['time'], + ), + ), + ], + ) + ], + ), + i != records.length - 1 + ? Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 15, + left: 40), + child: Container( + height: 1, + color: ColorsManager.greyColor, + ), + ) + : const SizedBox.shrink(), + ], + ), + ], + )) + ], + ), + ], + ), + ), + ); + } + + String formatDate(DateTime date) { + return '${date.year}/${date.month.toString().padLeft(2, '0')}/${date.day.toString().padLeft(2, '0')}'; + } + + //time from 1200 to 12:00 + String formatTime(int time) { + return '${(time / 100).floor()}:${(time % 100).toString().padLeft(2, '0')}'; + } + + /// Sorts the given [records] by date and returns a nested list of records grouped by date. + /// + /// The [records] parameter is a list of maps, where each map represents a record with a 'date' key. + /// The records are sorted in ascending order based on the 'date' value. + /// The returned nested list contains sublists, where each sublist represents records for a specific date. + /// + /// Example usage: + /// ```dart + /// List> records = [ + /// {'date': '2022-01-01', 'data': 'Record 1'}, + /// {'date': '2022-01-02', 'data': 'Record 2'}, + /// {'date': '2022-01-01', 'data': 'Record 3'}, + /// ]; + /// + /// List>> sortedRecords = sortRecordsByDate(records); + /// print(sortedRecords); + /// ``` + /// This Dart function, sortRecordsByDate, takes a list of records as input, + /// where each record is a map with string keys and dynamic values. The + /// function's purpose is to sort these records by date and return a list of + /// lists, where each sublist contains records for a specific date. +// The function begins by initializing two empty lists: sortedRecords and dates. +// The sortedRecords list will eventually hold the sorted records, while the +//dates list will hold all unique dates present in the records. + +// The first for loop iterates over each record in the input list. For each +//record, it parses the 'date' field into a DateTime object and checks if this +//date is already in the dates list. If the date is not already in the list, it +// is added. This way, the dates list ends up holding all unique dates present +//in the records. + +// Next, the dates list is sorted in ascending order using the sort method with +// a comparator function that compares two DateTime objects. + +// The second for loop iterates over each unique date. For each date, it +//initializes an empty list recordsForDate to hold all records for that date. +// It then iterates over each record in the input list again. If a record's +//ate matches the current date (i.e., their comparison result is 0), the record +// is added to recordsForDate. After all records have been checked, r +//ecordsForDate is added to sortedRecords. + +// Finally, the function returns sortedRecords, which is a list of lists of +//records, where each sublist contains all records for a specific date, and the +//sublists are sorted in ascending order of date. + List>> sortRecordsByDate( + List> records) { + List>> sortedRecords = []; + List dates = []; + for (var record in records) { + if (!dates.contains(DateTime.parse(record['date'].toString()))) { + dates.add(DateTime.parse(record['date'].toString())); + } + } + dates.sort((a, b) => a.compareTo(b)); + for (var date in dates) { + List> recordsForDate = []; + for (var record in records) { + if (DateTime.parse(record['date'].toString()).compareTo(date) == 0) { + recordsForDate.add(record); + } + } + sortedRecords.add(recordsForDate); + } + return sortedRecords; + } +} + +enum UnlockingMethod { + Fingerprint, + Password, + Card, + Face, + Remote, +} + +List> unlockingRecords = [ + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240305, + 'day': 'Tuesday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Face", + 'time': 1200, + 'date': 20240305, + 'day': 'Tuesday', + 'method': UnlockingMethod.Face, + }, + { + 'title': "XXX Remote", + 'time': 1200, + 'date': 20240305, + 'day': 'Tuesday', + 'method': UnlockingMethod.Remote, + }, + { + 'title': "XXX Face", + 'time': 1200, + 'date': 20240405, + 'day': 'Wednesday', + 'method': UnlockingMethod.Face, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240405, + 'day': 'Wednesday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240405, + 'day': 'Wednesday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Remote", + 'time': 1500, + 'date': 20240505, + 'day': 'Thursday', + 'method': UnlockingMethod.Remote, + }, + { + 'title': "XXX Face", + 'time': 1200, + 'date': 20240505, + 'day': 'Thursday', + 'method': UnlockingMethod.Face, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240505, + 'day': 'Thursday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240505, + 'day': 'Thursday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Remote", + 'time': 1500, + 'date': 20240505, + 'day': 'Thursday', + 'method': UnlockingMethod.Remote, + }, + { + 'title': "XXX Face", + 'time': 1200, + 'date': 20240605, + 'day': 'Friday', + 'method': UnlockingMethod.Face, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240605, + 'day': 'Friday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240605, + 'day': 'Friday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Remote", + 'time': 1500, + 'date': 20240705, + 'day': 'Saturday', + 'method': UnlockingMethod.Remote, + }, + { + 'title': "XXX Face", + 'time': 1200, + 'date': 20240705, + 'day': 'Saturday', + 'method': UnlockingMethod.Face, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240705, + 'day': 'Saturday', + 'method': UnlockingMethod.Fingerprint, + }, + { + 'title': "XXX Fingerprint", + 'time': 1200, + 'date': 20240705, + 'day': 'Saturday', + 'method': UnlockingMethod.Fingerprint, + } +]; diff --git a/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart b/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart new file mode 100644 index 0000000..03bf22f --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'create_temporary_password.dart'; +import 'door_dialog.dart'; + +class ViewTemporaryPassword extends StatelessWidget { + final String? deviceId; + final String? type; + const ViewTemporaryPassword({super.key, this.deviceId, this.type}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!)..add(InitialPasswordsPage( )), + child: BlocConsumer( + listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + }, + builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + title: 'Passwords', + actions: [ + IconButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => + CreateTemporaryPassword(deviceId: deviceId, type: type))).then((result) { + if (result != null && result) { + smartDoorBloc.add(InitialPasswordsPage()); + smartDoorBloc.add(InitialPasswordsPage()); + } + }); + }, + icon: const Icon(Icons.add) + ) + ], + child: Builder( + builder: (context) { + return state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Center( + child: smartDoorBloc.temporaryPasswords!.isNotEmpty + ? ListView.builder( + itemCount: smartDoorBloc.temporaryPasswords!.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(5.0), + child: DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + Assets.timeLimitedPasswordIcon), + title: BodyMedium( + text: 'Password Name: ${smartDoorBloc.temporaryPasswords![index].name}', + fontWeight: FontWeight.normal, + ), + onTap: () async { + final result = await showDialog( + context: context, + builder: (context) { + return DoorDialog( + title: 'Password Information', + temporaryPassword: smartDoorBloc.temporaryPasswords![index], + ); + }, + ); + if(result=='delete'){ + smartDoorBloc.add(DeletePasswordEvent(passwordId: smartDoorBloc.temporaryPasswords![index].id.toString())); + } + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ), + ); + }, + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.noValidPasswords), + const SizedBox( + height: 10, + ), + const BodyMedium(text: 'No Valid Passwords') + ], + ), + ); + }, + )); + }) + ); + } +} diff --git a/lib/features/devices/view/widgets/three_gang/gang_switch.dart b/lib/features/devices/view/widgets/three_gang/gang_switch.dart new file mode 100644 index 0000000..c0bd8f9 --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/gang_switch.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +// import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart'; +// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class GangSwitch extends StatelessWidget { + const GangSwitch( + {super.key, required this.threeGangSwitch, required this.value, required this.action}); + + final DeviceModel threeGangSwitch; + final bool value; + final Function action; + + @override + Widget build(BuildContext context) { + return InkWell( + overlayColor: MaterialStateProperty.all(Colors.transparent), + onTap: () { + action(); + // var tempControl = DeviceControlModel( + // deviceId: control.deviceId, code: control.code!, value: !control.value!); + // DevicesCubit.getInstance().deviceControl( + // tempControl, + // control.deviceId!, + // ); + }, + child: Stack( + alignment: value ? Alignment.topCenter : Alignment.bottomCenter, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(100.0)), + color: value ? ColorsManager.primaryColorWithOpacity : ColorsManager.switchOffColor, + ), + width: 60, + height: 115, + ), + Padding( + padding: const EdgeInsets.all(5.0), + child: SizedBox.square( + dimension: 60, + child: SvgPicture.asset( + value ? Assets.assetsIconsLightSwitchOn : Assets.assetsIconsLightSwitchOff, + fit: BoxFit.fill, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/three_gang/schedule_screen.dart b/lib/features/devices/view/widgets/three_gang/schedule_screen.dart new file mode 100644 index 0000000..1c527e3 --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/schedule_screen.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/timer_screen.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class ScheduleScreen extends StatelessWidget { + final DeviceModel device; + const ScheduleScreen({required this.device, super.key}); + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Schedule', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + body: SafeArea( + child: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 48, + ), + DefaultContainer( + width: MediaQuery.sizeOf(context).width, + child: Column( + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => TimerScreen( + device: device, + deviceCode: 'countdown_1', + ))); + }, + child: Container( + padding: + const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodySmall( + text: "Bedside Light", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 18, + ) + ], + ), + ), + ), + const Divider( + color: ColorsManager.dividerColor, + ), + InkWell( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => TimerScreen( + device: device, + deviceCode: 'countdown_2', + ))); + }, + child: Container( + padding: + const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodySmall( + text: "Ceiling Light", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 18, + ) + ], + ), + ), + ), + const Divider( + color: ColorsManager.dividerColor, + ), + InkWell( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => TimerScreen( + device: device, + deviceCode: 'countdown_3', + ))); + }, + child: Container( + padding: + const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodySmall( + text: "Spotlight", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 18, + ) + ], + ), + ), + ), + ], + )), + ], + ), + ), + ), + )); + } +} diff --git a/lib/features/devices/view/widgets/three_gang/three_gang_interface.dart b/lib/features/devices/view/widgets/three_gang/three_gang_interface.dart new file mode 100644 index 0000000..c509d97 --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/three_gang_interface.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_screen.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class ThreeGangInterface extends StatelessWidget { + const ThreeGangInterface({super.key, this.gangSwitch}); + + final DeviceModel? gangSwitch; + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: gangSwitch != null + ? DeviceAppbar( + deviceName: gangSwitch!.name!, + deviceUuid: gangSwitch!.uuid!, + ) + : AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: gangSwitch?.name ?? 'Lights', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.only( + left: Constants.defaultPadding, + right: Constants.defaultPadding, + bottom: Constants.bottomNavBarHeight, + ), + child: ThreeGangScreen(device: gangSwitch), + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/three_gang/three_gang_list.dart b/lib/features/devices/view/widgets/three_gang/three_gang_list.dart new file mode 100644 index 0000000..0d9b143 --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/three_gang_list.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart'; +import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class ThreeGangList extends StatelessWidget { + const ThreeGangList({super.key, required this.threeGangList, required this.allSwitches}); + + final List threeGangList; + final bool allSwitches; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 10), + const BodySmall(text: 'All Lights'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: allSwitches, + action: () { + BlocProvider.of(context).add(GroupAllOnEvent()); + }, + secondAction: () { + BlocProvider.of(context).add(GroupAllOffEvent()); + }, + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0), + itemCount: threeGangList.length, + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + BodySmall(text: '${threeGangList[index].deviceName} beside light'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: threeGangList[index].firstSwitch, + action: () { + BlocProvider.of(context).add(ChangeFirstSwitchStatusEvent( + value: threeGangList[index].firstSwitch, + deviceId: threeGangList[index].deviceId)); + }, + ), + const SizedBox(height: 10), + BodySmall(text: '${threeGangList[index].deviceName} ceiling light'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: threeGangList[index].secondSwitch, + action: () { + BlocProvider.of(context).add(ChangeSecondSwitchStatusEvent( + value: threeGangList[index].secondSwitch, + deviceId: threeGangList[index].deviceId)); + }, + ), + const SizedBox(height: 10), + BodySmall(text: '${threeGangList[index].deviceName} spotlight'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: threeGangList[index].thirdSwitch, + action: () { + BlocProvider.of(context).add(ChangeThirdSwitchStatusEvent( + value: threeGangList[index].thirdSwitch, + deviceId: threeGangList[index].deviceId)); + }, + ), + ], + ); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart b/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart new file mode 100644 index 0000000..3b42646 --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart @@ -0,0 +1,401 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart'; +import 'package:syncrow_app/features/devices/model/three_gang_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/schedule_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class ThreeGangScreen extends StatelessWidget { + const ThreeGangScreen({super.key, this.device}); + + final DeviceModel? device; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ThreeGangBloc(threeGangId: device?.uuid ?? '') + ..add(InitialEvent(groupScreen: device != null ? false : true)), + child: BlocBuilder( + builder: (context, state) { + ThreeGangModel threeGangModel = ThreeGangModel( + firstSwitch: false, + secondSwitch: false, + thirdSwitch: false, + firstCountDown: 0, + secondCountDown: 0, + thirdCountDown: 0); + + List groupThreeGangModel = []; + bool allSwitchesOn = false; + + if (state is LoadingNewSate) { + threeGangModel = state.threeGangModel; + } else if (state is UpdateState) { + threeGangModel = state.threeGangModel; + } else if (state is UpdateGroupState) { + groupThreeGangModel = state.threeGangList; + allSwitchesOn = state.allSwitches; + } + return state is LoadingInitialState + ? const Center( + child: + DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), + ) + : device == null + ? ThreeGangList( + threeGangList: groupThreeGangModel, + allSwitches: allSwitchesOn, + ) + : RefreshIndicator( + onRefresh: () async { + BlocProvider.of(context) + .add(InitialEvent(groupScreen: device != null ? false : true)); + }, + child: ListView( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Expanded(child: SizedBox.shrink()), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + children: [ + GangSwitch( + threeGangSwitch: device!, + value: threeGangModel.firstSwitch, + action: () { + BlocProvider.of(context).add( + ChangeFirstSwitchStatusEvent( + value: threeGangModel.firstSwitch)); + }, + // control: DeviceControlModel( + // deviceId: device.uuid, + // // code: 'switch_1', + // code: device.status[0].code, + // // value: true, + // value: device.status[0].value, + // ), + ), + const SizedBox(height: 20), + const SizedBox( + width: 70, + child: BodySmall( + text: "Bedside Light", + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ), + Column( + children: [ + GangSwitch( + threeGangSwitch: device!, + value: threeGangModel.secondSwitch, + action: () { + BlocProvider.of(context).add( + ChangeSecondSwitchStatusEvent( + value: threeGangModel.secondSwitch)); + }, + // control: DeviceControlModel( + // // deviceId: 'bfe10693d4fd263206ocq9', + // // code: 'switch_2', + // // value: true, + // deviceId: device.uuid, + // code: device.status[1].code, + // value: device.status[1].value, + // ), + ), + const SizedBox(height: 20), + const SizedBox( + width: 70, + child: BodySmall( + text: "Ceiling Light", + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ), + Column( + children: [ + GangSwitch( + threeGangSwitch: device!, + value: threeGangModel.thirdSwitch, + action: () { + BlocProvider.of(context).add( + ChangeThirdSwitchStatusEvent( + value: threeGangModel.thirdSwitch)); + }, + ), + const SizedBox(height: 20), + const SizedBox( + width: 70, + child: BodySmall( + text: "Spotlight", + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + ), + ), + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + onTap: () { + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_1', + // value: true, + // ), + // device.uuid!, + // ); + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_2', + // value: true, + // ), + // device.uuid!, + // ); + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_3', + // value: true, + // ), + // device.uuid!, + // ); + BlocProvider.of(context) + .add(AllOnEvent()); + }, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(100), + ), + ), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(100), + ), + child: Center( + child: BodySmall( + text: "On", + style: context.bodyMedium.copyWith( + color: ColorsManager + .primaryColorWithOpacity, + fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + text: "All On", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ), + const SizedBox( + width: 20, + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: + (context, animation1, animation2) => + ScheduleScreen( + device: device!, + ))); + }, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(100), + ), + ), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(100), + ), + child: Center( + child: Icon( + Icons.access_time, + color: + ColorsManager.primaryColorWithOpacity, + size: 25, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + text: "Timer", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ), + const SizedBox( + width: 20, + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Card( + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + child: GestureDetector( + onTap: () { + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_1', + // value: false, + // ), + // device.uuid!, + // ); + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_2', + // value: false, + // ), + // device.uuid!, + // ); + // DevicesCubit.getInstance().deviceControl( + // DeviceControlModel( + // deviceId: device.uuid, + // code: 'switch_3', + // value: false, + // ), + // device.uuid!, + // ); + BlocProvider.of(context) + .add(AllOffEvent()); + }, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(100), + ), + ), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(100), + ), + child: Center( + child: BodySmall( + text: "Off", + style: context.bodyMedium.copyWith( + color: ColorsManager + .primaryColorWithOpacity, + fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 10), + BodySmall( + text: "All Off", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + ), + ), + ], + ), + ], + ), + ), + Expanded(child: SizedBox()) + ], + ), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/three_gang/timer_screen.dart b/lib/features/devices/view/widgets/three_gang/timer_screen.dart new file mode 100644 index 0000000..d5e1c8b --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/timer_screen.dart @@ -0,0 +1,185 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart'; +import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class TimerScreen extends StatelessWidget { + final DeviceModel device; + final String deviceCode; + const TimerScreen({required this.device, required this.deviceCode, super.key}); + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Schedule', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + ), + body: SafeArea( + child: BlocProvider( + create: (context) => ThreeGangBloc(threeGangId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)), + child: BlocBuilder( + builder: (context, state) { + Duration duration = Duration.zero; + int countNum = 0; + int tabNum = 0; + if (state is UpdateTimerState) { + countNum = state.seconds; + } else if (state is TimerRunInProgress) { + countNum = state.remainingTime; + } else if (state is TimerRunComplete) { + countNum = 0; + } else if (state is LoadingNewSate) { + countNum = 0; + } + + if (state is ChangeSlidingSegmentState) { + tabNum = state.value; + } + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (!didPop) { + BlocProvider.of(context).add(OnClose()); + Navigator.pop(context); + } + }, + child: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + padding: const EdgeInsets.all(24), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: state is LoadingInitialState || state is LoadingNewSate + ? const Center( + child: DefaultContainer( + width: 50, height: 50, child: CircularProgressIndicator()), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + decoration: const ShapeDecoration( + color: ColorsManager.slidingBlueColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + child: CupertinoSlidingSegmentedControl( + thumbColor: ColorsManager.slidingBlueColor, + onValueChanged: (value) { + BlocProvider.of(context) + .add(ChangeSlidingSegment(value: value ?? 0)); + }, + groupValue: + state is ChangeSlidingSegmentState ? state.value : 0, + backgroundColor: Colors.white, + children: { + 0: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: BodySmall( + text: 'Countdown', + style: context.bodySmall.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ), + 1: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + 'Schedule', + style: context.bodySmall.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ), + }, + ), + ), + if (tabNum == 0) + countNum > 0 + ? BodyLarge( + text: _formatDuration(countNum), + fontColor: ColorsManager.slidingBlueColor, + fontSize: 40, + ) + : CupertinoTimerPicker( + mode: CupertinoTimerPickerMode.hm, + onTimerDurationChanged: (Duration newDuration) { + duration = newDuration; + }, + ), + if (tabNum == 0) + GestureDetector( + onTap: () { + if (state is LoadingNewSate) { + return; + } + if (countNum > 0) { + BlocProvider.of(context).add( + SetCounterValue( + deviceCode: deviceCode, + duration: Duration.zero)); + } else if (duration != Duration.zero) { + BlocProvider.of(context).add( + SetCounterValue( + deviceCode: deviceCode, duration: duration)); + } + }, + child: SvgPicture.asset( + countNum > 0 ? Assets.pauseIcon : Assets.playIcon)) + ], + ))); + }, + ), + ), + ), + ), + ); + } + + String _formatDuration(int seconds) { + final hours = (seconds ~/ 3600).toString().padLeft(2, '0'); + final minutes = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0'); + final secs = (seconds % 60).toString().padLeft(2, '0'); + return '$hours:$minutes:$secs'; + } +} diff --git a/lib/features/devices/view/widgets/universal_switch.dart b/lib/features/devices/view/widgets/universal_switch.dart new file mode 100644 index 0000000..0cd5a85 --- /dev/null +++ b/lib/features/devices/view/widgets/universal_switch.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class UniversalSwitch extends StatelessWidget { + const UniversalSwitch({ + super.key, + required this.allOn, + }); + + final bool allOn; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + // bool? status = category.devicesStatus; + return Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + BlocProvider.of(context).add(const ChangeAllSwitch(value: true)); + }, + child: Container( + height: 60, + decoration: BoxDecoration( + color: allOn ? ColorsManager.primaryColor : Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + bottomLeft: Radius.circular(15), + ), + ), + child: Center( + child: BodyMedium( + text: StringsManager.on, + fontColor: allOn ? Colors.white : null, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + Expanded( + child: InkWell( + onTap: () { + BlocProvider.of(context).add(const ChangeAllSwitch(value: false)); + }, + child: Container( + height: 60, + decoration: BoxDecoration( + color: allOn ? Colors.white : ColorsManager.primaryColor, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + ), + child: Center( + child: BodyMedium( + text: StringsManager.off, + fontColor: allOn ? null : Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/features/devices/view/widgets/wall_sensor/parameters_list.dart b/lib/features/devices/view/widgets/wall_sensor/parameters_list.dart new file mode 100644 index 0000000..ccb3d01 --- /dev/null +++ b/lib/features/devices/view/widgets/wall_sensor/parameters_list.dart @@ -0,0 +1,342 @@ +part of "wall_sensor_interface.dart"; + +class ParametersList extends StatelessWidget { + const ParametersList({ + super.key, + required this.wallSensor, + required this.presenceSensorsModel, + }); + + final DeviceModel wallSensor; + final WallSensorModel presenceSensorsModel; + @override + Widget build(BuildContext context) { + return Expanded( + flex: 2, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: List.generate( + wallSensorButtons.length, + (index) { + if (index == 3) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + DefaultContainer( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () async { + if ((wallSensor.isOnline ?? false) == false) { + return; + } + String controlCode = 'motion_sensitivity_value'; + final result = await showDialog( + context: context, + builder: (context) => ParameterControlDialog( + title: 'Motion Detection Sensitivity', + sensor: wallSensor, + value: presenceSensorsModel.motionSensitivity, + min: wallSensor.functions + .firstWhere((element) => element.code == controlCode) + .values + ?.min ?? + 0, + max: wallSensor.functions + .firstWhere((element) => element.code == controlCode) + .values + ?.max ?? + 0, + ), + ); + if (result != null) { + BlocProvider.of(context) + .add(ChangeValueEvent(value: result, code: controlCode)); + } + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + const BodySmall( + text: 'Motion\nDetection\nSensitivity', + textAlign: TextAlign.center), + BodyLarge( + text: presenceSensorsModel.motionSensitivity.toString(), + style: context.bodyLarge.copyWith(fontWeight: FontsManager.bold), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Container( + width: 1, + height: 45, + color: ColorsManager.greyColor, + ), + ), + InkWell( + onTap: () async { + if ((wallSensor.isOnline ?? false) == false) { + return; + } + String controlCode = 'motionless_sensitivity'; + final result = await showDialog( + context: context, + builder: (context) => ParameterControlDialog( + title: 'Motionless Detection Sensitivity', + sensor: wallSensor, + value: presenceSensorsModel.motionlessSensitivity, + min: wallSensor.functions + .firstWhere((element) => element.code == controlCode) + .values + ?.min ?? + 0, + max: wallSensor.functions + .firstWhere((element) => element.code == controlCode) + .values + ?.max ?? + 0, + ), + ); + + if (result != null) { + BlocProvider.of(context) + .add(ChangeValueEvent(value: result, code: controlCode)); + } + }, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodySmall( + text: 'Motionless\nDetection\nSensitivity', + textAlign: TextAlign.center), + BodyLarge( + text: presenceSensorsModel.motionlessSensitivity.toString(), + style: context.bodyLarge.copyWith( + fontWeight: FontsManager.bold, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Container( + width: 1, + height: 45, + color: ColorsManager.greyColor, + ), + ), + InkWell( + onTap: () async { + if ((wallSensor.isOnline ?? false) == false) { + return; + } + + String controlCode = 'far_detection'; + final result = await showDialog( + context: context, + builder: (context) => ParameterControlDialog( + title: 'Far Detection', + sensor: wallSensor, + value: presenceSensorsModel.farDetection, + min: wallSensor.functions + .firstWhere((element) => element.code == controlCode) + .values + ?.min ?? + 0, + max: wallSensor.functions + .firstWhere((element) => element.code == controlCode) + .values + ?.max ?? + 0, + ), + ); + if (result != null) { + BlocProvider.of(context) + .add(ChangeValueEvent(value: result, code: controlCode)); + } + }, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodySmall( + text: 'Far\nDetection', textAlign: TextAlign.center), + BodyLarge( + text: + '${presenceSensorsModel.farDetection.toString()}${wallSensor.functions.firstWhere((element) => element.code == 'far_detection').values?.unit ?? ''}', + style: context.bodyLarge.copyWith( + fontWeight: FontsManager.bold, + ), + ), + ], + ), + ), + ], + ), + ), + listItem(wallSensorButtons[index], context, wallSensor, presenceSensorsModel) + ], + ); + } + return listItem(wallSensorButtons[index], context, wallSensor, presenceSensorsModel); + }, + ), + ), + ), + ); + } + +//{"result":{"productId":"awarhusb","productType":"WPS","status":[{"code":"presence_state","value":"none"},{"code":"far_detection","value":75},{"code":"presence_time","value":0},{"code":"motion_sensitivity_value","value":5},{"code":"motionless_sensitivity","value":5},{"code":"dis_current","value":214},{"code":"illuminance_value","value":231},{"code":"indicator","value":true}]},"success":true} + final List> wallSensorButtons = const [ + { + 'icon': Assets.assetsIconsPresenceSensorAssetsTime, + 'title': 'Presence Time', + 'code': 'presence_time', + }, + { + 'icon': Assets.assetsIconsPresenceSensorAssetsDistance, + 'title': 'Current Distance', + 'code': 'dis_current', + }, + { + 'icon': Assets.assetsIconsPresenceSensorAssetsIlluminanceValue, + 'title': 'Illuminance Value', + 'code': 'illuminance_value', + }, + { + 'icon': Assets.assetsIconsPresenceSensorAssetsEmpty, + 'title': 'Nobody Time', + 'code': null, + //TODO: Implement the nobody time + }, + { + 'icon': Assets.assetsIconsPresenceSensorAssetsIndicator, + 'title': 'Indicator', + 'code': 'indicator', + }, + { + 'icon': Assets.assetsIconsPresenceSensorAssetsRecord, + 'title': 'Presence Record', + 'code': null + }, + { + 'icon': Assets.assetsIconsPresenceSensorAssetsIlluminanceRecord, + 'title': 'Illuminance Record', + 'code': null + }, + ]; +} + +Widget listItem(Map wallSensorButton, BuildContext context, DeviceModel wallSensor, + WallSensorModel wallSensorStatus) { + String? unit; + dynamic value; + if (wallSensorButton['code'] != null) { + // if (wallSensor.status.any((element) => element.code == wallSensorButton['code'] as String)) { + // unit = unitsMap[wallSensorButton['code'] as String]; + // value = wallSensor.status + // .firstWhere((element) => element.code == wallSensorButton['code'] as String) + // .value; + // } + if (wallSensorButton['code'] == 'presence_time') { + unit = unitsMap[wallSensorButton['code'] as String]; + value = wallSensorStatus.presenceTime; + } else if (wallSensorButton['code'] == 'dis_current') { + unit = unitsMap[wallSensorButton['code'] as String]; + value = wallSensorStatus.currentDistance; + } else if (wallSensorButton['code'] == 'illuminance_value') { + unit = unitsMap[wallSensorButton['code'] as String]; + value = wallSensorStatus.illuminance; + } + } + return DefaultContainer( + margin: const EdgeInsets.only(bottom: 5), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20), + onTap: () { + if (wallSensorButton['page'] != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => wallSensorButton['page'] as Widget, + ), + ); + } + }, + child: Row( + children: [ + SvgPicture.asset( + wallSensorButton['icon'] as String, + ), + const SizedBox( + width: 25, + ), + BodyMedium( + text: wallSensorButton['title'] as String, + ), + if (wallSensorButton['code'] != null) const Spacer(), + if (wallSensorButton['code'] != null) + if (wallSensorButton['title'] != 'Indicator') + BodyMedium( + text: '${value ?? 'N/A'}${unit ?? ''}', + style: context.bodyMedium.copyWith(color: ColorsManager.greyColor), + ), + if (wallSensorButton['title'] == 'Indicator') + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: wallSensorStatus.indicator, + onChanged: (value) { + if (wallSensor.isOnline ?? false) { + BlocProvider.of(context) + .add(ChangeIndicatorEvent(value: wallSensorStatus.indicator)); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Device is offline'), + backgroundColor: Colors.red, + ), + ); + } + }, + applyTheme: true, + )), + ], + ), + ), + if (wallSensorButton['title'] == 'Nobody Time') + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ], + ), + ], + ), + ); +} + +Map unitsMap = { + 'presence_time': 's', + 'dis_current': 'cm', + 'illuminance_value': 'lux', + 'far_detection': 'cm', + 'motion_sensitivity_value': '', + 'motionless_sensitivity': '', +}; diff --git a/lib/features/devices/view/widgets/wall_sensor/presence_indicator.dart b/lib/features/devices/view/widgets/wall_sensor/presence_indicator.dart new file mode 100644 index 0000000..74ebf83 --- /dev/null +++ b/lib/features/devices/view/widgets/wall_sensor/presence_indicator.dart @@ -0,0 +1,35 @@ +part of "wall_sensor_interface.dart"; + +class PresenceIndicator extends StatelessWidget { + const PresenceIndicator({super.key, required this.state}); + final String state; + @override + Widget build(BuildContext context) { + // String state = wallSensor.status + // .firstWhere((element) => element.code == "presence_state") + // .value + // .toString(); + return Expanded( + flex: 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + state.toLowerCase() == 'motion' + ? Assets.assetsIconsPresenceSensorAssetsPresenceSensorMotion + : Assets.assetsIconsPresenceSensorAssetsPresence, + width: 100, + height: 100, + ), + const SizedBox( + height: 10, + ), + BodyMedium( + text: StringHelpers.toTitleCase(state), + style: context.bodyMedium.copyWith(fontWeight: FontsManager.bold), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart b/lib/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart new file mode 100644 index 0000000..00b2373 --- /dev/null +++ b/lib/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart @@ -0,0 +1,111 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/wall_sensor_bloc/wall_sensor_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/wall_sensor_bloc/wall_sensor_state.dart'; +import 'package:syncrow_app/features/devices/bloc/wall_sensor_bloc/wall_sensor_event.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/wall_sensor_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/misc_string_helpers.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; +part "parameters_list.dart"; +part "presence_indicator.dart"; +part "../ceiling_sensor/parameter_control_dialog.dart"; + +class WallMountedInterface extends StatelessWidget { + const WallMountedInterface({super.key, required this.deviceModel}); + final DeviceModel deviceModel; + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => WallSensorBloc(deviceId: deviceModel.uuid ?? '')..add(InitialEvent()), + child: BlocBuilder(builder: (context, state) { + WallSensorModel wallSensorModel = WallSensorModel( + presenceState: 'none', + farDetection: 0, + presenceTime: 0, + motionSensitivity: 0, + motionlessSensitivity: 0, + currentDistance: 0, + illuminance: 0, + indicator: false); + + if (state is UpdateState) { + wallSensorModel = state.wallSensorModel; + } else if (state is LoadingNewSate) { + wallSensorModel = state.wallSensorModel; + } + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: DeviceAppbar( + deviceName: deviceModel.name!, + deviceUuid: deviceModel.uuid!, + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + padding: const EdgeInsets.all(Constants.defaultPadding), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: state is LoadingInitialState + ? const Center( + child: RefreshProgressIndicator(), + ) + : SafeArea( + child: RefreshIndicator( + onRefresh: () async { + BlocProvider.of(context).add(InitialEvent()); + }, + child: ListView( + children: [ + Container( + height: MediaQuery.of(context).size.height, + child: Column( + children: [ + PresenceIndicator( + state: wallSensorModel.presenceState, + ), + ParametersList( + wallSensor: deviceModel, + presenceSensorsModel: wallSensorModel, + ), + ], + ), + ), + ], + ), + ), + ), + ), + )); + }), + ); + } +} diff --git a/lib/features/devices/view/widgets/wizard_page.dart b/lib/features/devices/view/widgets/wizard_page.dart new file mode 100644 index 0000000..6639321 --- /dev/null +++ b/lib/features/devices/view/widgets/wizard_page.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_interface.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class WizardPage extends StatelessWidget { + final List groupsList; + const WizardPage({super.key, required this.groupsList}); + + @override + Widget build(BuildContext context) { + return GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.5, + ), + padding: const EdgeInsets.only(top: 10), + shrinkWrap: true, + itemCount: groupsList.length, + itemBuilder: (_, index) { + return GestureDetector( + onTap: () { + if (groupsList[index].name == 'AC') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => const ACsView())); + } + if (groupsList[index].name == '3G') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const ThreeGangInterface())); + } + }, + child: DefaultContainer( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + groupsList[index].icon!, + fit: BoxFit.contain, + ), + // CustomSwitch( + ], + ), + FittedBox( + fit: BoxFit.scaleDown, + child: BodyLarge( + text: groupsList[index].name!, + style: context.bodyLarge.copyWith( + fontWeight: FontWeight.bold, + height: 0, + fontSize: 20, + color: Colors.grey, + ), + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/layout/bloc/layout_cubit.dart b/lib/features/layout/bloc/layout_cubit.dart new file mode 100644 index 0000000..db65846 --- /dev/null +++ b/lib/features/layout/bloc/layout_cubit.dart @@ -0,0 +1,7 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'layout_state.dart'; + +class LayoutCubit extends Cubit { + LayoutCubit() : super(LayoutInitial()); +} diff --git a/lib/features/layout/bloc/layout_state.dart b/lib/features/layout/bloc/layout_state.dart new file mode 100644 index 0000000..7127dc1 --- /dev/null +++ b/lib/features/layout/bloc/layout_state.dart @@ -0,0 +1,5 @@ +part of 'layout_cubit.dart'; + +abstract class LayoutState {} + +class LayoutInitial extends LayoutState {} diff --git a/lib/features/layout/view/layout_view.dart b/lib/features/layout/view/layout_view.dart new file mode 100644 index 0000000..ce265e5 --- /dev/null +++ b/lib/features/layout/view/layout_view.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/layout/bloc/layout_cubit.dart'; + +class LayoutPage extends StatelessWidget { + const LayoutPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => LayoutCubit(), + child: BlocBuilder( + builder: (context, state) { + return const Center( + child: Text('Layout Page'), + ); + }, + ), + ); + } +} diff --git a/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart b/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart new file mode 100644 index 0000000..e31fd2b --- /dev/null +++ b/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart @@ -0,0 +1,238 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; +import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart'; +import 'package:syncrow_app/services/api/home_creation_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class CreateUnitBloc extends Bloc { + List rooms = []; + String unitName = ''; + String locationName = ''; + + CreateUnitBloc() : super(InitialState()) { + on(_createRoom); + on(_removeRoom); + on(_save); + } + + void _createRoom(CreateRoomEvent event, Emitter emit) async { + try { + emit(LoadingState()); + if (rooms.contains(event.roomName)) { + emit(const ErrorState(message: 'This name is already exist')); + emit(CreateRoomState(roomList: rooms)); + return; + } + rooms.add(event.roomName); + emit(CreateRoomState(roomList: rooms)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + void _removeRoom(RemoveRoomEvent event, Emitter emit) { + try { + emit(LoadingState()); + rooms.removeWhere((element) => element == event.roomName); + emit(CreateRoomState(roomList: rooms)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + void _save(SaveRooms event, Emitter emit) async { + try { + if (event.unitName.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter the unit name'); + return; + } + if (rooms.isEmpty) { + CustomSnackBar.displaySnackBar('Please add at least one room!'); + return; + } + + emit(LoadingState()); + + var storage = const FlutterSecureStorage(); + var userId = await storage.read(key: UserModel.userUuidKey) ?? ''; + + Map communityBody = {'communityName': event.communityName}; + final response = await HomeCreation.createCommunity(communityBody); + if (response['data']['uuid'] != '') { + // final result = + // await _assignToCommunity(communityId: response['data']['uuid'], userId: userId); + + // if (result) { + String buildingId = await _createBuilding( + buildingName: event.buildingName, + communityId: response['data']['uuid'], + userId: userId); + + if (buildingId.isNotEmpty) { + final floorId = await _createFloor( + floorName: event.floorName, buildingId: buildingId, userId: userId); + + if (floorId.isNotEmpty) { + final unitId = + await _createUnit(unitName: event.unitName, floorId: floorId, userId: userId); + + if (unitId.isNotEmpty && rooms.isNotEmpty) { + rooms.forEach((room) async { + await _createNewRoom(roomName: room, unitId: unitId, userId: userId); + }); + } + } + } + // } + } + await HomeCubit.getInstance().fetchUnitsByUserId(); + emit(RoomsSavedSuccessfully()); + } catch (_) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } +} + +Future _assignToCommunity({required String communityId, required String userId}) async { + try { + Map body = { + 'communityUuid': communityId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToCommunity(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createBuilding( + {required String buildingName, required String communityId, required String userId}) async { + try { + Map body = {'buildingName': buildingName, 'communityUuid': communityId}; + final response = await HomeCreation.createBuilding(body); + // if (response['data']['uuid'] != '') { + // final result = await _assignToBuilding(buildingId: response['data']['uuid'], userId: userId); + + return response['data']['uuid'] ?? ''; + // } else { + // return ''; + // } + } catch (_) { + return ''; + } +} + +Future _assignToBuilding({required String buildingId, required String userId}) async { + try { + Map body = { + 'buildingUuid': buildingId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToBuilding(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createFloor( + {required String floorName, required String buildingId, required String userId}) async { + try { + Map body = {'floorName': floorName, 'buildingUuid': buildingId}; + final response = await HomeCreation.createFloor(body); + // if (response['data']['uuid'] != '') { + // final result = await _assignToFloor(buildingId: response['data']['uuid'], userId: userId); + + return response['data']['uuid'] ?? ''; + // } else { + // return ''; + // } + } catch (_) { + return ''; + } +} + +Future _assignToFloor({required String buildingId, required String userId}) async { + try { + Map body = { + 'floorUuid': buildingId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToFloor(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createUnit( + {required String unitName, required String floorId, required String userId}) async { + try { + Map body = {'unitName': unitName, 'floorUuid': floorId}; + final response = await HomeCreation.createUnit(body); + if (response['data']['uuid'] != '') { + final result = await _assignToUnit(unitId: response['data']['uuid'], userId: userId); + + return result ? response['data']['uuid'] : ''; + } else { + return ''; + } + } catch (_) { + return ''; + } +} + +Future _assignToUnit({required String unitId, required String userId}) async { + try { + Map body = { + 'unitUuid': unitId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToUnit(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} + +Future _createNewRoom( + {required String roomName, required String unitId, required String userId}) async { + try { + Map body = {'roomName': roomName, 'unitUuid': unitId}; + final response = await HomeCreation.createRoom(body); + // if (response['data']['uuid'] != '') { + // final result = await _assignToRoom(roomId: response['data']['uuid'], userId: userId); + + return response['data']['uuid'] ?? ''; + // } else { + // return ''; + // } + } catch (e) { + return ''; + } +} + +Future _assignToRoom({required String roomId, required String userId}) async { + try { + Map body = { + 'roomUuid': roomId, + 'userUuid': userId, + }; + final response = await HomeCreation.assignUserToRoom(body); + + return response['success'] ?? false; + } catch (_) { + return false; + } +} diff --git a/lib/features/menu/bloc/create_unit_bloc/create_unit_event.dart b/lib/features/menu/bloc/create_unit_bloc/create_unit_event.dart new file mode 100644 index 0000000..8c79c3e --- /dev/null +++ b/lib/features/menu/bloc/create_unit_bloc/create_unit_event.dart @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.dart'; + +abstract class CreateUnitEvent extends Equatable { + const CreateUnitEvent(); + + @override + List get props => []; +} + +class InitialEvent extends CreateUnitEvent {} + +class LoadingEvent extends CreateUnitEvent {} + +class CreateRoomEvent extends CreateUnitEvent { + final String roomName; + + const CreateRoomEvent({required this.roomName}); + + @override + List get props => [roomName]; +} + +class RemoveRoomEvent extends CreateUnitEvent { + final String roomName; + + const RemoveRoomEvent({required this.roomName}); + + @override + List get props => [roomName]; +} + +class SaveRooms extends CreateUnitEvent { + final String communityName; + final String buildingName; + final String floorName; + final String unitName; + + const SaveRooms({ + required this.communityName, + required this.buildingName, + required this.floorName, + required this.unitName, + }); + + @override + List get props => [communityName, buildingName, floorName, unitName]; +} diff --git a/lib/features/menu/bloc/create_unit_bloc/create_unit_state.dart b/lib/features/menu/bloc/create_unit_bloc/create_unit_state.dart new file mode 100644 index 0000000..511c637 --- /dev/null +++ b/lib/features/menu/bloc/create_unit_bloc/create_unit_state.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +abstract class CreateUnitState extends Equatable { + const CreateUnitState(); + + @override + List get props => []; +} + +class InitialState extends CreateUnitState {} + +class LoadingState extends CreateUnitState {} + +class CreateRoomState extends CreateUnitState { + final List roomList; + + const CreateRoomState({required this.roomList}); + + @override + List get props => [roomList]; +} + +class RoomsSavedSuccessfully extends CreateUnitState {} + +class ErrorState extends CreateUnitState { + final String message; + + const ErrorState({required this.message}); + + @override + List get props => [message]; +} diff --git a/lib/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart new file mode 100644 index 0000000..46a0c8a --- /dev/null +++ b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart @@ -0,0 +1,114 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/services/api/home_creation_api.dart'; +import 'package:syncrow_app/services/api/home_management_api.dart'; +import 'package:syncrow_app/services/api/spaces_api.dart'; + +class ManageUnitBloc extends Bloc { + List allDevices = []; + String unitName = ''; + + ManageUnitBloc() : super(InitialState()) { + on(_fetchRoomsAndDevices); + on(_fetchDevicesByRoomId); + on(_assignDevice); + on(_addNewRoom); + } + + void _fetchRoomsAndDevices(FetchRoomsEvent event, Emitter emit) async { + try { + emit(LoadingState()); + final roomsList = await SpacesAPI.getRoomsBySpaceId(event.unitId); + emit(FetchRoomsState(devicesList: allDevices, roomsList: roomsList)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + void _fetchDevicesByRoomId(FetchDevicesByRoomIdEvent event, Emitter emit) async { + try { + Map roomDevicesId = {}; + emit(LoadingState()); + final devicesList = await DevicesAPI.getDevicesByRoomId(event.roomId); + allDevices = await HomeManagementAPI.fetchDevicesByUserId(); + + List allDevicesIds = []; + + allDevices.forEach((element) { + allDevicesIds.add(element.uuid!); + }); + + devicesList.forEach((e) { + if (allDevicesIds.contains(e.uuid!)) { + roomDevicesId[e.uuid!] = true; + } else { + roomDevicesId[e.uuid!] = false; + } + }); + emit(FetchDeviceByRoomIdState( + roomDevices: devicesList, + allDevices: allDevices, + roomDevicesId: roomDevicesId, + roomId: event.roomId)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + void _assignDevice(AssignRoomEvent event, Emitter emit) async { + try { + Map roomDevicesId = {}; + emit(LoadingState()); + Map body = {"deviceUuid": event.deviceId, "roomUuid": event.roomId}; + await HomeManagementAPI.assignDeviceToRoom(body); + final devicesList = await DevicesAPI.getDevicesByRoomId(event.roomId); + + List allDevicesIds = []; + + allDevices.forEach((element) { + allDevicesIds.add(element.uuid!); + }); + + devicesList.forEach((e) { + if (allDevicesIds.contains(e.uuid!)) { + roomDevicesId[e.uuid!] = true; + } else { + roomDevicesId[e.uuid!] = false; + } + }); + emit(FetchDeviceByRoomIdState( + roomDevices: devicesList, + allDevices: allDevices, + roomDevicesId: roomDevicesId, + roomId: event.roomId)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + _addNewRoom(AddNewRoom event, Emitter emit) async { + Map body = {'roomName': event.roomName, 'unitUuid': event.unitId}; + try { + emit(LoadingState()); + final response = await HomeCreation.createRoom(body); + if (response['data']['uuid'] != '') { + final roomsList = await SpacesAPI.getRoomsBySpaceId(event.unitId); + allDevices = await HomeManagementAPI.fetchDevicesByUserId(); + emit(FetchRoomsState(devicesList: allDevices, roomsList: roomsList)); + await HomeCubit.getInstance().fetchUnitsByUserId(); + } else { + emit(const ErrorState(message: 'Something went wrong')); + } + } catch (_) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } +} diff --git a/lib/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart new file mode 100644 index 0000000..03c0722 --- /dev/null +++ b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart @@ -0,0 +1,50 @@ +import 'package:equatable/equatable.dart'; + +abstract class ManageUnitEvent extends Equatable { + const ManageUnitEvent(); + + @override + List get props => []; +} + +class InitialEvent extends ManageUnitEvent {} + +class LoadingEvent extends ManageUnitEvent {} + +class FetchRoomsEvent extends ManageUnitEvent { + final String unitId; + + const FetchRoomsEvent({required this.unitId}); + + @override + List get props => [unitId]; +} + +class FetchDevicesByRoomIdEvent extends ManageUnitEvent { + final String roomId; + + const FetchDevicesByRoomIdEvent({required this.roomId}); + + @override + List get props => [roomId]; +} + +class AddNewRoom extends ManageUnitEvent { + final String roomName; + final String unitId; + + const AddNewRoom({required this.roomName, required this.unitId}); + + @override + List get props => [roomName, unitId]; +} + +class AssignRoomEvent extends ManageUnitEvent { + final String roomId; + final String deviceId; + + const AssignRoomEvent({required this.roomId, required this.deviceId}); + + @override + List get props => [roomId]; +} diff --git a/lib/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart new file mode 100644 index 0000000..56236a5 --- /dev/null +++ b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart @@ -0,0 +1,51 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/room_model.dart'; + +abstract class ManageUnitState extends Equatable { + const ManageUnitState(); + + @override + List get props => []; +} + +class InitialState extends ManageUnitState {} + +class LoadingState extends ManageUnitState {} + +class FetchRoomsState extends ManageUnitState { + final List roomsList; + final List devicesList; + + const FetchRoomsState({required this.devicesList, required this.roomsList}); + + @override + List get props => [devicesList]; +} + +class FetchDeviceByRoomIdState extends ManageUnitState { + final List roomDevices; + final List allDevices; + final Map roomDevicesId; + final String roomId; + + const FetchDeviceByRoomIdState( + {required this.roomDevices, + required this.allDevices, + required this.roomDevicesId, + required this.roomId}); + + @override + List get props => [roomDevices, allDevices, roomDevicesId, roomId]; +} + +class AssignRoomState extends ManageUnitState {} + +class ErrorState extends ManageUnitState { + final String message; + + const ErrorState({required this.message}); + + @override + List get props => [message]; +} diff --git a/lib/features/menu/bloc/menu_cubit.dart b/lib/features/menu/bloc/menu_cubit.dart new file mode 100644 index 0000000..068d947 --- /dev/null +++ b/lib/features/menu/bloc/menu_cubit.dart @@ -0,0 +1,12 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'menu_state.dart'; + +class MenuCubit extends Cubit { + MenuCubit() : super(MenuInitial()); + + static MenuCubit of(context) => BlocProvider.of(context); + + String name = ''; + +} diff --git a/lib/features/menu/bloc/menu_state.dart b/lib/features/menu/bloc/menu_state.dart new file mode 100644 index 0000000..b59b70d --- /dev/null +++ b/lib/features/menu/bloc/menu_state.dart @@ -0,0 +1,5 @@ +part of 'menu_cubit.dart'; + +abstract class MenuState {} + +class MenuInitial extends MenuState {} diff --git a/lib/features/menu/bloc/profile_bloc/profile_bloc.dart b/lib/features/menu/bloc/profile_bloc/profile_bloc.dart new file mode 100644 index 0000000..c01a5c5 --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/profile_bloc.dart @@ -0,0 +1,297 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart'; +import 'package:syncrow_app/features/menu/model/region_model.dart'; +import 'package:syncrow_app/features/menu/model/time_zone_model.dart'; +import 'package:syncrow_app/services/api/profile_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class ProfileBloc extends Bloc { + bool isSaving = false; + bool editName = false; + final FocusNode focusNode = FocusNode(); + File? image; + final ImagePicker _picker = ImagePicker(); + String timeZoneSelected = ''; + String regionSelected = ''; + final TextEditingController searchController = TextEditingController(); + final TextEditingController nameController = + TextEditingController(text: '${HomeCubit.user!.firstName} ${HomeCubit.user!.lastName}'); + + List allRegions = []; + List allTimeZone = []; + + ProfileBloc() : super(InitialState()) { + on(_fetchUserInfo); + on(_fetchTimeZone); + on(_fetchRegion); + on(saveName); + on(_selectImage); + on(_changeName); + on(selectTimeZone); + on(searchRegion); + on(searchTimeZone); + on(selectRegion); + } + + Future saveName(SaveNameEvent event, Emitter emit) async { + if (_validateInputs()) return; + try { + add(const ChangeNameEvent(value: false)); + isSaving = true; + emit(LoadingInitialState()); + final fullName = nameController.text; + final nameParts = fullName.split(' '); + final firstName = nameParts[0]; + final lastName = nameParts.length > 1 ? nameParts[1] : ''; + await ProfileApi.saveName(firstName: firstName, lastName: lastName); + add(InitialProfileEvent()); + await HomeCubit.getInstance().fetchUserInfo(); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } finally { + isSaving = false; + } + } + + void _changeName(ChangeNameEvent event, Emitter emit) { + emit(LoadingInitialState()); + editName = event.value!; + if (editName) { + Future.delayed(const Duration(milliseconds: 500), () { + focusNode.requestFocus(); + }); + } else { + focusNode.unfocus(); + } + emit(NameEditingState(editName: editName)); + } + + void _fetchUserInfo(InitialProfileEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + HomeCubit.user = await ProfileApi().fetchUserInfo(HomeCubit.user!.uuid); + emit(SaveState()); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + Future _fetchTimeZone(TimeZoneInitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + allTimeZone = await ProfileApi.fetchTimeZone(); + emit(TimeZoneLoadedState(timezone: allTimeZone)); + return allTimeZone; + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + Future selectTimeZone(SelectTimeZoneEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + timeZoneSelected = event.val; + await ProfileApi.saveTimeZone(regionUuid: event.val); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + Future selectRegion(SelectRegionEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + await ProfileApi.saveRegion(regionUuid: event.val); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + Future searchRegion(SearchRegionEvent event, Emitter emit) async { + emit(LoadingInitialState()); + final query = event.query.toLowerCase(); + if (allRegions.isEmpty) { + allRegions = await ProfileApi.fetchRegion(); + } + if (query.isNotEmpty) { + final filteredRegions = allRegions.where((region) { + return region.name.toLowerCase().contains(query); + }).toList(); + emit(RegionsLoadedState(regions: filteredRegions)); + } else { + emit(RegionsLoadedState(regions: allRegions)); + } + } + + Future searchTimeZone(SearchTimeZoneEvent event, Emitter emit) async { + emit(LoadingInitialState()); + final query = event.query.toLowerCase(); + if (allTimeZone.isEmpty) { + allTimeZone = await ProfileApi.fetchTimeZone(); + } + if (query.isNotEmpty) { + final filteredRegions = allTimeZone.where((region) { + return region.name.toLowerCase().contains(query); + }).toList(); + emit(TimeZoneLoadedState(timezone: filteredRegions)); + } else { + emit(TimeZoneLoadedState(timezone: allTimeZone)); + } + } + + void _fetchRegion(RegionInitialEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + allRegions = await ProfileApi.fetchRegion(); + emit(RegionsLoadedState(regions: allRegions)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + Future _selectImage(SelectImageEvent event, Emitter emit) async { + try { + if (await _requestPermission()) { + emit(ChangeImageState()); + final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + image = File(pickedFile.path); + final bytes = image!.readAsBytesSync().lengthInBytes; + final kb = bytes / 1024; + final mb = kb / 1024; + if (mb > 1) { + image = null; + CustomSnackBar.displaySnackBar('Image size must be 1 MB or less'); + } else { + emit(LoadingInitialState()); + await _saveImage(); + emit(ImageSelectedState()); + } + } + emit(ImageSelectedState()); + } else { + _showPermissionDeniedDialog(event.context); + } + } catch (_) { + emit(const FailedState(errorMessage: 'Something went wrong')); + } + } + + Future _saveImage() async { + List imageBytes = image!.readAsBytesSync(); + String base64Image = base64Encode(imageBytes); + await ProfileApi.saveImage(base64Image); + } + + void _showPermissionDeniedDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Permission Denied'), + content: const Text( + 'Photo access is required to select an image. Please allow photo access in the app settings.'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + openAppSettings(); + }, + child: const Text('Settings'), + ), + ], + ); + }, + ); + } + + bool _validateInputs() { + final nameError = fullNameValidator(nameController.text); + if (nameError != null) { + CustomSnackBar.displaySnackBar(nameError); + return true; + } + return false; + } + + String? fullNameValidator(String? value) { + if (value == null) return 'Full name is required'; + final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim(); + if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) { + return 'Full name must be between 2 and 30 characters long'; + } + // Test if it contains anything but alphanumeric spaces and single quote + if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) { + return 'Only alphanumeric characters, space, dash and single quote are allowed'; + } + final parts = withoutExtraSpaces.split(' '); + if (parts.length < 2) return 'Full name must contain first and last names'; + if (parts.length > 2) return 'Full name can at most contain 2 parts'; + if (parts.any((part) => part.length < 2 || part.length > 30)) { + return 'Full name parts must be between 2 and 30 characters long'; + } + return null; + } + + Future _requestPermission() async { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + if (Platform.isAndroid) { + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + PermissionStatus status = await Permission.photos.status; + if (androidInfo.version.sdkInt < 33) { + if (status.isDenied) { + PermissionStatus status = await Permission.storage.request(); + if (status.isGranted) { + return true; + } else { + return false; + } + } + } else { + if (status.isGranted) { + return true; + } else if (status.isDenied) { + PermissionStatus status = await Permission.photos.request(); + if (status.isGranted) { + return true; + } else { + return false; + } + } + } + return false; + } else { + SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); + bool firstClick = sharedPreferences.getBool('firstPermission') ?? true; + await sharedPreferences.setBool('firstPermission', false); + if (firstClick == false) { + var status = await Permission.photos.status; + return status.isGranted; + } else { + return true; + } + } + } +} diff --git a/lib/features/menu/bloc/profile_bloc/profile_event.dart b/lib/features/menu/bloc/profile_bloc/profile_event.dart new file mode 100644 index 0000000..15d9336 --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/profile_event.dart @@ -0,0 +1,76 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; + +abstract class ProfileEvent extends Equatable { + const ProfileEvent(); + + @override + List get props => []; +} + +class InitialProfileEvent extends ProfileEvent {} + +class TimeZoneInitialEvent extends ProfileEvent {} + +class ChangeNameEvent extends ProfileEvent { + final bool? value; + const ChangeNameEvent({ this.value}); +} + +class RegionInitialEvent extends ProfileEvent {} + + +class SaveNameEvent extends ProfileEvent { + final BuildContext context; + const SaveNameEvent({required this.context}); + @override + List get props => [context]; +} + + +class SelectImageEvent extends ProfileEvent { + final BuildContext context; + final bool isSelected; + const SelectImageEvent({required this.context,required this.isSelected}); + @override + List get props => [context,isSelected]; +} + + +class ToggleRepeatEvent extends ProfileEvent {} + +class SelectTimeZoneEvent extends ProfileEvent { + final String val; + final BuildContext context; + const SelectTimeZoneEvent({required this.val,required this.context}); + @override + List get props => [val]; +} + +class SelectRegionEvent extends ProfileEvent { + final String val; + final BuildContext context; + const SelectRegionEvent({required this.val,required this.context}); + @override + List get props => [val,context]; +} + + +class SearchRegionEvent extends ProfileEvent { + final String query; + + const SearchRegionEvent({required this.query}); + @override + List get props => [query]; +} + +class SearchTimeZoneEvent extends ProfileEvent { + final String query; + + const SearchTimeZoneEvent({required this.query}); + @override + List get props => [query]; +} + + + diff --git a/lib/features/menu/bloc/profile_bloc/profile_state.dart b/lib/features/menu/bloc/profile_bloc/profile_state.dart new file mode 100644 index 0000000..f98b1bb --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/profile_state.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/menu/model/region_model.dart'; +import 'package:syncrow_app/features/menu/model/time_zone_model.dart'; + +class ProfileState extends Equatable { + const ProfileState(); + + @override + List get props => []; +} + +class InitialState extends ProfileState {} + +class LoadingInitialState extends ProfileState {} + +class UpdateState extends ProfileState { + final List timeZoneList; + + UpdateState({required this.timeZoneList}); +} + +class NameEditingState extends ProfileState { + final bool editName; + + NameEditingState({required this.editName}); +} + + + +class FailedState extends ProfileState { + final String errorMessage; + + const FailedState({required this.errorMessage}); + + @override + List get props => [errorMessage]; +} + + +class ImageSelectedState extends ProfileState {} + +class ChangeImageState extends ProfileState {} + +class SaveState extends ProfileState {} + +class LoadingSaveState extends ProfileState {} +class RegionsLoadedState extends ProfileState { + final List regions; + + const RegionsLoadedState({required this.regions}); +} + +class TimeZoneLoadedState extends ProfileState { + final List timezone; + + const TimeZoneLoadedState({required this.timezone}); +} \ No newline at end of file diff --git a/lib/features/menu/model/region_model.dart b/lib/features/menu/model/region_model.dart new file mode 100644 index 0000000..fd6306a --- /dev/null +++ b/lib/features/menu/model/region_model.dart @@ -0,0 +1,25 @@ + + +class RegionModel { + final String name; + final String id; + + RegionModel({ + required this.name, + required this.id, + }); + + factory RegionModel.fromJson(Map json) { + return RegionModel( + name: json['regionName'], + id: json['uuid'].toString(), // Ensure id is a String + ); + } + + Map toJson() { + return { + 'regionName': name, + 'uuid': id, + }; + } +} diff --git a/lib/features/menu/model/time_zone_model.dart b/lib/features/menu/model/time_zone_model.dart new file mode 100644 index 0000000..b37049b --- /dev/null +++ b/lib/features/menu/model/time_zone_model.dart @@ -0,0 +1,27 @@ +class TimeZone { + final String name; + final String offset; + final String id; + + TimeZone({ + required this.name, + required this.offset, + required this.id, + }); + + factory TimeZone.fromJson(Map json) { + return TimeZone( + name: json['cityName'], + offset: json['timeZoneOffset'], + id: json['uuid'].toString(), // Ensure id is a String + ); + } + + Map toJson() { + return { + 'name': name, + 'offset': offset, + 'id': id, + }; + } +} diff --git a/lib/features/menu/view/menu_view.dart b/lib/features/menu/view/menu_view.dart new file mode 100644 index 0000000..298d1f7 --- /dev/null +++ b/lib/features/menu/view/menu_view.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart'; +import 'package:syncrow_app/features/menu/view/widgets/menu_list.dart'; +import 'package:syncrow_app/features/menu/view/widgets/profile/profile_tab.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class MenuView extends StatelessWidget { + const MenuView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => MenuCubit(), + child: BlocBuilder( + builder: (context, state) { + return BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + const ProfileTab(), + for (var section in menuSections) + MenuList( + section: section, + ), + const SizedBox( + height: 15, + ), + const BodyMedium( + text: String.fromEnvironment('FLAVOR', defaultValue: 'production')), + const SizedBox( + height: 15, + ), + InkWell( + onTap: () { + AuthCubit.get(context).logout(); + }, + child: Row( + children: [ + Expanded( + child: DefaultContainer( + child: Center( + child: BodyLarge( + text: 'Logout', + style: context.bodyLarge.copyWith( + color: Colors.red, + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ); + }, + ); + }, + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/create_home/create_home_view.dart b/lib/features/menu/view/widgets/create_home/create_home_view.dart new file mode 100644 index 0000000..e11bede --- /dev/null +++ b/lib/features/menu/view/widgets/create_home/create_home_view.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class CreateUnitView extends StatelessWidget { + const CreateUnitView({super.key}); + + @override + Widget build(BuildContext context) { + String unitName = ''; + TextEditingController textEditingController = TextEditingController(); + + return BlocProvider( + create: (context) => CreateUnitBloc(), + child: BlocConsumer( + listener: (context, state) { + if (state is RoomsSavedSuccessfully) { + CustomSnackBar.displaySnackBar('Unit created successfully'); + Navigator.of(context).pop(); + } + + if (state is ErrorState) { + CustomSnackBar.displaySnackBar(state.message); + } + }, + builder: (context, state) { + textEditingController.text = ''; + return DefaultScaffold( + actions: [ + TextButton( + onPressed: () { + BlocProvider.of(context).add(SaveRooms( + communityName: 'Community Test', + buildingName: 'Building Test', + floorName: 'Floor Test', + unitName: unitName, + )); + }, + child: const BodyLarge( + text: 'Save', + fontWeight: FontWeight.bold, + ), + ), + ], + title: 'Create a Unit', + child: Container( + padding: const EdgeInsets.symmetric(vertical: 40), + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: state is LoadingState + ? const Center( + child: CircularProgressIndicator(), + ) + : SingleChildScrollView( + child: Column( + children: [ + // Home Info + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Unit Name'), + Flexible( + child: TextField( + textAlign: TextAlign.end, + onChanged: (value) { + unitName = value; + }, + decoration: InputDecoration( + hintText: 'Enter Name', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + const Divider( + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Location '), + Flexible( + child: TextField( + textAlign: TextAlign.end, + onChanged: (value) {}, + decoration: InputDecoration( + hintText: 'Set', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + ], + ), + ), + + // Rooms Info + const SizedBox(height: 10), + const Row( + children: [ + SizedBox( + width: 25, + ), + BodySmall( + text: 'Spaces', + fontWeight: FontWeight.bold, + ), + ], + ), + const SizedBox( + height: 4, + ), + Container( + // margin: EdgeInsets.zero, + // margin: const EdgeInsets.symmetric( + // horizontal: 25, + // vertical: 5, + // ), + decoration: const ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (state is CreateRoomState) + if (state.roomList.isNotEmpty) + Column( + children: [ + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (context, index) => const Divider( + color: ColorsManager.greyColor, + ), + itemCount: state.roomList.length, + itemBuilder: (context, index) { + return Dismissible( + key: Key(state.roomList[index]), + background: Container( + padding: const EdgeInsets.only(right: 10), + alignment: AlignmentDirectional.centerEnd, + decoration: ShapeDecoration( + color: const Color(0xFFFF0000), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(index == 0 ? 20 : 0), + topRight: + Radius.circular(index == 0 ? 20 : 0), + ), + ), + ), + child: SvgPicture.asset( + Assets.assetsDeleteIcon, + width: 20, + height: 22, + ), + ), + direction: DismissDirection.endToStart, + onDismissed: (direction) { + String removedUnit = state.roomList[index]; + + BlocProvider.of(context) + .add(RemoveRoomEvent(roomName: removedUnit)); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('$removedUnit removed')), + ); + }, + child: Container( + height: 50, + padding: const EdgeInsets.symmetric( + vertical: 16, horizontal: 25), + child: Text( + state.roomList[index], + style: const TextStyle( + color: Color(0xFF5D5D5D), + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + }, + ), + const Divider( + color: ColorsManager.greyColor, + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 5), + child: Row( + children: [ + TextButton( + onPressed: () { + if (textEditingController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Please add the room name')), + ); + return; + } + BlocProvider.of(context).add( + CreateRoomEvent( + roomName: textEditingController.text)); + textEditingController.clear(); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + BodyMedium( + text: 'Add custom Space', + fontColor: ColorsManager.primaryColor, + ), + ], + ), + ), + Flexible( + child: TextField( + textAlign: TextAlign.end, + controller: textEditingController, + decoration: InputDecoration( + hintText: 'Set', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/join_home/join_home_view.dart b/lib/features/menu/view/widgets/join_home/join_home_view.dart new file mode 100644 index 0000000..69952c9 --- /dev/null +++ b/lib/features/menu/view/widgets/join_home/join_home_view.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class JoinHomeView extends StatelessWidget { + const JoinHomeView({super.key}); + + @override + Widget build(BuildContext context) { + TextEditingController textEditingController = TextEditingController(); + return DefaultScaffold( + title: "Join a Home", + child: Column( + children: [ + SvgPicture.asset( + Assets.assetsIconsMenuIconsHomeManagementIconsJoinAHome, + width: 70, + height: 70, + ), + const Padding( + padding: EdgeInsets.symmetric( + vertical: 30, + ), + child: BodyMedium( + textAlign: TextAlign.center, + text: + 'Please contact with the\nadministration to get an invitation\n(Menu → Manage your Home → Add Member)'), + ), + DefaultContainer( + padding: const EdgeInsets.symmetric( + vertical: 5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: TextField( + controller: textEditingController, + decoration: InputDecoration( + hintText: 'Invitatoin code', + hintStyle: context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + IconButton( + onPressed: () async { + if (textEditingController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter the invitation code'); + return; + } + if (await HomeCubit.getInstance().joinAUnit(textEditingController.text)) { + CustomSnackBar.displaySnackBar('Done successfully'); + Navigator.of(context).pop(); + } else { + CustomSnackBar.displaySnackBar('Wrong code!'); + } + }, + icon: const Icon( + Icons.arrow_right_alt, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/manage_home/assign_devices.dart b/lib/features/menu/view/widgets/manage_home/assign_devices.dart new file mode 100644 index 0000000..bc097fb --- /dev/null +++ b/lib/features/menu/view/widgets/manage_home/assign_devices.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class AssignDeviceView extends StatelessWidget { + final String unitId; + final String roomId; + const AssignDeviceView({super.key, required this.unitId, required this.roomId}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ManageUnitBloc()..add(FetchDevicesByRoomIdEvent(roomId: roomId)), + child: BlocConsumer(listener: (context, state) { + if (state is FetchDeviceByRoomIdState) { + if (state.allDevices.isEmpty) { + CustomSnackBar.displaySnackBar('You do not have the permission to assign devices'); + Navigator.of(context).pop(); + } + } + }, builder: (context, state) { + return DefaultScaffold( + title: 'Space Setting', + child: state is LoadingState + ? const Center(child: RefreshProgressIndicator()) + : state is FetchDeviceByRoomIdState + ? Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: 1.0, + ), + itemCount: state.allDevices.length, + itemBuilder: (context, index) { + return Container( + width: MediaQuery.sizeOf(context).width, + padding: const EdgeInsets.all(16), + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + state.allDevices[index].icon!, + fit: BoxFit.contain, + ), + GestureDetector( + onTap: () { + if (state.roomDevicesId[ + state.allDevices[index].uuid!] ?? + false == false) { + BlocProvider.of(context).add( + AssignRoomEvent( + deviceId: + state.allDevices[index].uuid ?? '', + roomId: roomId)); + } + }, + child: SvgPicture.asset( + state.roomDevicesId[ + state.allDevices[index].uuid!] ?? + false + ? Assets.blueCheckboxIcon + : Assets.emptyCheckboxIcon, + width: 22, + height: 22, + )) + ], + ), + Text(state.allDevices[index].name ?? ''), + ], + ), + ); + }), + ) + : Container()); + })); + } +} diff --git a/lib/features/menu/view/widgets/manage_home/home_settings.dart b/lib/features/menu/view/widgets/manage_home/home_settings.dart new file mode 100644 index 0000000..751c04b --- /dev/null +++ b/lib/features/menu/view/widgets/manage_home/home_settings.dart @@ -0,0 +1,213 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/menu/view/widgets/manage_home/room_screen.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class HomeSettingsView extends StatelessWidget { + const HomeSettingsView({super.key, this.space}); + + final SpaceModel? space; + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Home Settings', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + //Home Info + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Name '), + Flexible( + child: TextField( + textAlign: TextAlign.end, + readOnly: true, + decoration: InputDecoration( + hintText: space?.name ?? 'Enter Name', + hintStyle: context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + //Divider + Container( + margin: const EdgeInsets.only(bottom: 10), + height: 1, + color: ColorsManager.greyColor, + ), + GestureDetector( + onTap: () { + Navigator.of(context).push(CustomPageRoute( + builder: (context) => RoomsView( + unitId: space?.id ?? '', + ))); + }, + child: Container( + width: MediaQuery.sizeOf(context).width, + padding: const EdgeInsets.all(4), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium(text: 'Rooms'), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + ), + ), + //Divider + Container( + height: 1, + margin: const EdgeInsets.only(top: 10), + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Location'), + Flexible( + child: TextField( + textAlign: TextAlign.end, + decoration: InputDecoration( + hintText: 'Set', + hintStyle: context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + //Divider + Container( + margin: const EdgeInsets.only(bottom: 10), + height: 1, + color: ColorsManager.greyColor, + ), + Container( + width: MediaQuery.sizeOf(context).width, + padding: const EdgeInsets.only(bottom: 10), + child: GestureDetector( + onTap: () async { + await HomeCubit.getInstance().generateInvitation(space?.id ?? ''); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium(text: 'Invite a member'), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + ), + ), + ], + ), + ), + //Members Info + const SizedBox( + height: 10, + ), + //TODO connect the members to this GridView + const BodySmall( + text: "Members", + fontWeight: FontWeight.bold, + ), + GridView.builder( + shrinkWrap: true, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + ), + itemCount: 2, + itemBuilder: (context, index) => Stack( + alignment: Alignment.topCenter, + children: [ + DefaultContainer( + margin: const EdgeInsets.only(top: 20), + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 40), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 50, + ), + BodyMedium( + text: 'Member ${index + 1}', + fontWeight: FontWeight.bold, + ), + const SizedBox(height: 3), + const BodySmall( + text: 'Syncrow Account', + textAlign: TextAlign.center, + ), + ], + ), + ), + const SizedBox.square( + dimension: 80, + child: CircleAvatar( + backgroundColor: Colors.white, + child: SizedBox.square( + dimension: 77, + child: CircleAvatar( + backgroundColor: ColorsManager.greyColor, + child: Icon(Icons.person), + ), + ), + ), + ) + ], + ), + ), + const Spacer(), + InkWell( + onTap: () {}, + child: Row( + children: [ + Expanded( + child: DefaultContainer( + child: Center( + child: BodyLarge( + text: 'Leave Home', + style: context.bodyLarge.copyWith( + color: Colors.red, + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/manage_home/manage_home_view.dart b/lib/features/menu/view/widgets/manage_home/manage_home_view.dart new file mode 100644 index 0000000..268a87a --- /dev/null +++ b/lib/features/menu/view/widgets/manage_home/manage_home_view.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/menu/view/widgets/manage_home/home_settings.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; +import 'package:syncrow_app/utils/helpers/misc_string_helpers.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class ManageHomeView extends StatelessWidget { + const ManageHomeView({super.key}); + + @override + Widget build(BuildContext context) { + var spaces = HomeCubit.getInstance().spaces; + return DefaultScaffold( + title: 'Manage Your Home', + child: spaces == null + ? const Center( + child: BodyMedium(text: 'No spaces found'), + ) + : Column( + children: [ + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate( + spaces.length, + (index) { + if (index == spaces.length - 1) { + return InkWell( + onTap: () { + Navigator.of(context).push(CustomPageRoute( + builder: (context) => HomeSettingsView( + space: spaces[index], + ))); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: StringHelpers.toTitleCase(spaces[index].name ?? "")), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + ); + } + return InkWell( + onTap: () { + //TODO refactor the routing to use named routes + // Navigator.of(context).pushNamed( + // '/home_settings', + // arguments: spaces[index], + // ); + + Navigator.of(context).push(CustomPageRoute( + builder: (context) => HomeSettingsView( + space: spaces[index], + ))); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: HomeCubit.getInstance().spaces![index].name ?? ""), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 15), + height: 1, + color: ColorsManager.greyColor, + ), + ], + ), + ); + }, + ), + ), + ), + ], + )); + } +} diff --git a/lib/features/menu/view/widgets/manage_home/room_screen.dart b/lib/features/menu/view/widgets/manage_home/room_screen.dart new file mode 100644 index 0000000..34f2326 --- /dev/null +++ b/lib/features/menu/view/widgets/manage_home/room_screen.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart'; +import 'package:syncrow_app/features/menu/view/widgets/manage_home/assign_devices.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class RoomsView extends StatelessWidget { + final String unitId; + const RoomsView({super.key, required this.unitId}); + + @override + Widget build(BuildContext context) { + TextEditingController textEditingController = TextEditingController(); + return BlocProvider( + create: (context) => ManageUnitBloc()..add(FetchRoomsEvent(unitId: unitId)), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + return DefaultScaffold( + title: 'Space Management', + child: state is LoadingState + ? const Center(child: RefreshProgressIndicator()) + : SizedBox( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: ListView( + children: [ + const SizedBox( + height: 32, + ), + if (state is FetchRoomsState) + Container( + decoration: const ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + child: Column( + children: [ + if (state.roomsList.isNotEmpty) + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (context, index) => const Divider( + color: ColorsManager.greyColor, + ), + itemCount: state.roomsList.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + // BlocProvider.of(context).add( + // FetchDevicesByRoomIdEvent( + // roomId: state.roomsList[index].id ?? '')); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => AssignDeviceView( + roomId: state.roomsList[index].id ?? '', + unitId: unitId, + )), + ); + }, + child: Container( + height: 50, + padding: const EdgeInsets.symmetric( + vertical: 16, horizontal: 25), + child: Text( + state.roomsList[index].name ?? '', + style: const TextStyle( + color: Color(0xFF5D5D5D), + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + }, + ), + const Divider( + color: ColorsManager.greyColor, + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 25, vertical: 5), + child: Row( + children: [ + TextButton( + onPressed: () { + if (textEditingController.text.isEmpty) { + CustomSnackBar.displaySnackBar( + 'Please add the room name'); + return; + } + BlocProvider.of(context).add( + AddNewRoom( + roomName: textEditingController.text, + unitId: unitId)); + textEditingController.clear(); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + BodyMedium( + text: 'Add Space', + fontColor: ColorsManager.primaryColor, + ), + ], + ), + ), + Flexible( + child: TextField( + textAlign: TextAlign.end, + controller: textEditingController, + decoration: InputDecoration( + hintText: 'Set', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + ), + ], + ), + ) + ], + ), + ), + ); + })); + } +} diff --git a/lib/features/menu/view/widgets/menu_list.dart b/lib/features/menu/view/widgets/menu_list.dart new file mode 100644 index 0000000..511f476 --- /dev/null +++ b/lib/features/menu/view/widgets/menu_list.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/menu/view/widgets/menu_list_divider.dart'; +import 'package:syncrow_app/features/menu/view/widgets/menu_list_item.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class MenuList extends StatelessWidget { + const MenuList({ + super.key, + required this.section, + }); + + final Map section; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 5), + BodySmall( + text: section['title'] as String, + ), + const SizedBox(height: 5), + DefaultContainer( + child: ListView.separated( + shrinkWrap: true, + padding: const EdgeInsets.all(0), + physics: const NeverScrollableScrollPhysics(), + itemCount: section['buttons'].length, + itemBuilder: (context, index) { + return MenuListItem( + title: section['buttons'][index]['title'] as String, + icon: section['buttons'][index]['Icon'] as String, + page: section['buttons'][index]['page'] as Widget?, + color: section['color'], + ); + }, + separatorBuilder: (context, index) => const MenuListDivider()), + ), + const SizedBox(height: 5), + ], + ); + } +} diff --git a/lib/features/menu/view/widgets/menu_list_divider.dart b/lib/features/menu/view/widgets/menu_list_divider.dart new file mode 100644 index 0000000..6de4e6a --- /dev/null +++ b/lib/features/menu/view/widgets/menu_list_divider.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class MenuListDivider extends StatelessWidget { + const MenuListDivider({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Divider( + color: Colors.grey.withOpacity(0.5), + thickness: 1, + indent: 43, + endIndent: 10, + ); + } +} diff --git a/lib/features/menu/view/widgets/menu_list_item.dart b/lib/features/menu/view/widgets/menu_list_item.dart new file mode 100644 index 0000000..264e2b8 --- /dev/null +++ b/lib/features/menu/view/widgets/menu_list_item.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; + +class MenuListItem extends StatelessWidget { + const MenuListItem({ + super.key, + required this.title, + required this.icon, + required this.page, + required this.color, + }); + + final String title; + final String icon; + final Widget? page; + final Color color; + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (page == null) { + return; + } + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => page!)); + }, + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(right: 10), + height: 33, + width: 33, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(15), + ), + child: Center( + child: SizedBox.square( + dimension: 15, + child: SvgPicture.asset( + icon, + width: 15, + height: 15, + fit: BoxFit.contain, + ), + ), + )), + BodyMedium( + text: title, + ) + ], + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/privacy/privacy_view.dart b/lib/features/menu/view/widgets/privacy/privacy_view.dart new file mode 100644 index 0000000..e2e4c37 --- /dev/null +++ b/lib/features/menu/view/widgets/privacy/privacy_view.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class PrivacyView extends StatelessWidget { + const PrivacyView({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Privacy', + child: Column( + children: [ + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + InkWell( + onTap: () {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: 'Notification Settings', + ), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 15), + height: 1, + color: ColorsManager.greyColor, + ), + ], + ), + ), + InkWell( + onTap: () {}, + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: 'Permissions Settings', + ), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/profile/profile_tab.dart b/lib/features/menu/view/widgets/profile/profile_tab.dart new file mode 100644 index 0000000..2350040 --- /dev/null +++ b/lib/features/menu/view/widgets/profile/profile_tab.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class ProfileTab extends StatelessWidget { + const ProfileTab({ + super.key, + }); + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return _buildProfileContent(context); + }, + ); + } + + Widget _buildProfileContent(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + child: InkWell( + onTap: () { + Navigator.of(context) + .push( + MaterialPageRoute( + builder: (context) => const ProfileView(), + ), + ) + .then((result) { + context.read().fetchUserInfo(); + }); + }, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 20), + DefaultContainer( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + BodyMedium( + text: '${HomeCubit.user?.firstName ?? ''} ', + fontWeight: FontWeight.bold, + ), + BodyMedium( + text: HomeCubit.user?.lastName ?? '', + fontWeight: FontWeight.bold, + ), + ], + ), + const SizedBox( + height: 5, + ), + const BodySmall(text: "Syncrow Account"), + ], + ), + ), + ), + ], + ), + Positioned( + right: 20, + top: 0, + child: CircleAvatar( + radius: 38, + backgroundColor: Colors.white, + child: CircleAvatar( + radius: 37, + backgroundColor: Colors.grey, + child: ClipOval( + child: HomeCubit.user?.profilePicture != null + ? Image.memory( + HomeCubit.user!.profilePicture!, + fit: BoxFit.cover, + width: 110, + height: 110, + ) + : const Icon(Icons.person, size: 70), // Fallback if no image + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/profile/profile_view.dart b/lib/features/menu/view/widgets/profile/profile_view.dart new file mode 100644 index 0000000..7fab399 --- /dev/null +++ b/lib/features/menu/view/widgets/profile/profile_view.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart'; +import 'package:syncrow_app/features/menu/view/widgets/profile/region_page.dart'; +import 'package:syncrow_app/features/menu/view/widgets/profile/time_zone_screen_page.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class ProfileView extends StatelessWidget { + const ProfileView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => ProfileBloc()..add(InitialProfileEvent()), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final profileBloc = BlocProvider.of(context); + return DefaultScaffold( + title: 'Syncrow Account', + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : SizedBox( + height: MediaQuery.sizeOf(context).height, + child: ListView( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.05, + ), + InkWell( + onTap: () { + profileBloc.add(SelectImageEvent(context: context, isSelected: false)); + }, + child: SizedBox.square( + dimension: 125, + child: CircleAvatar( + backgroundColor: Colors.white, + child: SizedBox.square( + dimension: 120, + child: CircleAvatar( + backgroundColor: Colors.white, + backgroundImage: profileBloc.image == null + ? null + : FileImage(profileBloc.image!), + child: profileBloc.image != null + ? null + : HomeCubit.user!.profilePicture != null + ? ClipOval( + child: Image.memory( + HomeCubit.user!.profilePicture!, + fit: BoxFit.cover, + width: 120, + height: 120, + ), + ) + : null, + ), + ), + ), + ), + ), + const SizedBox(height: 20), + SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IntrinsicWidth( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200), + child: TextFormField( + maxLength: 30, + style: const TextStyle( + color: Colors.black, + ), + textAlign: TextAlign.center, + focusNode: profileBloc.focusNode, + controller: profileBloc.nameController, + enabled: profileBloc.editName, + onEditingComplete: () { + profileBloc.add(SaveNameEvent(context: context)); + }, + decoration: const InputDecoration( + hintText: "Your Name", + border: InputBorder.none, + fillColor: Colors.white10, + counterText: '', + ), + ), + ), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + profileBloc.add(const ChangeNameEvent(value: true)); + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Icon( + Icons.edit_outlined, + size: 20, + color: ColorsManager.textPrimaryColor, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'email '), + Flexible( + child: BodyMedium( + text: HomeCubit.user!.email ?? 'No Email')), + ], + ), + ), + Container( + height: 1, + color: ColorsManager.greyColor, + ), + InkWell( + onTap: () { + profileBloc.add(const ChangeNameEvent(value: false)); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const RegionPage(), + ), + ).then((result) { + profileBloc.add(InitialProfileEvent()); + }); + }, + child: Padding( + padding: const EdgeInsets.only(top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Region '), + Flexible( + child: BodyMedium( + text: HomeCubit.user!.regionName ?? 'No Region')), + ], + ), + ), + ), + Container( + height: 1, + color: ColorsManager.greyColor, + ), + InkWell( + onTap: () { + profileBloc.add(const ChangeNameEvent(value: false)); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const TimeZoneScreenPage(), + ), + ).then((result) { + profileBloc.add(InitialProfileEvent()); + }); + }, + child: Padding( + padding: const EdgeInsets.only(top: 15, bottom: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Time Zone '), + Flexible( + child: BodyMedium( + text: HomeCubit.user!.timeZone ?? "No Time Zone"), + ), + ], + ), + ), + ), + ], + )), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/profile/region_page.dart b/lib/features/menu/view/widgets/profile/region_page.dart new file mode 100644 index 0000000..5f1a0f1 --- /dev/null +++ b/lib/features/menu/view/widgets/profile/region_page.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class RegionPage extends StatelessWidget { + const RegionPage({super.key}); + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => ProfileBloc()..add(RegionInitialEvent()), + child: BlocConsumer(listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + if (state is SaveState) { + Navigator.of(context).pop(true); + } + }, + builder: (context, state) { + final profileBloc = BlocProvider.of(context); + final regionList = state is RegionsLoadedState ? state.regions : []; + return DefaultScaffold( + padding: const EdgeInsets.all(0), + title: 'Region', + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + TextFormField( + controller:profileBloc.searchController , + onChanged: (value) { + profileBloc.add(SearchRegionEvent(query: value)); + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.search), + hintText: 'Search', + fillColor: ColorsManager.textGray, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.onPrimaryColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child:Padding( + padding: const EdgeInsets.only(bottom: 10.0,top: 10.0,left: 15,right: 15), + child: ListView.builder( + itemCount: regionList.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + profileBloc.add(SelectRegionEvent(val: regionList[index].id, context: context)); + }, + child:Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10,), + child: SizedBox( + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: BodyMedium( + fontSize: 15, + text: regionList[index].name, + ), + ), + ), + ), + const Divider(color: ColorsManager.textGray), // Divider between items + ], + ), + ); + }, + )), + ), + ), + ], + ), + ); + })); + } +} diff --git a/lib/features/menu/view/widgets/profile/time_zone_screen_page.dart b/lib/features/menu/view/widgets/profile/time_zone_screen_page.dart new file mode 100644 index 0000000..9aac6cf --- /dev/null +++ b/lib/features/menu/view/widgets/profile/time_zone_screen_page.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class TimeZoneScreenPage extends StatelessWidget { + const TimeZoneScreenPage({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => ProfileBloc()..add(TimeZoneInitialEvent()), + child: BlocConsumer(listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + if (state is SaveState) { + Navigator.of(context).pop(true); + } + }, + builder: (context, state) { + final profileBloc = BlocProvider.of(context); + final timeZoneList = state is TimeZoneLoadedState ?state.timezone : []; + return DefaultScaffold( + padding: const EdgeInsets.all(0), + title: 'Time Zone', + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + TextFormField( + controller:profileBloc.searchController , + onChanged: (value) { + profileBloc.add(SearchTimeZoneEvent(query: value)); + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.search), + hintText: 'Search', + fillColor: ColorsManager.textGray, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.onPrimaryColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Padding( + padding: const EdgeInsets.only(bottom: 10.0,top: 10.0,left: 15,right: 15), + child: ListView.builder( + itemCount: timeZoneList.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + profileBloc.add(SelectTimeZoneEvent(val: timeZoneList[index].id, context: context)); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10,), + child: SizedBox( + height: 45, + child: ListTile( + contentPadding: EdgeInsets.zero, + trailing: BodyMedium( + text: timeZoneList[index].offset, + fontSize: 13, + fontColor: ColorsManager.textGray,), + leading: BodyMedium( + fontSize: 13, + text: timeZoneList[index].name,),), + ), + ), + const Divider(color: ColorsManager.textGray), // Divider between items + ], + ), + ); + }, + )), + ), + ), + ], + ), + ); + }) + ); + } +} diff --git a/lib/features/menu/view/widgets/securty/securty_view.dart b/lib/features/menu/view/widgets/securty/securty_view.dart new file mode 100644 index 0000000..b20029c --- /dev/null +++ b/lib/features/menu/view/widgets/securty/securty_view.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SecurtyView extends StatelessWidget { + const SecurtyView({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Securty', + child: Column( + children: [ + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + InkWell( + onTap: () {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: 'Change Password', + ), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 15), + height: 1, + color: ColorsManager.greyColor, + ), + ], + ), + ), + InkWell( + onTap: () {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: 'App Lock', + ), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 15), + height: 1, + color: ColorsManager.greyColor, + ), + ], + ), + ), + InkWell( + onTap: () {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: 'User Code', + ), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 15), + height: 1, + color: ColorsManager.greyColor, + ), + ], + ), + ), + InkWell( + onTap: () {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: 'Delete Account', + ), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 15), + height: 1, + color: ColorsManager.greyColor, + ), + ], + ), + ), + InkWell( + onTap: () {}, + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: 'Device Update', + ), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/scene/bloc/create_scene/create_scene_bloc.dart b/lib/features/scene/bloc/create_scene/create_scene_bloc.dart new file mode 100644 index 0000000..0a52b02 --- /dev/null +++ b/lib/features/scene/bloc/create_scene/create_scene_bloc.dart @@ -0,0 +1,583 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/helper/scene_operations_data_helper.dart'; +import 'package:syncrow_app/features/scene/model/create_automation_model.dart'; +import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; +import 'package:syncrow_app/services/api/scene_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +part 'create_scene_event.dart'; +part 'create_scene_state.dart'; + +class CreateSceneBloc extends Bloc + with SceneOperationsDataHelper { + CreateSceneBloc() : super(CreateSceneInitial()) { + on(_createSceneWithTasks); + on(_onAddSceneTask); + on(_selectedValue); + on(_removeTaskById); + on(_clearTaskList); + on(_clearTempTaskList); + on(_fetchSceneTasks); + on(_onTempHoldSceneTask); + on(_removeTempTaskById); + on(_removeFromSelectedValueById); + on(_deleteScene); + on(_updateTaskValue); + on(_selectConditionRule); + on(_sceneTypeEvent); + on(_onEffectiveTimeEvent); + } + + CreateSceneEnum sceneType = CreateSceneEnum.none; + + /// tab to run values and list + List tasksList = []; + List tempTasksList = []; + final Map selectedValues = {}; + + /// automation values and list + List automationTasksList = []; + List automationTempTasksList = []; + final Map automationSelectedValues = {}; + final Map automationComparatorValues = {}; + String conditionRule = 'or'; + EffectiveTime? effectiveTime; + + FutureOr _onAddSceneTask(AddTaskEvent event, Emitter emit) { + emit(CreateSceneLoading()); + if (event.isAutomation == true) { + final copyList = List.from(automationTempTasksList); + automationTasksList.addAll(copyList); + automationTempTasksList.clear(); + automationSelectedValues.clear(); + automationComparatorValues.clear(); + emit(AddSceneTask( + automationTasksList: automationTasksList, + tasksList: tasksList, + condition: conditionRule, + )); + } else { + final copyList = List.from(tempTasksList); + tasksList.addAll(copyList); + tempTasksList.clear(); + selectedValues.clear(); + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + } + + FutureOr _onTempHoldSceneTask( + TempHoldSceneTasksEvent event, Emitter emit) { + if (event.isAutomation == true) { + addToTempAutomationTaskList(event, emit); + } else { + addToTempTaskList(event, emit); + } + } + + void addToTempTaskList(TempHoldSceneTasksEvent event, Emitter emit) { + emit(CreateSceneLoading()); + bool updated = false; + + // Check and update if the task exists in tempTasksList + for (var element in tempTasksList) { + if (element.code == event.deviceControlModel.code) { + var updatedElement = element.copyWith( + operationName: event.operation, + deviceName: event.deviceName, + icon: event.icon, + code: event.deviceControlModel.code ?? '', + deviceId: event.deviceId, + functionValue: event.deviceControlModel.value, + operationDialogType: event.operationType, + operationalValues: [ + SceneOperationalValue( + value: event.deviceControlModel.value, + icon: '', + ), + ], + ); + tempTasksList[tempTasksList.indexOf(element)] = updatedElement; + selectedValues[updatedElement.code] = event.deviceControlModel.value; + updated = true; + break; + } + } + + if (!updated) { + /// for smart scene add to view + for (var element in tasksList) { + if (element.deviceId == event.deviceControlModel.deviceId && + element.code == event.deviceControlModel.code) { + var updatedElement = element.copyWith( + operationName: event.operation, + deviceName: event.deviceName, + icon: event.icon, + code: event.deviceControlModel.code ?? '', + deviceId: event.deviceId, + functionValue: event.deviceControlModel.value, + operationDialogType: event.operationType, + operationalValues: [ + SceneOperationalValue( + value: event.deviceControlModel.value, + icon: '', + ), + ], + ); + tasksList[tasksList.indexOf(element)] = updatedElement; + selectedValues[updatedElement.code] = event.deviceControlModel.value; + updated = true; + break; + } + } + } + + // Add new element if it doesn't exist in either list + if (!updated) { + var newElement = SceneStaticFunction( + operationName: event.operation, + deviceName: event.deviceName, + icon: event.icon, + code: event.deviceControlModel.code ?? '', + operationDialogType: event.operationType, + deviceId: event.deviceId, + functionValue: event.deviceControlModel.value, + operationalValues: [ + SceneOperationalValue( + value: event.deviceControlModel.value, + icon: '', + ), + ], + ); + tempTasksList.add(newElement); + selectedValues[newElement.code] = event.deviceControlModel.value; + } + + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + + void addToTempAutomationTaskList(TempHoldSceneTasksEvent event, Emitter emit) { + emit(CreateSceneLoading()); + bool updated = false; + for (var element in automationTempTasksList) { + if (element.code == event.deviceControlModel.code) { + // Update the existing function with new values + var updatedElement = element.copyWith( + operationName: event.operation, + deviceName: event.deviceName, + icon: event.icon, + code: event.deviceControlModel.code ?? '', + deviceId: event.deviceId, + functionValue: event.deviceControlModel.value, + operationDialogType: event.operationType, + operationalValues: [ + SceneOperationalValue( + value: event.deviceControlModel.value, + icon: '', + ), + ], + comparator: automationComparatorValues[element.code], + ); + automationTempTasksList[automationTempTasksList.indexOf(element)] = updatedElement; + automationSelectedValues[updatedElement.code] = event.deviceControlModel.value; + updated = true; + break; + } + } + if (!updated) { + var newElement = SceneStaticFunction( + operationName: event.operation, + deviceName: event.deviceName, + icon: event.icon, + code: event.deviceControlModel.code ?? '', + operationDialogType: event.operationType, + deviceId: event.deviceId, + functionValue: event.deviceControlModel.value, + operationalValues: [ + SceneOperationalValue( + value: event.deviceControlModel.value, + icon: '', + ), + ], + comparator: automationComparatorValues[event.deviceControlModel.code] ?? '==', + ); + automationTempTasksList.add(newElement); + automationSelectedValues[newElement.code] = event.deviceControlModel.value; + } + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + + FutureOr _selectedValue(SelectedValueEvent event, Emitter emit) { + if (event.isAutomation == true) { + automationSelectedValues[event.code] = event.value; + automationComparatorValues[event.code] = event.comparator ?? '=='; + + // Update the comparator value for the specific task in automationTasksList + for (int i = 0; i < automationTasksList.length; i++) { + if (automationTasksList[i].code == event.code) { + automationTasksList[i] = automationTasksList[i].copyWith( + comparator: event.comparator ?? '==', + functionValue: event.value, + ); + break; + } + } + for (int i = 0; i < tasksList.length; i++) { + if (tasksList[i].code == event.code) { + tasksList[i] = tasksList[i].copyWith( + comparator: event.comparator ?? '==', + functionValue: event.value, + ); + break; + } + } + } else { + selectedValues[event.code] = event.value; + } + emit(SelectedTaskValueState(value: event.value)); + emit(AddSceneTask( + tasksList: List.from(tasksList), + automationTasksList: List.from( + automationTasksList, + ), + condition: conditionRule, + )); + } + + FutureOr _removeTaskById(RemoveTaskByIdEvent event, Emitter emit) { + emit(CreateSceneLoading()); + if (event.isAutomation == true) { + for (var element in automationTasksList) { + if (element.uniqueCustomId == event.taskId) { + automationTasksList.remove(element); + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + break; + } + } + } else { + for (var element in tasksList) { + if (element.uniqueCustomId == event.taskId) { + tasksList.remove(element); + + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + break; + } + } + } + } + + FutureOr _removeTempTaskById( + RemoveTempTaskByIdEvent event, Emitter emit) { + emit(CreateSceneLoading()); + if (event.isAutomation == true) { + for (var element in automationTempTasksList) { + if (element.uniqueCustomId == event.code) { + automationTempTasksList.remove(element); + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + break; + } + } + } else { + for (var element in tempTasksList) { + if (element.code == event.code) { + tempTasksList.remove(element); + + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + + break; + } + } + } + } + + FutureOr _createSceneWithTasks( + CreateSceneWithTasksEvent event, Emitter emit) async { + emit(CreateSceneLoading()); + try { + dynamic response; + if (event.createSceneModel != null) { + response = event.updateScene + ? await SceneApi.updateScene(event.createSceneModel!, event.sceneId) + : await SceneApi.createScene(event.createSceneModel!); + } else if (event.createAutomationModel != null) { + response = event.updateScene + ? await SceneApi.updateAutomation(event.createAutomationModel!, event.sceneId) + : await SceneApi.createAutomation(event.createAutomationModel!); + } + + if (response['success'] == true) { + tasksList.clear(); + tempTasksList.clear(); + selectedValues.clear(); + automationTasksList.clear(); + automationTempTasksList.clear(); + automationSelectedValues.clear(); + automationComparatorValues.clear(); + effectiveTime = EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'); + sceneType = CreateSceneEnum.none; + conditionRule = 'or'; + emit(const CreateSceneWithTasks(success: true)); + CustomSnackBar.greenSnackBar( + event.updateScene ? 'Scene updated successfully' : 'Scene created successfully'); + } else { + emit(const CreateSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const CreateSceneError(message: 'Something went wrong')); + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + } + + FutureOr _clearTaskList(ClearTaskListEvent event, Emitter emit) { + emit(CreateSceneLoading()); + automationTasksList.clear(); + tasksList.clear(); + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + + FutureOr _fetchSceneTasks( + FetchSceneTasksEvent event, Emitter emit) async { + emit(CreateSceneLoading()); + + try { + tasksList.clear(); + tempTasksList.clear(); + selectedValues.clear(); + automationTasksList.clear(); + automationTempTasksList.clear(); + automationSelectedValues.clear(); + automationComparatorValues.clear(); + effectiveTime = EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'); + sceneType = CreateSceneEnum.none; + conditionRule = 'or'; + + final response = event.isAutomation + ? await SceneApi.getAutomationDetails(event.sceneId) + : await SceneApi.getSceneDetails(event.sceneId); + if (response.id.isNotEmpty) { + if (event.isAutomation) { + automationTasksList = List.from(getTaskListFunctionsFromApi( + actions: [], isAutomation: true, conditions: response.conditions)); + tasksList = List.from( + getTaskListFunctionsFromApi(actions: response.actions, isAutomation: false)); + + conditionRule = response.decisionExpr ?? conditionRule; + + effectiveTime = response.effectiveTime != null + ? EffectiveTime( + start: response.effectiveTime!.start, + end: response.effectiveTime!.end, + loops: response.effectiveTime!.loops, + ) + : EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'); + + // Set the days directly from the API response + BlocProvider.of(NavigationService.navigatorKey.currentContext!) + .add(SetDays(response.effectiveTime?.loops ?? '1111111')); + + // Set Custom Time and reset days first + BlocProvider.of(NavigationService.navigatorKey.currentContext!) + .add(SetCustomTime(effectiveTime!.start, effectiveTime!.end)); + + emit(AddSceneTask( + automationTasksList: automationTasksList, + tasksList: tasksList, + condition: conditionRule, + )); + } else { + tasksList = List.from( + getTaskListFunctionsFromApi(actions: response.actions, isAutomation: false)); + emit(AddSceneTask( + tasksList: tasksList, + condition: conditionRule, + )); + } + } else { + emit(const CreateSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const CreateSceneError(message: 'Something went wrong')); + } + } + + String _getDayFromIndex(int index) { + const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + return days[index]; + } + + FutureOr _clearTempTaskList(ClearTempTaskListEvent event, Emitter emit) { + emit(CreateSceneLoading()); + if (event.isAutomation == true) { + automationTempTasksList.clear(); + automationSelectedValues.clear(); + automationComparatorValues.clear(); + } else { + tempTasksList.clear(); + selectedValues.clear(); + } + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + + FutureOr _removeFromSelectedValueById( + RemoveFromSelectedValueById event, Emitter emit) { + emit(CreateSceneLoading()); + if (event.isAutomation == true) { + if (automationSelectedValues.containsKey(event.code)) { + automationSelectedValues.remove(event.code); + automationComparatorValues.remove(event.code); + emit(const SelectedTaskValueState(value: null)); + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + } else { + if (selectedValues.containsKey(event.code)) { + selectedValues.remove(event.code); + emit(const SelectedTaskValueState(value: null)); + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + } + } + + FutureOr _deleteScene(DeleteSceneEvent event, Emitter emit) async { + emit(DeleteSceneLoading()); + + try { + final response = sceneType.name == CreateSceneEnum.deviceStatusChanges.name + ? await SceneApi.deleteAutomation(automationId: event.sceneId, unitUuid: event.unitUuid) + : await SceneApi.deleteScene(sceneId: event.sceneId, unitUuid: event.unitUuid); + if (response == true) { + emit(const DeleteSceneSuccess(true)); + } else { + emit(const DeleteSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const DeleteSceneError(message: 'Something went wrong')); + } + } + + FutureOr _updateTaskValue(UpdateTaskEvent event, Emitter emit) { + emit(CreateSceneLoading()); + if (event.isAutomation == true) { + for (var i = 0; i < automationTasksList.length; i++) { + if (automationTasksList[i].uniqueCustomId == event.taskId) { + automationTasksList[i] = automationTasksList[i].copyWith( + functionValue: event.newValue, + ); + break; + } + } + for (var i = 0; i < tasksList.length; i++) { + if (tasksList[i].uniqueCustomId == event.taskId) { + tasksList[i] = tasksList[i].copyWith( + functionValue: event.newValue, + ); + break; + } + } + } else { + for (var i = 0; i < tasksList.length; i++) { + if (tasksList[i].uniqueCustomId == event.taskId) { + tasksList[i] = tasksList[i].copyWith( + functionValue: event.newValue, + ); + break; + } + } + } + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + + FutureOr _selectConditionRule(SelectConditionEvent event, Emitter emit) { + emit(CreateSceneInitial()); + if (event.condition.contains('any')) { + conditionRule = 'or'; + } else { + conditionRule = 'and'; + } + + emit(AddSceneTask( + tasksList: tasksList, + automationTasksList: automationTasksList, + condition: conditionRule, + )); + } + + FutureOr _sceneTypeEvent(SceneTypeEvent event, Emitter emit) { + // emit(CreateSceneInitial()); + + if (event.type == CreateSceneEnum.tabToRun) { + sceneType = CreateSceneEnum.tabToRun; + } else if (event.type == CreateSceneEnum.deviceStatusChanges) { + sceneType = CreateSceneEnum.deviceStatusChanges; + } else { + sceneType = CreateSceneEnum.none; + } + + // emit(SceneTypeState(event.type)); + } + + FutureOr _onEffectiveTimeEvent( + EffectiveTimePeriodEvent event, Emitter emit) { + effectiveTime = event.period; + } +} diff --git a/lib/features/scene/bloc/create_scene/create_scene_event.dart b/lib/features/scene/bloc/create_scene/create_scene_event.dart new file mode 100644 index 0000000..74046bd --- /dev/null +++ b/lib/features/scene/bloc/create_scene/create_scene_event.dart @@ -0,0 +1,185 @@ +part of 'create_scene_bloc.dart'; + +sealed class CreateSceneEvent extends Equatable { + const CreateSceneEvent(); + + @override + List get props => []; +} + +class AddTaskEvent extends CreateSceneEvent { + const AddTaskEvent({this.isAutomation}); + final bool? isAutomation; + + @override + List get props => []; +} + +class TempHoldSceneTasksEvent extends CreateSceneEvent { + final DeviceControlModel deviceControlModel; + final String deviceId; + final String icon; + final String operation; + final String deviceName; + final String uniqueId; + final bool? isAutomation; + final OperationDialogType operationType; + + const TempHoldSceneTasksEvent({ + required this.deviceControlModel, + required this.deviceId, + required this.icon, + required this.operation, + required this.deviceName, + required this.uniqueId, + this.isAutomation, + required this.operationType, + }); + + @override + List get props => [ + deviceControlModel, + deviceId, + deviceName, + icon, + operation, + uniqueId, + deviceName, + icon, + ]; +} + +class UpdateTaskEvent extends CreateSceneEvent { + final String taskId; + final dynamic newValue; + final bool? isAutomation; + const UpdateTaskEvent({ + required this.taskId, + required this.newValue, + this.isAutomation, + }); + @override + List get props => [taskId, newValue]; +} + +class SelectedValueEvent extends CreateSceneEvent { + final dynamic value; + final dynamic automationValue; + final String code; + final bool? isAutomation; + final String? comparator; + + const SelectedValueEvent({ + this.value, + required this.code, + this.isAutomation, + this.automationValue, + this.comparator, + }); + + @override + List get props => [value!, code]; +} + +class RemoveTaskByIdEvent extends CreateSceneEvent { + final String taskId; + final bool? isAutomation; + const RemoveTaskByIdEvent({ + required this.taskId, + this.isAutomation, + }); + + @override + List get props => [taskId]; +} + +class RemoveTempTaskByIdEvent extends CreateSceneEvent { + final String code; + final bool? isAutomation; + const RemoveTempTaskByIdEvent({required this.code, this.isAutomation}); + + @override + List get props => [code]; +} + +class RemoveFromSelectedValueById extends CreateSceneEvent { + final String code; + final bool? isAutomation; + const RemoveFromSelectedValueById({required this.code, this.isAutomation}); + + @override + List get props => [code]; +} + +class CreateSceneWithTasksEvent extends CreateSceneEvent { + final CreateSceneModel? createSceneModel; + final bool updateScene; + final String sceneId; + final CreateAutomationModel? createAutomationModel; + //final bool isAutomation; + const CreateSceneWithTasksEvent({ + required this.createSceneModel, + required this.updateScene, + required this.sceneId, + required this.createAutomationModel, + // required this.isAutomation, + }); + + @override + List get props => []; +} + +class ClearTaskListEvent extends CreateSceneEvent { + const ClearTaskListEvent({this.isAutomation}); + final bool? isAutomation; + + @override + List get props => []; +} + +class ClearTempTaskListEvent extends CreateSceneEvent { + const ClearTempTaskListEvent({this.isAutomation}); + final bool? isAutomation; + + @override + List get props => []; +} + +class FetchSceneTasksEvent extends CreateSceneEvent { + final String sceneId; + final bool isAutomation; + + const FetchSceneTasksEvent( + {this.isAutomation = false, required this.sceneId}); + + @override + List get props => [sceneId, isAutomation]; +} + +class DeleteSceneEvent extends CreateSceneEvent { + final String sceneId; + final String unitUuid; + const DeleteSceneEvent({ + required this.sceneId, + required this.unitUuid, + }); + + @override + List get props => [sceneId, unitUuid]; +} + +class SelectConditionEvent extends CreateSceneEvent { + final String condition; + + const SelectConditionEvent(this.condition); +} + +class SceneTypeEvent extends CreateSceneEvent { + final CreateSceneEnum type; + const SceneTypeEvent(this.type); +} + +class EffectiveTimePeriodEvent extends CreateSceneEvent { + final EffectiveTime period; + const EffectiveTimePeriodEvent(this.period); +} diff --git a/lib/features/scene/bloc/create_scene/create_scene_state.dart b/lib/features/scene/bloc/create_scene/create_scene_state.dart new file mode 100644 index 0000000..4108738 --- /dev/null +++ b/lib/features/scene/bloc/create_scene/create_scene_state.dart @@ -0,0 +1,86 @@ +part of 'create_scene_bloc.dart'; + +sealed class CreateSceneState extends Equatable { + const CreateSceneState(); + + @override + List get props => []; +} + +final class CreateSceneInitial extends CreateSceneState {} + +class CreateSceneLoading extends CreateSceneState {} + +class CreateSceneError extends CreateSceneState { + final String message; + const CreateSceneError({required this.message}); + + @override + List get props => [message]; +} + +class AddSceneTask extends CreateSceneState { + final List tasksList; + final List? automationTasksList; + final String? condition; + const AddSceneTask( + {required this.tasksList, this.automationTasksList, this.condition}); + + @override + List get props => [tasksList]; +} + +class TempHoldSceneTask extends CreateSceneState { + final List tempTasksList; + final List? automationTempTasksList; + const TempHoldSceneTask( + {required this.tempTasksList, this.automationTempTasksList}); + + @override + List get props => [tempTasksList]; +} + +class SelectedTaskValueState extends CreateSceneState { + final dynamic value; + const SelectedTaskValueState({required this.value}); + + @override + List get props => [value]; +} + +class CreateSceneWithTasks extends CreateSceneState { + final bool success; + const CreateSceneWithTasks({required this.success}); + + @override + List get props => [success]; +} + +class DeleteSceneSuccess extends CreateSceneState { + final bool success; + const DeleteSceneSuccess(this.success); + + @override + List get props => [success]; +} + +class DeleteSceneError extends CreateSceneState { + final String message; + const DeleteSceneError({required this.message}); + + @override + List get props => [message]; +} + +class DeleteSceneLoading extends CreateSceneState {} + +class ConditionSelectedState extends CreateSceneState { + final String condition; + + const ConditionSelectedState(this.condition); +} + +class SceneTypeState extends CreateSceneState { + final CreateSceneEnum type; + const SceneTypeState(this.type); +} diff --git a/lib/features/scene/bloc/effective_period/effect_period_bloc.dart b/lib/features/scene/bloc/effective_period/effect_period_bloc.dart new file mode 100644 index 0000000..307a950 --- /dev/null +++ b/lib/features/scene/bloc/effective_period/effect_period_bloc.dart @@ -0,0 +1,129 @@ +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_app/features/scene/enum/effective_period_options.dart'; +import 'package:syncrow_app/features/scene/model/create_automation_model.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; + +class EffectPeriodBloc extends Bloc { + final daysMap = { + 'Sun': 'S', + 'Mon': 'M', + 'Tue': 'T', + 'Wed': 'W', + 'Thu': 'T', + 'Fri': 'F', + 'Sat': 'S', + }; + + EffectPeriodBloc() : super(EffectPeriodState.initial()) { + on(_onSetPeriod); + on(_onToggleDay); + on(_onSetCustomTime); + on(_onResetEffectivePeriod); + on(_onResetDays); + on(_setAllDays); + } + + void _onSetPeriod(SetPeriod event, Emitter emit) { + String startTime = ''; + String endTime = ''; + + switch (event.period) { + case EnumEffectivePeriodOptions.allDay: + startTime = '00:00'; + endTime = '23:59'; + break; + case EnumEffectivePeriodOptions.daytime: + startTime = '06:00'; + endTime = '18:00'; + break; + case EnumEffectivePeriodOptions.night: + startTime = '18:00'; + endTime = '06:00'; + break; + case EnumEffectivePeriodOptions.custom: + startTime = state.customStartTime ?? '00:00'; + endTime = state.customEndTime ?? '23:59'; + break; + default: + break; + } + + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent( + EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); + + emit(state.copyWith( + selectedPeriod: event.period, customStartTime: startTime, customEndTime: endTime)); + } + + void _onToggleDay(ToggleDay event, Emitter emit) { + final daysList = state.selectedDaysBinary.split(''); + final dayIndex = getDayIndex(event.day); + if (daysList[dayIndex] == '1') { + daysList[dayIndex] = '0'; + } else { + daysList[dayIndex] = '1'; + } + final newDaysBinary = daysList.join(); + emit(state.copyWith(selectedDaysBinary: newDaysBinary)); + + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent(EffectiveTime( + start: state.customStartTime ?? '00:00', + end: state.customEndTime ?? '23:59', + loops: newDaysBinary))); + } + + void _onSetCustomTime(SetCustomTime event, Emitter emit) { + String startTime = event.startTime; + String endTime = event.endTime; + EnumEffectivePeriodOptions period; + + // Determine the period based on start and end times + if (startTime == '00:00' && endTime == '23:59') { + period = EnumEffectivePeriodOptions.allDay; + } else if (startTime == '06:00' && endTime == '18:00') { + period = EnumEffectivePeriodOptions.daytime; + } else if (startTime == '18:00' && endTime == '06:00') { + period = EnumEffectivePeriodOptions.night; + } else { + period = EnumEffectivePeriodOptions.custom; + } + + emit( + state.copyWith(customStartTime: startTime, customEndTime: endTime, selectedPeriod: period)); + + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent( + EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary))); + } + + void _onResetEffectivePeriod(ResetEffectivePeriod event, Emitter emit) { + emit(state.copyWith( + selectedPeriod: EnumEffectivePeriodOptions.allDay, + customStartTime: '00:00', + customEndTime: '23:59', + selectedDaysBinary: '1111111')); + + BlocProvider.of(NavigationService.navigatorKey.currentContext!).add( + EffectiveTimePeriodEvent(EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'))); + } + + void _onResetDays(ResetDays event, Emitter emit) { + emit(state.copyWith(selectedDaysBinary: '1111111')); + } + + int getDayIndex(String day) { + const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + return days.indexOf(day); + } + + FutureOr _setAllDays(SetDays event, Emitter emit) { + emit(state.copyWith(selectedDaysBinary: event.daysBinary)); + } +} diff --git a/lib/features/scene/bloc/effective_period/effect_period_event.dart b/lib/features/scene/bloc/effective_period/effect_period_event.dart new file mode 100644 index 0000000..cd1a9c0 --- /dev/null +++ b/lib/features/scene/bloc/effective_period/effect_period_event.dart @@ -0,0 +1,50 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/scene/enum/effective_period_options.dart'; + +abstract class EffectPeriodEvent extends Equatable { + const EffectPeriodEvent(); + + @override + List get props => []; +} + +class SetPeriod extends EffectPeriodEvent { + final EnumEffectivePeriodOptions period; + + const SetPeriod(this.period); + + @override + List get props => [period]; +} + +class ToggleDay extends EffectPeriodEvent { + final String day; + + const ToggleDay(this.day); + + @override + List get props => [day]; +} + +class SetCustomTime extends EffectPeriodEvent { + final String startTime; + final String endTime; + + const SetCustomTime(this.startTime, this.endTime); + + @override + List get props => [startTime, endTime]; +} + +class ResetEffectivePeriod extends EffectPeriodEvent {} + +class ResetDays extends EffectPeriodEvent { + @override + List get props => []; +} + +class SetDays extends EffectPeriodEvent { + final String daysBinary; + + const SetDays(this.daysBinary); +} diff --git a/lib/features/scene/bloc/effective_period/effect_period_state.dart b/lib/features/scene/bloc/effective_period/effect_period_state.dart new file mode 100644 index 0000000..2b00a45 --- /dev/null +++ b/lib/features/scene/bloc/effective_period/effect_period_state.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/scene/enum/effective_period_options.dart'; + +class EffectPeriodState extends Equatable { + final EnumEffectivePeriodOptions selectedPeriod; + final String selectedDaysBinary; + final String? customStartTime; + final String? customEndTime; + + const EffectPeriodState({ + required this.selectedPeriod, + required this.selectedDaysBinary, + this.customStartTime, + this.customEndTime, + }); + + factory EffectPeriodState.initial() { + return const EffectPeriodState( + selectedPeriod: EnumEffectivePeriodOptions.allDay, + selectedDaysBinary: "1111111", // All days selected + customStartTime: "00:00", + customEndTime: "23:59", + ); + } + + EffectPeriodState copyWith({ + EnumEffectivePeriodOptions? selectedPeriod, + String? selectedDaysBinary, + String? customStartTime, + String? customEndTime, + }) { + return EffectPeriodState( + selectedPeriod: selectedPeriod ?? this.selectedPeriod, + selectedDaysBinary: selectedDaysBinary ?? this.selectedDaysBinary, + customStartTime: customStartTime ?? this.customStartTime, + customEndTime: customEndTime ?? this.customEndTime, + ); + } + + EnumEffectivePeriodOptions getEffectivePeriod() { + if (customStartTime == '00:00' && customEndTime == '23:59') { + return EnumEffectivePeriodOptions.allDay; + } else if (customStartTime == '06:00' && customEndTime == '18:00') { + return EnumEffectivePeriodOptions.daytime; + } else if (customStartTime == '18:00' && customEndTime == '06:00') { + return EnumEffectivePeriodOptions.night; + } else { + return EnumEffectivePeriodOptions.custom; + } + } + + @override + List get props => + [selectedPeriod, selectedDaysBinary, customStartTime, customEndTime]; +} diff --git a/lib/features/scene/bloc/scene_bloc/scene_bloc.dart b/lib/features/scene/bloc/scene_bloc/scene_bloc.dart new file mode 100644 index 0000000..047e9cb --- /dev/null +++ b/lib/features/scene/bloc/scene_bloc/scene_bloc.dart @@ -0,0 +1,111 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; +import 'package:syncrow_app/features/scene/model/scenes_model.dart'; +import 'package:syncrow_app/services/api/scene_api.dart'; + +part 'scene_state.dart'; + +class SceneBloc extends Bloc { + SceneBloc() : super(SceneInitial()) { + on(_onLoadScenes); + on(_onLoadAutomation); + on(_onSceneTrigger); + on(_onUpdateAutomationStatus); + } + + List scenes = []; + List automationList = []; + + Future _onLoadScenes(LoadScenes event, Emitter emit) async { + emit(SceneLoading()); + + try { + if (event.unitId.isNotEmpty) { + scenes = await SceneApi.getScenesByUnitId(event.unitId); + emit(SceneLoaded(scenes, automationList)); + } else { + emit(const SceneError(message: 'Unit ID is empty')); + } + } catch (e) { + emit(const SceneError(message: 'Something went wrong')); + } + } + + Future _onLoadAutomation( + LoadAutomation event, Emitter emit) async { + emit(SceneLoading()); + + try { + if (event.unitId.isNotEmpty) { + automationList = await SceneApi.getAutomationByUnitId(event.unitId); + emit(SceneLoaded(scenes, automationList)); + } else { + emit(const SceneError(message: 'Unit ID is empty')); + } + } catch (e) { + emit(const SceneError(message: 'Something went wrong')); + } + } + + Future _onSceneTrigger( + SceneTrigger event, Emitter emit) async { + final currentState = state; + if (currentState is SceneLoaded) { + emit(SceneLoaded( + currentState.scenes, + currentState.automationList, + loadingSceneId: event.sceneId, + )); + + try { + final success = await SceneApi.triggerScene(event.sceneId); + if (success) { + emit(SceneTriggerSuccess(event.name)); + emit(SceneLoaded(currentState.scenes, currentState.automationList)); + } else { + emit(const SceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const SceneError(message: 'Something went wrong')); + } + } + } + + Future _onUpdateAutomationStatus( + UpdateAutomationStatus event, Emitter emit) async { + final currentState = state; + if (currentState is SceneLoaded) { + final newLoadingStates = + Map.from(currentState.loadingStates) + ..[event.automationId] = true; + + emit(SceneLoaded( + currentState.scenes, + currentState.automationList, + loadingStates: newLoadingStates, + )); + + try { + final success = await SceneApi.updateAutomationStatus( + event.automationId, event.automationStatusUpdate); + if (success) { + automationList = await SceneApi.getAutomationByUnitId( + event.automationStatusUpdate.unitUuid); + newLoadingStates[event.automationId] = false; + emit(SceneLoaded( + currentState.scenes, + automationList, + loadingStates: newLoadingStates, + )); + } else { + emit(const SceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const SceneError(message: 'Something went wrong')); + } + } + } +} diff --git a/lib/features/scene/bloc/scene_bloc/scene_event.dart b/lib/features/scene/bloc/scene_bloc/scene_event.dart new file mode 100644 index 0000000..d580ed4 --- /dev/null +++ b/lib/features/scene/bloc/scene_bloc/scene_event.dart @@ -0,0 +1,49 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/scene/model/update_automation.dart'; + +abstract class SceneEvent extends Equatable { + const SceneEvent(); + + @override + List get props => []; +} + +class LoadScenes extends SceneEvent { + final String unitId; + + const LoadScenes(this.unitId); + + @override + List get props => [unitId]; +} + +class LoadAutomation extends SceneEvent { + final String unitId; + + const LoadAutomation(this.unitId); + + @override + List get props => [unitId]; +} + +class SceneTrigger extends SceneEvent { + final String sceneId; + final String name; + + const SceneTrigger(this.sceneId, this.name); + + @override + List get props => [sceneId]; +} + +//updateAutomationStatus +class UpdateAutomationStatus extends SceneEvent { + final String automationId; + final AutomationStatusUpdate automationStatusUpdate; + + const UpdateAutomationStatus( + {required this.automationStatusUpdate, required this.automationId}); + + @override + List get props => [automationStatusUpdate]; +} diff --git a/lib/features/scene/bloc/scene_bloc/scene_state.dart b/lib/features/scene/bloc/scene_bloc/scene_state.dart new file mode 100644 index 0000000..0b6ac85 --- /dev/null +++ b/lib/features/scene/bloc/scene_bloc/scene_state.dart @@ -0,0 +1,48 @@ +part of 'scene_bloc.dart'; + +abstract class SceneState extends Equatable { + const SceneState(); + + @override + List get props => []; +} + +class SceneInitial extends SceneState {} + +class SceneLoading extends SceneState {} + +class SceneLoaded extends SceneState { + final List scenes; + final List automationList; + final String? loadingSceneId; + final Map loadingStates; + + const SceneLoaded(this.scenes, this.automationList, + {this.loadingSceneId, this.loadingStates = const {}}); + + @override + List get props => + [scenes, loadingSceneId, automationList, loadingStates]; +} + +class SceneError extends SceneState { + final String message; + + const SceneError({required this.message}); + + @override + List get props => [message]; +} + +class SceneTriggerSuccess extends SceneState { + final String sceneName; + + const SceneTriggerSuccess(this.sceneName); + + @override + List get props => [sceneName]; +} + +class UpdateAutomationStatusLoading extends SceneState { + const UpdateAutomationStatusLoading(); +} diff --git a/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart b/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart new file mode 100644 index 0000000..a6370d8 --- /dev/null +++ b/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/smart_scene_enable.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; +part 'smart_scene_select_dart_event.dart'; +part 'smart_scene_select_dart_state.dart'; + +class SmartSceneSelectBloc + extends Bloc { + SmartSceneSelectBloc() : super(SmartSceneSelectInitial()) { + on(_onSmartSceneEnable); + on(_smartSceneClear); + on(_smartSceneConfirmSelection); + } + + SmartSceneEnable? smartSceneEnable; + + FutureOr _onSmartSceneEnable( + SmartSceneEnableEvent event, Emitter emit) { + smartSceneEnable = event.smartSceneEnable; + } + + FutureOr _smartSceneClear( + SmartSceneClearEvent event, Emitter emit) { + smartSceneEnable = null; + emit(SmartSceneSelectInitial()); + } + + FutureOr _smartSceneConfirmSelection( + SmartSceneConfirmSelectionEvent event, + Emitter emit) { + final createSceneBloc = NavigationService.navigatorKey.currentState!.context + .read(); + + createSceneBloc.add(TempHoldSceneTasksEvent( + deviceControlModel: DeviceControlModel( + deviceId: smartSceneEnable?.entityId ?? '', + code: CreateSceneEnum.smartSceneSelect.name, + value: smartSceneEnable?.actionExecutor ?? '', + ), + deviceId: smartSceneEnable?.entityId ?? '', + operation: smartSceneEnable?.type ?? '', + icon: smartSceneEnable?.isAutomation == true + ? Assets.player + : Assets.handClickIcon, + deviceName: smartSceneEnable?.sceneORAutomationName ?? '', + uniqueId: smartSceneEnable?.entityId ?? '', + operationType: OperationDialogType.none, + isAutomation: false, + )); + + emit(SmartSceneSelected(smartSceneEnable: smartSceneEnable!)); + NavigationService.navigatorKey.currentState!.context + .read() + .add(const AddTaskEvent(isAutomation: false)); + } +} diff --git a/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_event.dart b/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_event.dart new file mode 100644 index 0000000..ce70e65 --- /dev/null +++ b/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_event.dart @@ -0,0 +1,25 @@ +part of 'smart_scene_select_dart_bloc.dart'; + +sealed class SmartSceneSelectEvent extends Equatable { + const SmartSceneSelectEvent(); + + @override + List get props => []; +} + +class SmartSceneEnableEvent extends SmartSceneSelectEvent { + final SmartSceneEnable smartSceneEnable; + + const SmartSceneEnableEvent(this.smartSceneEnable); + + @override + List get props => [smartSceneEnable]; +} + +class SmartSceneClearEvent extends SmartSceneSelectEvent { + const SmartSceneClearEvent(); +} + +class SmartSceneConfirmSelectionEvent extends SmartSceneSelectEvent { + const SmartSceneConfirmSelectionEvent(); +} diff --git a/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_state.dart b/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_state.dart new file mode 100644 index 0000000..d29b748 --- /dev/null +++ b/lib/features/scene/bloc/smart_scene/smart_scene_select_dart_state.dart @@ -0,0 +1,19 @@ +part of 'smart_scene_select_dart_bloc.dart'; + +sealed class SmartSceneSelectState extends Equatable { + const SmartSceneSelectState(); + + @override + List get props => []; +} + +final class SmartSceneSelectInitial extends SmartSceneSelectState {} + +class SmartSceneSelected extends SmartSceneSelectState { + final SmartSceneEnable smartSceneEnable; + + const SmartSceneSelected({required this.smartSceneEnable}); + + @override + List get props => [smartSceneEnable]; +} diff --git a/lib/features/scene/bloc/tab_change/tab_change_bloc.dart b/lib/features/scene/bloc/tab_change/tab_change_bloc.dart new file mode 100644 index 0000000..a7c6481 --- /dev/null +++ b/lib/features/scene/bloc/tab_change/tab_change_bloc.dart @@ -0,0 +1,25 @@ +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_event.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_event.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_state.dart'; + +class TabBarBloc extends Bloc { + final DeviceManagerBloc deviceManagerBloc; + TabBarBloc(this.deviceManagerBloc) : super(const Initial()) { + on(_handleTabChanged); + } + + FutureOr _handleTabChanged( + TabChanged event, Emitter emit) { + if (event.roomId == "-1") { + deviceManagerBloc.add(FetchAllDevices()); + } else { + deviceManagerBloc.add(FetchDevicesByRoomId(event.roomId)); + } + emit(TabSelected( + roomId: event.roomId, selectedTabIndex: event.selectedIndex)); + } +} diff --git a/lib/features/scene/bloc/tab_change/tab_change_event.dart b/lib/features/scene/bloc/tab_change/tab_change_event.dart new file mode 100644 index 0000000..c052280 --- /dev/null +++ b/lib/features/scene/bloc/tab_change/tab_change_event.dart @@ -0,0 +1,9 @@ +abstract class TabBarEvent { + const TabBarEvent(); +} + +class TabChanged extends TabBarEvent { + final int selectedIndex; + final String roomId; + const TabChanged({required this.selectedIndex, required this.roomId}); +} diff --git a/lib/features/scene/bloc/tab_change/tab_change_state.dart b/lib/features/scene/bloc/tab_change/tab_change_state.dart new file mode 100644 index 0000000..4a42c44 --- /dev/null +++ b/lib/features/scene/bloc/tab_change/tab_change_state.dart @@ -0,0 +1,13 @@ +abstract class TabBarState { + const TabBarState(); +} + +class Initial extends TabBarState { + const Initial(); +} + +class TabSelected extends TabBarState { + final int selectedTabIndex; + final String roomId; + const TabSelected({required this.roomId, required this.selectedTabIndex}); +} diff --git a/lib/features/scene/enum/ac_values.dart b/lib/features/scene/enum/ac_values.dart new file mode 100644 index 0000000..5694fdd --- /dev/null +++ b/lib/features/scene/enum/ac_values.dart @@ -0,0 +1,7 @@ +// ignore_for_file: constant_identifier_names + +enum AcValuesEnums { + Cooling, + Heating, + Ventilation, +} diff --git a/lib/features/scene/enum/create_scene_enum.dart b/lib/features/scene/enum/create_scene_enum.dart new file mode 100644 index 0000000..9a88069 --- /dev/null +++ b/lib/features/scene/enum/create_scene_enum.dart @@ -0,0 +1,6 @@ +enum CreateSceneEnum { + tabToRun, + deviceStatusChanges, + smartSceneSelect, + none, +} diff --git a/lib/features/scene/enum/effective_period_options.dart b/lib/features/scene/enum/effective_period_options.dart new file mode 100644 index 0000000..97877b6 --- /dev/null +++ b/lib/features/scene/enum/effective_period_options.dart @@ -0,0 +1,7 @@ +enum EnumEffectivePeriodOptions { + allDay, + daytime, + night, + custom, + none, +} diff --git a/lib/features/scene/enum/operation_dialog_type.dart b/lib/features/scene/enum/operation_dialog_type.dart new file mode 100644 index 0000000..b1829b1 --- /dev/null +++ b/lib/features/scene/enum/operation_dialog_type.dart @@ -0,0 +1,9 @@ +enum OperationDialogType { + countdown, + delay, + temperature, + onOff, + integerSteps, + listOfOptions, + none, +} diff --git a/lib/features/scene/helper/effect_period_helper.dart b/lib/features/scene/helper/effect_period_helper.dart new file mode 100644 index 0000000..2b54fa8 --- /dev/null +++ b/lib/features/scene/helper/effect_period_helper.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_app/features/scene/enum/effective_period_options.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:time_picker_spinner/time_picker_spinner.dart'; + +class EffectPeriodHelper { + static Future?> showCustomTimePicker( + BuildContext context) async { + String selectedStartTime = "00:00"; + String selectedEndTime = "23:59"; + PageController pageController = PageController(initialPage: 0); + + DateTime startDateTime = DateTime(2022, 1, 1, 0, 0); + DateTime endDateTime = DateTime(2022, 1, 1, 23, 59); + + context.customAlertDialog( + alertBody: SizedBox( + height: 250, + child: PageView( + controller: pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + _buildTimePickerPage( + context: context, + pageController: pageController, + isStartTime: true, + time: startDateTime, + onTimeChange: (time) { + selectedStartTime = + "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"; + }, + ), + _buildTimePickerPage( + context: context, + pageController: pageController, + isStartTime: false, + time: endDateTime, + onTimeChange: (time) { + selectedEndTime = + "${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}"; + }, + ), + ], + ), + ), + title: "Custom", + onConfirm: () { + context.read().add( + SetCustomTime(selectedStartTime, selectedEndTime), + ); + context.read().add( + const SetPeriod(EnumEffectivePeriodOptions.custom), + ); + + Navigator.of(context).pop(); + }, + ); + return null; + } + + static Widget _buildTimePickerPage({ + required BuildContext context, + required PageController pageController, + required bool isStartTime, + required DateTime time, + required Function(DateTime) onTimeChange, + }) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!isStartTime) + TextButton( + onPressed: () { + pageController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + child: const BodyMedium(text: "Start"), + ), + TextButton( + onPressed: () {}, + child: BodyMedium( + text: isStartTime ? "Start" : "End", + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColor, + ), + ), + ), + if (isStartTime) + TextButton( + onPressed: () { + pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + }, + child: const BodyMedium(text: "End"), + ), + ], + ), + ), + TimePickerSpinner( + is24HourMode: false, + normalTextStyle: const TextStyle( + fontSize: 24, + color: Colors.grey, + ), + highlightedTextStyle: const TextStyle( + fontSize: 24, + color: ColorsManager.primaryColor, + ), + spacing: 20, + itemHeight: 50, + isForce2Digits: true, + time: time, + onTimeChange: onTimeChange, + ), + const SizedBox(height: 16), + ], + ); + } + + static String formatEnumValue(EnumEffectivePeriodOptions value) { + switch (value) { + case EnumEffectivePeriodOptions.allDay: + return "All Day"; + case EnumEffectivePeriodOptions.daytime: + return "Daytime"; + case EnumEffectivePeriodOptions.night: + return "Night"; + case EnumEffectivePeriodOptions.custom: + return "Custom"; + case EnumEffectivePeriodOptions.none: + return "None"; + default: + return ""; + } +} +} diff --git a/lib/features/scene/helper/functions_per_device/ac_functions.dart b/lib/features/scene/helper/functions_per_device/ac_functions.dart new file mode 100644 index 0000000..35b47bd --- /dev/null +++ b/lib/features/scene/helper/functions_per_device/ac_functions.dart @@ -0,0 +1,271 @@ +import 'package:syncrow_app/features/scene/enum/ac_values.dart'; +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class ACFunctionsHelper { + static List tabToRunAcFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Power', + code: 'switch', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsFreezing, + operationName: 'Mode', + code: 'mode', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcCooling, + description: AcValuesEnums.Cooling.name, + value: TempModes.cold.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcHeating, + description: AcValuesEnums.Heating.name, + value: TempModes.hot.name, + ), + SceneOperationalValue( + icon: Assets.assetsFanSpeed, + description: AcValuesEnums.Ventilation.name, + value: TempModes.wind.name, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsTempreture, + operationName: 'Set Temperature', + code: 'temp_set', + functionValue: functionValue, + operationDialogType: OperationDialogType.temperature, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsCelsiusDegrees, + value: 0, + description: 'COOL TO', + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsFanSpeed, + operationName: 'Fan Speed', + code: 'level', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcFanLow, + description: ValueACRange.LOW.name, + value: FanSpeeds.low.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanMiddle, + description: ValueACRange.MIDDLE.name, + value: FanSpeeds.middle.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanHigh, + description: ValueACRange.HIGH.name, + value: FanSpeeds.high.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanAuto, + description: ValueACRange.AUTO.name, + value: FanSpeeds.auto.name, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsChildLock, + operationName: 'Child Lock', + code: 'child_lock', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsSceneChildLock, + description: 'Lock', + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsSceneChildUnlock, + description: 'Unlock', + value: false, + ), + ], + ), + ]; + } + + static List automationAcFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Power', + code: 'switch', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsFreezing, + operationName: 'Mode', + code: 'mode', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcCooling, + description: AcValuesEnums.Cooling.name, + value: TempModes.cold.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcHeating, + description: AcValuesEnums.Heating.name, + value: TempModes.hot.name, + ), + SceneOperationalValue( + icon: Assets.assetsFanSpeed, + description: AcValuesEnums.Ventilation.name, + value: TempModes.wind.name, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsTempreture, + operationName: 'Set Temperature', + code: 'temp_set', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsCelsiusDegrees, + value: 0.0, + description: '°C', + minValue: 20, + maxValue: 30, + stepValue: 0.5, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsCurrentTemp, + operationName: 'Current Temperature', + code: 'temp_current', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsCelsiusDegrees, + value: 0.0, + description: '°C', + minValue: -9.9, + maxValue: 99.9, + stepValue: 0.1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsFanSpeed, + operationName: 'Fan Speed', + code: 'level', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcFanLow, + description: ValueACRange.LOW.name, + value: FanSpeeds.low.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanMiddle, + description: ValueACRange.MIDDLE.name, + value: FanSpeeds.middle.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanHigh, + description: ValueACRange.HIGH.name, + value: FanSpeeds.high.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanAuto, + description: ValueACRange.AUTO.name, + value: FanSpeeds.auto.name, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsChildLock, + operationName: 'Child Lock', + code: 'child_lock', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsSceneChildLock, + description: 'Lock', + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsSceneChildUnlock, + description: 'Unlock', + value: false, + ), + ], + ), + ]; + } +} diff --git a/lib/features/scene/helper/functions_per_device/door_lock_functions.dart b/lib/features/scene/helper/functions_per_device/door_lock_functions.dart new file mode 100644 index 0000000..501acb1 --- /dev/null +++ b/lib/features/scene/helper/functions_per_device/door_lock_functions.dart @@ -0,0 +1,265 @@ +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class DoorLockHelperFunctions { + /// tab to run functions + static List doorLockTapToRunFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsIconsDoorLock, + operationName: 'Set Door lock Normal Open', + functionValue: functionValue, + code: 'normal_open_switch', + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue(icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue(icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + ]; + } + + //// automation functions + static List doorLockAutomationFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsFingerprintUnlock, + operationName: 'Fingerprint Unlock', + functionValue: functionValue, + code: 'unlock_fingerprint', + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0.0, + maxValue: 999, + stepValue: 1.0, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsPasswordUnlock, + operationName: 'Password Unlock', + functionValue: functionValue, + code: 'unlock_password', + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0.0, + maxValue: 999, + stepValue: 1.0, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsCardUnlock, + operationName: 'Card Unlock', + functionValue: functionValue, + code: 'unlock_card', + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0.0, + maxValue: 999, + stepValue: 1.0, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsLockAlarm, + operationName: 'Lock Alarm', + functionValue: functionValue, + code: 'alarm_lock', + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsFingerprintUnlock, + description: "Fingerprint Mismatch", + value: 'wrong_finger', + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsRemoteUnlockReq, + operationName: 'Remote Unlock Request', + functionValue: functionValue, + code: 'unlock_request', + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 90, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsResidualElectricity, + operationName: 'Residual Electricity', + functionValue: functionValue, + code: 'residual_electricity', + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 100, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsDoubleLock, + operationName: 'Double Lock', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + code: 'reverse_lock', + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsRemoteUnlockViaApp, + operationName: 'Remote Unlock Via App', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + code: 'unlock_app', + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 999, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsHijackAlarm, + operationName: 'Hijack Alarm', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + code: 'hijack', + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsDoorlockNormalOpen, + operationName: 'Set Door Lock Normal Open', + functionValue: functionValue, + code: 'normal_open_switch', + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsDoorlockNormalOpen, + operationName: 'Doorbell', + functionValue: functionValue, + code: 'doorbell', + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsTempPasswordUnlock, + operationName: 'Temporary Password Unlock', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + code: 'unlock_temporary', + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 999, + stepValue: 1, + ), + ], + ), + ]; + } +} diff --git a/lib/features/scene/helper/functions_per_device/gateway_functions.dart b/lib/features/scene/helper/functions_per_device/gateway_functions.dart new file mode 100644 index 0000000..7a356a7 --- /dev/null +++ b/lib/features/scene/helper/functions_per_device/gateway_functions.dart @@ -0,0 +1,62 @@ +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class GatewayHelperFunctions { + static List tabToRunGatewayFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsSwitchAlarmSound, + operationName: 'Switch Alarm Sound', + code: 'switch_alarm_sound', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsMasterState, + operationName: 'Master State', + code: 'master_state', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "Alarm", + value: 'alarm', + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "Normal", + value: 'normal', + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsFactoryReset, + operationName: 'Factory Reset', + code: 'factory_reset', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsSceneRefresh, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsResetOff, description: "OFF", value: false), + ], + ), + ]; + } +} diff --git a/lib/features/scene/helper/functions_per_device/human_presence_functions.dart b/lib/features/scene/helper/functions_per_device/human_presence_functions.dart new file mode 100644 index 0000000..d6f6070 --- /dev/null +++ b/lib/features/scene/helper/functions_per_device/human_presence_functions.dart @@ -0,0 +1,260 @@ +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class HumanPresenceHelperFunctions { + static List tabToRunHumanPresenceFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsFarDetection, + operationName: 'Far Detection', + code: 'far_detection', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 75, + description: '75cm', + iconValue: '75', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 150, + description: '150cm', + iconValue: '150', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 225, + description: '225cm', + iconValue: '225', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 300, + description: '300cm', + iconValue: '300', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 375, + description: '375cm', + iconValue: '375', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 450, + description: '450cm', + iconValue: '450', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 525, + description: '525cm', + iconValue: '525', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 600, + description: '600cm', + iconValue: '600', + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsMotionDetection, + operationName: 'Motion Detection Sensitivity', + code: 'motion_sensitivity_value', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 1, + description: 1.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 2, + description: 2.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 3, + description: 3.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 4, + description: 4.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 5, + description: 5.toString(), + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsMotionlessDetection, + operationName: 'Motionless Detection Sensitivity', + code: 'motionless_sensitivity', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + iconValue: '1', + icon: Assets.assetsFarDetectionFunction, + value: 1, + description: '1', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 2, + description: '2', + iconValue: '2', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 3, + description: '3', + iconValue: '3', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 4, + description: '4', + iconValue: '4', + ), + SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: 5, + description: '5', + iconValue: '5', + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsIndicator, + operationName: 'Indicator', + code: 'indicator', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + ]; + } + + static List automationHumanPresenceFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsPresenceState, + operationName: 'Presence State', + code: 'presence_state', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsIconsPresenceSensorAssetsEmpty, + value: 'none', + description: 'None', + ), + SceneOperationalValue( + icon: Assets.assetsPresence, + value: 'presence', + description: 'Presence', + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsIconsPresenceSensorAssetsDistance, + operationName: 'Current Distance', + code: 'dis_current', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + value: 0.0, + description: "CM", + minValue: 1, + maxValue: 600, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsIconsPresenceSensorAssetsIlluminanceValue, + operationName: 'Illuminance Value', + code: 'illuminance_value', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + value: 0.0, + description: "Lux", + minValue: 0, + maxValue: 10000, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsIndicator, + operationName: 'Indicator', + code: 'indicator', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsIconsPresenceSensorAssetsTime, + operationName: 'Presence Time', + code: 'presence_time', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + value: 0.0, + minValue: 0.0, + maxValue: 65535, + stepValue: 1, + description: 'min'), + ], + ), + ]; + } +} diff --git a/lib/features/scene/helper/functions_per_device/presence_sensor.dart b/lib/features/scene/helper/functions_per_device/presence_sensor.dart new file mode 100644 index 0000000..25354e6 --- /dev/null +++ b/lib/features/scene/helper/functions_per_device/presence_sensor.dart @@ -0,0 +1,139 @@ +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class PresenceSensorHelperFunctions { + static List tabToRunPresenceSensorFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsSensitivityFunction, + operationName: 'Sensitivity', + code: 'sensitivity', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 1, + description: 1.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 2, + description: 2.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 3, + description: 3.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 4, + description: 4.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 5, + description: 5.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 6, + description: 6.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 7, + description: 7.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 8, + description: 8.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 9, + description: 9.toString(), + ), + SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: 10, + description: 10.toString(), + ), + ], + ), + ]; + } + + static List automationPresenceSensorFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsIconsSensors, + operationName: 'Presence Status', + code: 'presence_state', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsIconsPresenceSensorAssetsEmpty, + value: 'none', + description: 'None', + ), + // SceneOperationalValue( + // icon: Assets.assetsPresence, + // value: 'presence', + // description: 'Presence', + // ), + // SceneOperationalValue( + // icon: Assets.assetsMotion, + // value: 'motion', + // description: 'Motion', + // ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsSensitivityFunction, + operationName: 'Sensitivity', + code: 'sensitivity', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 10, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsSelfTestResult, + operationName: 'Self-Test Result', + code: 'checking_result', + functionValue: functionValue, + operationDialogType: OperationDialogType.listOfOptions, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsSelfTestResult, + value: 'check_success', + description: 'Self Testing Success', + ), + ], + ), + ]; + } +} diff --git a/lib/features/scene/helper/functions_per_device/three_gang_functions.dart b/lib/features/scene/helper/functions_per_device/three_gang_functions.dart new file mode 100644 index 0000000..88eaef3 --- /dev/null +++ b/lib/features/scene/helper/functions_per_device/three_gang_functions.dart @@ -0,0 +1,200 @@ +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class ThreeGangHelperFunctions { + static List threeGangHelperFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Light 1 Switch', + code: 'switch_1', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Light 2 Switch', + code: 'switch_2', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Light 3 Switch', + code: 'switch_3', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsLightCountdown, + operationName: 'Light 1 CountDown', + code: 'countdown_1', + functionValue: functionValue, + operationDialogType: OperationDialogType.countdown, + operationalValues: [ + SceneOperationalValue(icon: '', value: 0), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsLightCountdown, + operationName: 'Light 2 CountDown', + code: 'countdown_2', + functionValue: functionValue, + operationDialogType: OperationDialogType.countdown, + operationalValues: [ + SceneOperationalValue(icon: '', value: 0), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsLightCountdown, + operationName: 'Light 3 CountDown', + code: 'countdown_3', + functionValue: functionValue, + operationDialogType: OperationDialogType.countdown, + operationalValues: [ + SceneOperationalValue(icon: '', value: 0), + ], + ), + ]; + } + + static List threeGangAutomationFunctions( + String deviceId, String deviceName, functionValue) { + return [ + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Light 1 Switch', + code: 'switch_1', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Light 2 Switch', + code: 'switch_2', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsAcPower, + operationName: 'Light 3 Switch', + code: 'switch_3', + functionValue: functionValue, + operationDialogType: OperationDialogType.onOff, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsLightCountdown, + operationName: 'Light 1 CountDown', + code: 'countdown_1', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsLightCountdown, + operationName: 'Light 2 CountDown', + code: 'countdown_2', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ], + ), + SceneStaticFunction( + deviceId: deviceId, + deviceName: deviceName, + icon: Assets.assetsLightCountdown, + operationName: 'Light 3 CountDown', + code: 'countdown_3', + functionValue: functionValue, + operationDialogType: OperationDialogType.integerSteps, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ], + ), + ]; + } +} diff --git a/lib/features/scene/helper/scene_logic_helper.dart b/lib/features/scene/helper/scene_logic_helper.dart new file mode 100644 index 0000000..3219ba2 --- /dev/null +++ b/lib/features/scene/helper/scene_logic_helper.dart @@ -0,0 +1,217 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/create_automation_model.dart'; +import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_slider_steps.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +mixin SceneLogicHelper { + bool isOnlyDelayOrDelayLast(List tasks) { + final lastTask = tasks.isNotEmpty ? tasks.last : null; + + return tasks.isNotEmpty + ? tasks.every((task) => task.code == 'delay') || + lastTask!.code == 'delay' || + lastTask.deviceId == 'delay' + : false; + } + + void handleSaveButtonPress( + BuildContext context, { + required bool updateScene, + required String sceneId, + required bool isAutomation, + required TextEditingController sceneName, + required List actions, + required List conditions, + }) { + final sceneBloc = context.read(); + final effectiveTime = sceneBloc.effectiveTime; + + if (isOnlyDelayOrDelayLast(actions)) { + context.showCustomSnackbar( + message: 'A single delay or delay-last operations are NOT allowed.', + icon: const Icon( + Icons.error, + color: Colors.red, + ), + ); + return; + } + + if (isAutomation == true && effectiveTime?.loops == '0000000') { + context.showCustomSnackbar( + message: 'At least one day in Effective Period must be selected!', + icon: const Icon( + Icons.error, + color: Colors.red, + ), + ); + return; + } + + if (isAutomation) { + final createAutomationModel = CreateAutomationModel( + unitUuid: HomeCubit.getInstance().selectedSpace!.id ?? '', + automationName: sceneName.text, + decisionExpr: sceneBloc.conditionRule, + effectiveTime: sceneBloc.effectiveTime ?? + EffectiveTime(start: '00:00', end: '23:59', loops: '1111111'), + conditions: List.generate( + conditions.length, + (index) { + final task = conditions[index]; + return CreateCondition( + code: index + 1, + entityId: task.deviceId, + entityType: 'device_report', + expr: ConditionExpr( + statusCode: task.code, + comparator: task.comparator ?? '==', + statusValue: task.functionValue, + ), + ); + }, + ), + actions: [ + ...List.generate( + actions.length, + (index) { + final task = actions[index]; + if (task.deviceId == 'delay') { + return CreateSceneAction( + entityId: actions[index].deviceId, + actionExecutor: 'delay', + executorProperty: CreateSceneExecutorProperty( + functionCode: '', + functionValue: '', + delaySeconds: task.functionValue ?? 1, + ), + ); + } + if (task.code == CreateSceneEnum.smartSceneSelect.name) { + return CreateSceneAction( + entityId: actions[index].deviceId, + actionExecutor: actions[index].functionValue, + executorProperty: null); + } + return CreateSceneAction( + entityId: task.deviceId, + actionExecutor: 'device_issue', + executorProperty: CreateSceneExecutorProperty( + functionCode: task.code, + functionValue: task.functionValue, + delaySeconds: 0, + ), + ); + }, + ), + ], + ); + sceneBloc.add(CreateSceneWithTasksEvent( + createSceneModel: null, + updateScene: updateScene, + sceneId: sceneId, + createAutomationModel: createAutomationModel, + )); + } else { + final createSceneModel = CreateSceneModel( + unitUuid: HomeCubit.getInstance().selectedSpace!.id ?? '', + sceneName: sceneName.text, + decisionExpr: 'and', + actions: [ + ...List.generate( + actions.length, + (index) { + final task = actions[index]; + if (task.deviceId == 'delay') { + return CreateSceneAction( + entityId: actions[index].deviceId, + actionExecutor: 'delay', + executorProperty: CreateSceneExecutorProperty( + functionCode: '', + functionValue: '', + delaySeconds: task.functionValue, + ), + ); + } + if (task.code == CreateSceneEnum.smartSceneSelect.name) { + return CreateSceneAction( + entityId: actions[index].deviceId, + actionExecutor: actions[index].functionValue, + executorProperty: null); + } + return CreateSceneAction( + entityId: task.deviceId, + actionExecutor: 'device_issue', + executorProperty: CreateSceneExecutorProperty( + functionCode: task.code, + functionValue: task.functionValue, + delaySeconds: 0, + ), + ); + }, + ), + ], + ); + sceneBloc.add(CreateSceneWithTasksEvent( + createSceneModel: createSceneModel, + createAutomationModel: null, + updateScene: updateScene, + sceneId: sceneId, + )); + } + } + + Widget getTheCorrectDialogBody( + SceneStaticFunction taskItem, dynamic functionValue, + {required bool isAutomation}) { + if (taskItem.operationDialogType == OperationDialogType.temperature) { + return AlertDialogTemperatureBody( + taskItem: taskItem, + functionValue: functionValue ?? taskItem.functionValue, + ); + } else if ((taskItem.operationDialogType == + OperationDialogType.countdown) || + (taskItem.operationDialogType == OperationDialogType.delay)) { + return AlertDialogCountdown( + durationValue: taskItem.functionValue ?? 0, + functionValue: functionValue ?? taskItem.functionValue, + function: taskItem, + ); + } else if (taskItem.operationDialogType == + OperationDialogType.integerSteps) { + return AlertDialogSliderSteps( + taskItem: taskItem, + functionValue: functionValue ?? taskItem.functionValue, + isAutomation: isAutomation, + ); + } + + return AlertDialogFunctionsOperationsBody( + taskItem: taskItem, + functionValue: functionValue ?? taskItem.functionValue, + isAutomation: isAutomation, + ); + } + + String getTaskDescription(SceneStaticFunction taskItem) { + if (taskItem.code == CreateSceneEnum.smartSceneSelect.name) { + if (taskItem.operationName == 'automation') { + return 'Automation: '; + } else { + return 'Tab-To-Run: '; + } + } else { + return "${taskItem.operationName}: "; + } + } +} diff --git a/lib/features/scene/helper/scene_operations_data_helper.dart b/lib/features/scene/helper/scene_operations_data_helper.dart new file mode 100644 index 0000000..53f760e --- /dev/null +++ b/lib/features/scene/helper/scene_operations_data_helper.dart @@ -0,0 +1,1233 @@ +import 'package:syncrow_app/features/devices/model/function_model.dart'; +import 'package:syncrow_app/features/scene/enum/ac_values.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/helper/functions_per_device/ac_functions.dart'; +import 'package:syncrow_app/features/scene/helper/functions_per_device/door_lock_functions.dart'; +import 'package:syncrow_app/features/scene/helper/functions_per_device/gateway_functions.dart'; +import 'package:syncrow_app/features/scene/helper/functions_per_device/human_presence_functions.dart'; +import 'package:syncrow_app/features/scene/helper/functions_per_device/presence_sensor.dart'; +import 'package:syncrow_app/features/scene/helper/functions_per_device/three_gang_functions.dart'; +import 'package:syncrow_app/features/scene/model/scene_details_model.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +mixin SceneOperationsDataHelper { + final Map, String, String, dynamic, bool)> _functionMap = + { + DeviceType.LightBulb: lightBulbFunctions, + DeviceType.CeilingSensor: ceilingSensorFunctions, + DeviceType.WallSensor: wallSensorFunctions, + DeviceType.AC: acFunctions, + DeviceType.DoorLock: doorLockFunctions, + DeviceType.Curtain: curtainFunctions, + DeviceType.ThreeGang: threeGangFunctions, + DeviceType.Gateway: gatewayFunctions, + }; + + final Map _titleMap = { + DeviceType.LightBulb: 'Light Bulb Functions', + DeviceType.CeilingSensor: 'Presence Sensor Functions', + DeviceType.WallSensor: 'Human Presence Sensor Functions', + DeviceType.AC: 'AC Functions', + DeviceType.DoorLock: 'Door Lock Functions', + DeviceType.Curtain: 'Curtain Functions', + DeviceType.ThreeGang: '3G Light Switch Functions', + DeviceType.Gateway: 'Gateway Functions', + }; + + List getFunctionsWithIcons({ + DeviceType? type, + required List functions, + required String deviceId, + required String deviceName, + required bool isAutomation, + }) { + final functionValue = null; + return _functionMap[type]?.call(functions, deviceId, deviceName, functionValue, isAutomation) ?? + lightBulbFunctions(functions, deviceId, deviceName, functionValue, isAutomation); + } + + String getTitle({DeviceType? type}) { + return _titleMap[type] ?? ''; + } + + static List ceilingSensorFunctions(List functions, + String deviceId, String deviceName, dynamic functionValue, bool isAutomation) { + if (isAutomation) { + return PresenceSensorHelperFunctions.automationPresenceSensorFunctions( + deviceId, deviceName, functionValue); + } + return PresenceSensorHelperFunctions.tabToRunPresenceSensorFunctions( + deviceId, deviceName, functionValue); + } + + static List curtainFunctions(List functions, String deviceId, + String deviceName, dynamic functionValue, bool isAutomation) { + return []; + } + + static List doorLockFunctions(List functions, String deviceId, + String deviceName, dynamic functionValue, bool isAutomation) { + if (isAutomation) { + return DoorLockHelperFunctions.doorLockAutomationFunctions( + deviceId, deviceName, functionValue); + } + return DoorLockHelperFunctions.doorLockTapToRunFunctions(deviceId, deviceName, functionValue); + } + + static List wallSensorFunctions(List functions, + String deviceId, String deviceName, dynamic functionValue, bool isAutomation) { + if (isAutomation) { + return HumanPresenceHelperFunctions.automationHumanPresenceFunctions( + deviceId, deviceName, functionValue); + } + return HumanPresenceHelperFunctions.tabToRunHumanPresenceFunctions( + deviceId, deviceName, functionValue); + } + + static List lightBulbFunctions(List functions, + String deviceId, String deviceName, dynamic functionValue, bool isAutomation) { + return []; + } + + static List gatewayFunctions(List functions, String deviceId, + String deviceName, dynamic functionValue, bool isAutomation) { + return GatewayHelperFunctions.tabToRunGatewayFunctions(deviceId, deviceName, functionValue); + } + + static List threeGangFunctions(List functions, + String deviceId, String deviceName, dynamic functionValue, bool isAutomation) { + if (isAutomation) { + return ThreeGangHelperFunctions.threeGangAutomationFunctions( + deviceId, deviceName, functionValue); + } + return ThreeGangHelperFunctions.threeGangHelperFunctions(deviceId, deviceName, functionValue); + } + + static List acFunctions(List functions, String deviceId, + String deviceName, dynamic functionValue, bool isAutomation) { + if (isAutomation) { + return ACFunctionsHelper.automationAcFunctions(deviceId, deviceName, functionValue); + } + return ACFunctionsHelper.tabToRunAcFunctions(deviceId, deviceName, functionValue); + } + + List getTaskListFunctionsFromApi({ + required List actions, + required bool isAutomation, + List? conditions, + }) { + List functions = []; + + // Handle actions + for (var action in actions) { + if (action.entityId == 'delay') { + functions.add( + SceneStaticFunction( + deviceId: action.entityId, + deviceName: 'delay', + deviceIcon: Assets.delay, + icon: Assets.delay, + operationName: 'delay', + operationDialogType: OperationDialogType.delay, + functionValue: action.executorProperty?.delaySeconds, + code: '', + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: action.executorProperty?.delaySeconds, + ), + ], + ), + ); + } else if (action.name != null && action.type != null) { + // Handle smart scenes + functions.add( + SceneStaticFunction( + deviceId: action.entityId, + deviceName: action.name.toString(), + deviceIcon: action.type == 'automation' ? Assets.player : Assets.handClickIcon, + icon: action.type == 'automation' ? Assets.player : Assets.handClickIcon, + operationName: action.type.toString(), + operationDialogType: OperationDialogType.onOff, + functionValue: action.actionExecutor, + code: CreateSceneEnum.smartSceneSelect.name, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "Enable", + value: 'rule_enable', + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "Disable", + value: 'rule_disable', + ), + ], + ), + ); + } else { + functions.add(_mapExecutorPropertyToSceneFunction(action, isAutomation)); + } + } + + // Handle conditions + if (conditions != null) { + for (var condition in conditions) { + // Create a dummy Action from Condition to reuse _mapExecutorPropertyToSceneFunction + Action dummyAction = Action( + actionExecutor: 'device_report', + entityId: condition.entityId, + executorProperty: ExecutorProperty( + functionCode: condition.expr.statusCode, + functionValue: condition.expr.statusValue, + ), + ); + var conditionFunction = _mapExecutorPropertyToSceneFunction( + dummyAction, + isAutomation, + comparator: condition.expr.comparator, + ); + functions.add(conditionFunction); + } + } + + return functions; + } + + SceneStaticFunction _mapExecutorPropertyToSceneFunction( + Action action, + bool isAutomation, { + String? comparator, + String? uniqueCustomId, + }) { + final executorProperty = action.executorProperty; + + final Map functionMap = { + 'sensitivity': _createSensitivityFunction, + 'normal_open_switch': _createNormalOpenSwitchFunction, + 'unlock_fingerprint': _createUnlockFingerprintFunction, + 'unlock_password': _createUnlockPasswordFunction, + 'unlock_card': _createUnlockCardFunction, + 'alarm_lock': _createAlarmLockFunction, + 'unlock_request': _createUnlockRequestFunction, + 'residual_electricity': _createResidualElectricityFunction, + 'reverse_lock': _createReverseLockFunction, + 'unlock_app': _createUnlockAppFunction, + 'hijack': _createHijackFunction, + 'doorbell': _createDoorbellFunction, + 'unlock_temporary': _createUnlockTemporaryFunction, + 'far_detection': _createFarDetectionFunction, + 'motion_sensitivity_value': _createMotionSensitivityFunction, + 'motionless_sensitivity': _createMotionlessSensitivityFunction, + 'indicator': _createIndicatorFunction, + 'presence_time': _createPresenceTimeFunction, + 'presence_state': _createPresenceStateFunction, + 'dis_current': _createDisCurrentFunction, + 'illuminance_value': _createIlluminanceValueFunction, + 'checking_result': _createCheckingResultFunction, + 'switch': _createSwitchFunction, + 'temp_set': _createTempSetFunction, + 'temp_current': _createTempCurrentFunction, + 'mode': _createModeFunction, + 'level': _createLevelFunction, + 'child_lock': _createChildLockFunction, + 'switch_1': _createSwitch1Function, + 'switch_2': _createSwitch2Function, + 'switch_3': _createSwitch3Function, + 'countdown_1': _createCountdown1Function, + 'countdown_2': _createCountdown2Function, + 'countdown_3': _createCountdown3Function, + 'switch_alarm_sound': _createSwitchAlarmSoundFunction, + 'master_state': _createMasterStateFunction, + 'factory_reset': _createFactoryResetFunction, + }; + + final functionCode = executorProperty?.functionCode ?? ''; + final createFunction = functionMap[functionCode]; + if (createFunction != null) { + return createFunction(action, isAutomation, comparator, uniqueCustomId); + } else { + throw ArgumentError('Unsupported function code: $functionCode'); + } + } + + SceneStaticFunction _createSceneFunction( + Action action, + String deviceName, + String icon, + String operationName, + OperationDialogType operationDialogType, + List operationalValues, + bool isAutomation, [ + String? comparator, + String? uniqueCustomId, + ]) { + final functionValue = action.executorProperty?.functionValue; + return SceneStaticFunction( + uniqueCustomId: uniqueCustomId, + deviceId: action.entityId, + deviceName: deviceName, + deviceIcon: icon, + icon: icon, + operationName: operationName, + functionValue: functionValue, + code: action.executorProperty?.functionCode ?? '', + operationDialogType: operationDialogType, + operationalValues: operationalValues, + comparator: comparator ?? '==', + ); + } + + SceneStaticFunction _createSensitivityFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Presence Sensor', + Assets.assetsIconsSensors, + 'Sensitivity', + isAutomation ? OperationDialogType.integerSteps : OperationDialogType.listOfOptions, + isAutomation ? _createIntegerStepsOptions() : _createSensitivityOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createNormalOpenSwitchFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Set Door lock Normal Open', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + List _createIntegerStepsOptions() { + return [ + SceneOperationalValue( + icon: '', + value: 0.0, + description: "CM", + minValue: 1, + maxValue: 600, + stepValue: 1, + ), + ]; + } + + List _createSensitivityOptions() { + return List.generate( + 10, + (index) => SceneOperationalValue( + icon: Assets.assetsSensitivityOperationIcon, + value: index + 1, + description: (index + 1).toString(), + ), + ); + } + + List _createOnOffOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; + } + + SceneStaticFunction _createUnlockFingerprintFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Fingerprint Unlock', + OperationDialogType.integerSteps, + _createFingerprintUnlockOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createUnlockPasswordFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Password Unlock', + OperationDialogType.integerSteps, + _createPasswordUnlockOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createUnlockCardFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Card Unlock', + OperationDialogType.integerSteps, + _createCardUnlockOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createAlarmLockFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Lock Alarm', + OperationDialogType.listOfOptions, + _createLockAlarmOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createUnlockRequestFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Remote Unlock Request', + OperationDialogType.integerSteps, + _createUnlockRequestOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createResidualElectricityFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Residual Electricity', + OperationDialogType.integerSteps, + _createResidualElectricityOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createReverseLockFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Double Lock', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createUnlockAppFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Remote Unlock Via App', + OperationDialogType.integerSteps, + _createUnlockAppOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createHijackFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Hijack Alarm', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createDoorbellFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Doorbell', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createUnlockTemporaryFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'WIFI LOCK PRO', + Assets.assetsIconsDoorLock, + 'Temporary Password Unlock', + OperationDialogType.integerSteps, + _createTemporaryPasswordUnlockOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createFarDetectionFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Far Detection', + OperationDialogType.listOfOptions, + _createFarDetectionOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createMotionSensitivityFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Motion Detection Sensitivity', + OperationDialogType.listOfOptions, + _createSensitivityOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createMotionlessSensitivityFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Motionless Detection Sensitivity', + OperationDialogType.listOfOptions, + _createSensitivityOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createIndicatorFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Indicator', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createPresenceTimeFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Nobody Time', + OperationDialogType.countdown, + _createCountdownOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createPresenceStateFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Presence State', + OperationDialogType.listOfOptions, + _createPresenceStateOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createDisCurrentFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Current Distance', + isAutomation ? OperationDialogType.integerSteps : OperationDialogType.countdown, + _createCurrentDistanceOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createIlluminanceValueFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Illuminance Value', + OperationDialogType.integerSteps, + _createIlluminanceValueOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createCheckingResultFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Human Presence Sensor', + Assets.assetsIconsSensors, + 'Self-Test Result', + OperationDialogType.listOfOptions, + _createSelfTestResultOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createSwitchFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Smart AC Thermostat - Grey - Model A', + Assets.assetsIconsAC, + 'Power', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createTempSetFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Smart AC Thermostat - Grey - Model A', + Assets.assetsIconsAC, + 'Set Temperature', + isAutomation ? OperationDialogType.integerSteps : OperationDialogType.temperature, + isAutomation ? _createAutomationTemperatureOptions() : _createTemperatureOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createTempCurrentFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Smart AC Thermostat - Grey - Model A', + Assets.assetsIconsAC, + 'Current Temperature', + OperationDialogType.integerSteps, + _createCurrentTemperatureOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createModeFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Smart AC Thermostat - Grey - Model A', + Assets.assetsIconsAC, + 'Mode', + OperationDialogType.listOfOptions, + _createAcModeOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createLevelFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Smart AC Thermostat - Grey - Model A', + Assets.assetsIconsAC, + 'Fan Speed', + OperationDialogType.listOfOptions, + _createFanSpeedOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createChildLockFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Smart AC Thermostat - Grey - Model A', + Assets.assetsIconsAC, + 'Child Lock', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createSwitch1Function( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + '3 Gang Button Switch L-L', + Assets.assetsIcons3GangSwitch, + 'Light 1 Switch', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createSwitch2Function( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + '3 Gang Button Switch L-L', + Assets.assetsIcons3GangSwitch, + 'Light 2 Switch', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createSwitch3Function( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + '3 Gang Button Switch L-L', + Assets.assetsIcons3GangSwitch, + 'Light 3 Switch', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createCountdown1Function( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + '3 Gang Button Switch L-L', + Assets.assetsIcons3GangSwitch, + 'Light 1 CountDown', + isAutomation ? OperationDialogType.integerSteps : OperationDialogType.countdown, + isAutomation ? _createAutomationCountDownOptions() : _createCountdownOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createCountdown2Function( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + '3 Gang Button Switch L-L', + Assets.assetsIcons3GangSwitch, + 'Light 2 CountDown', + isAutomation ? OperationDialogType.integerSteps : OperationDialogType.countdown, + isAutomation ? _createAutomationCountDownOptions() : _createCountdownOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createCountdown3Function( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + '3 Gang Button Switch L-L', + Assets.assetsIcons3GangSwitch, + 'Light 3 CountDown', + isAutomation ? OperationDialogType.integerSteps : OperationDialogType.countdown, + isAutomation ? _createAutomationCountDownOptions() : _createCountdownOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createSwitchAlarmSoundFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Gateway', + Assets.assetsIconsGateway, + 'Switch Alarm Sound', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createMasterStateFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Gateway', + Assets.assetsIconsGateway, + 'Master State', + OperationDialogType.listOfOptions, + _createMasterStateOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + SceneStaticFunction _createFactoryResetFunction( + Action action, bool isAutomation, String? comparator, String? uniqueCustomId) { + return _createSceneFunction( + action, + 'Gateway', + Assets.assetsIconsGateway, + 'Factory Reset', + OperationDialogType.onOff, + _createOnOffOptions(), + isAutomation, + comparator, + uniqueCustomId, + ); + } + + List _createFingerprintUnlockOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0.0, + maxValue: 999, + stepValue: 1.0, + ), + ]; + } + + List _createPasswordUnlockOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0.0, + maxValue: 999, + stepValue: 1.0, + ), + ]; + } + + List _createCardUnlockOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0.0, + maxValue: 999, + stepValue: 1.0, + ), + ]; + } + + List _createLockAlarmOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsFingerprintUnlock, + description: "Fingerprint Mismatch", + value: 'wrong_finger', + ), + ]; + } + + List _createUnlockRequestOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 90, + stepValue: 1, + ), + ]; + } + + List _createResidualElectricityOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 100, + stepValue: 1, + ), + ]; + } + + List _createUnlockAppOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 999, + stepValue: 1, + ), + ]; + } + + List _createTemporaryPasswordUnlockOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "", + value: 0.0, + minValue: 0, + maxValue: 999, + stepValue: 1, + ), + ]; + } + + List _createPresenceStateOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsIconsPresenceSensorAssetsEmpty, + value: 'none', + description: 'None', + ), + SceneOperationalValue( + icon: Assets.assetsPresence, + value: 'presence', + description: 'Presence', + ), + ]; + } + + List _createCurrentDistanceOptions() { + return [ + SceneOperationalValue( + icon: '', + value: 0.0, + description: "CM", + minValue: 1, + maxValue: 600, + stepValue: 1, + ), + ]; + } + + List _createIlluminanceValueOptions() { + return [ + SceneOperationalValue( + icon: '', + value: 0.0, + description: "Lux", + minValue: 0, + maxValue: 10000, + stepValue: 1, + ), + ]; + } + + List _createSelfTestResultOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsSelfTestResult, + value: 'check_success', + description: 'Self Testing Success', + ), + ]; + } + + List _createAutomationTemperatureOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsCelsiusDegrees, + value: 0.0, + description: '°C', + minValue: 20, + maxValue: 30, + stepValue: 0.5, + ), + ]; + } + + List _createAutomationCountDownOptions() { + return [ + SceneOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; + } + + List _createFarDetectionOptions() { + final distances = [75, 150, 225, 300, 375, 450, 525, 600]; + return distances + .map( + (distance) => SceneOperationalValue( + icon: Assets.assetsFarDetectionFunction, + value: distance, + description: '${distance}cm', + iconValue: distance.toString(), + ), + ) + .toList(); + } + + List _createTemperatureOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsCelsiusDegrees, + value: 0, + description: 'COOL TO', + ), + ]; + } + + List _createMasterStateOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "Alarm", + value: 'alarm', + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "Normal", + value: 'normal', + ), + ]; + } + + List _createAcModeOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsAcCooling, + description: AcValuesEnums.Cooling.name, + value: TempModes.cold.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcHeating, + description: AcValuesEnums.Heating.name, + value: TempModes.hot.name, + ), + SceneOperationalValue( + icon: Assets.assetsFanSpeed, + description: AcValuesEnums.Ventilation.name, + value: TempModes.wind.name, + ), + ]; + } + + List _createFanSpeedOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsAcFanLow, + description: ValueACRange.LOW.name, + value: FanSpeeds.low.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanMiddle, + description: ValueACRange.MIDDLE.name, + value: FanSpeeds.middle.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanHigh, + description: ValueACRange.HIGH.name, + value: FanSpeeds.high.name, + ), + SceneOperationalValue( + icon: Assets.assetsAcFanAuto, + description: ValueACRange.AUTO.name, + value: FanSpeeds.auto.name, + ), + ]; + } + + List _createCountdownOptions() { + return [ + SceneOperationalValue( + icon: '', + value: 0, + ), + ]; + } + + List getOperationsForOneFunction({ + required String deviceId, + required SceneStaticFunction taskItem, + required bool isAutomation, + }) { + if (deviceId.contains('delay')) { + return [ + SceneStaticFunction( + uniqueCustomId: taskItem.uniqueCustomId, + deviceId: taskItem.deviceId, + deviceName: 'delay', + deviceIcon: Assets.delay, + icon: Assets.delay, + operationName: 'delay', + functionValue: taskItem.functionValue, + code: '', + operationDialogType: OperationDialogType.delay, + operationalValues: [ + SceneOperationalValue( + icon: '', + description: "", + value: 0, + ), + ], + ), + ]; + } + if (taskItem.code == CreateSceneEnum.smartSceneSelect.name) { + return [ + SceneStaticFunction( + uniqueCustomId: taskItem.uniqueCustomId, + deviceId: taskItem.deviceId, + deviceName: taskItem.deviceName.toString(), + deviceIcon: taskItem.operationName == 'automation' ? Assets.player : Assets.handClickIcon, + icon: taskItem.operationName == 'automation' ? Assets.player : Assets.handClickIcon, + operationName: taskItem.operationName, + operationDialogType: OperationDialogType.onOff, + functionValue: taskItem.functionValue == 'rule_enable' ? true : false, + code: CreateSceneEnum.smartSceneSelect.name, + operationalValues: [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "Enable", + value: 'rule_enable', + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "Disable", + value: 'rule_disable', + ), + ], + ), + ]; + } + return [ + _mapExecutorPropertyToSceneFunction( + Action( + entityId: deviceId, + executorProperty: ExecutorProperty( + functionCode: taskItem.code, + functionValue: taskItem.functionValue, + ), + actionExecutor: '', + ), + isAutomation, + comparator: taskItem.comparator, + uniqueCustomId: taskItem.uniqueCustomId, + ), + ]; + } + + List _createCurrentTemperatureOptions() { + return [ + SceneOperationalValue( + icon: Assets.assetsCelsiusDegrees, + value: 0.0, + description: '°C', + minValue: -9.9, + maxValue: 99.9, + stepValue: 0.1, + ), + ]; + } +} diff --git a/lib/features/scene/model/create_automation_model.dart b/lib/features/scene/model/create_automation_model.dart new file mode 100644 index 0000000..a349dc2 --- /dev/null +++ b/lib/features/scene/model/create_automation_model.dart @@ -0,0 +1,172 @@ +import 'dart:convert'; + +import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; + +class CreateAutomationModel { + String unitUuid; + String automationName; + String decisionExpr; + EffectiveTime effectiveTime; + List conditions; + List actions; + + CreateAutomationModel({ + required this.unitUuid, + required this.automationName, + required this.decisionExpr, + required this.effectiveTime, + required this.conditions, + required this.actions, + }); + + CreateAutomationModel copyWith({ + String? unitUuid, + String? automationName, + String? decisionExpr, + EffectiveTime? effectiveTime, + List? conditions, + List? actions, + }) { + return CreateAutomationModel( + unitUuid: unitUuid ?? this.unitUuid, + automationName: automationName ?? this.automationName, + decisionExpr: decisionExpr ?? this.decisionExpr, + effectiveTime: effectiveTime ?? this.effectiveTime, + conditions: conditions ?? this.conditions, + actions: actions ?? this.actions, + ); + } + + Map toMap([String? automationId]) { + return { + if (automationId == null) 'unitUuid': unitUuid, + 'automationName': automationName, + 'decisionExpr': decisionExpr, + 'effectiveTime': effectiveTime.toMap(), + 'conditions': conditions.map((x) => x.toMap()).toList(), + 'actions': actions.map((x) => x.toMap()).toList(), + }; + } + + factory CreateAutomationModel.fromMap(Map map) { + return CreateAutomationModel( + unitUuid: map['unitUuid'] ?? '', + automationName: map['automationName'] ?? '', + decisionExpr: map['decisionExpr'] ?? '', + effectiveTime: EffectiveTime.fromMap(map['effectiveTime']), + conditions: List.from( + map['conditions']?.map((x) => CreateCondition.fromMap(x))), + actions: List.from( + map['actions']?.map((x) => CreateSceneAction.fromMap(x))), + ); + } + + String toJson([String? automationId]) => json.encode(toMap(automationId)); + + factory CreateAutomationModel.fromJson(String source) => + CreateAutomationModel.fromMap(json.decode(source)); + + @override + String toString() { + return 'CreateAutomationModel(unitUuid: $unitUuid, automationName: $automationName, decisionExpr: $decisionExpr, effectiveTime: $effectiveTime, conditions: $conditions, actions: $actions)'; + } +} + +class EffectiveTime { + String start; + String end; + String loops; + + EffectiveTime({ + required this.start, + required this.end, + required this.loops, + }); + + Map toMap() { + return { + 'start': start, + 'end': end, + 'loops': loops, + }; + } + + factory EffectiveTime.fromMap(Map map) { + return EffectiveTime( + start: map['start'] ?? '', + end: map['end'] ?? '', + loops: map['loops'] ?? '', + ); + } + + @override + String toString() => 'EffectiveTime(start: $start, end: $end, loops: $loops)'; +} + +class CreateCondition { + int code; + String entityId; + String entityType; + ConditionExpr expr; + + CreateCondition({ + required this.code, + required this.entityId, + required this.entityType, + required this.expr, + }); + + Map toMap() { + return { + 'code': code, + 'entityId': entityId, + 'entityType': entityType, + 'expr': expr.toMap(), + }; + } + + factory CreateCondition.fromMap(Map map) { + return CreateCondition( + code: map['code'] ?? 0, + entityId: map['entityId'] ?? '', + entityType: map['entityType'] ?? '', + expr: ConditionExpr.fromMap(map['expr']), + ); + } + + @override + String toString() => + 'CreateCondition(code: $code, entityId: $entityId, entityType: $entityType, expr: $expr)'; +} + +class ConditionExpr { + String statusCode; + String comparator; + dynamic statusValue; + + ConditionExpr({ + required this.statusCode, + required this.comparator, + required this.statusValue, + }); + + Map toMap() { + return { + 'statusCode': statusCode, + 'comparator': comparator, + 'statusValue': statusValue, + }; + } + + factory ConditionExpr.fromMap(Map map) { + return ConditionExpr( + statusCode: map['statusCode'] ?? '', + comparator: map['comparator'] ?? '', + statusValue: map['statusValue'], + ); + } + + @override + String toString() => + 'ConditionExpr(statusCode: $statusCode, comparator: $comparator, statusValue: $statusValue)'; +} diff --git a/lib/features/scene/model/create_scene_model.dart b/lib/features/scene/model/create_scene_model.dart new file mode 100644 index 0000000..5475b93 --- /dev/null +++ b/lib/features/scene/model/create_scene_model.dart @@ -0,0 +1,215 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +class CreateSceneModel { + String unitUuid; + String sceneName; + String decisionExpr; + List actions; + + CreateSceneModel({ + required this.unitUuid, + required this.sceneName, + required this.decisionExpr, + required this.actions, + }); + + CreateSceneModel copyWith({ + String? unitUuid, + String? sceneName, + String? decisionExpr, + List? actions, + }) { + return CreateSceneModel( + unitUuid: unitUuid ?? this.unitUuid, + sceneName: sceneName ?? this.sceneName, + decisionExpr: decisionExpr ?? this.decisionExpr, + actions: actions ?? this.actions, + ); + } + + Map toMap([String? sceneId]) { + return { + if (sceneId == null) 'unitUuid': unitUuid, + 'sceneName': sceneName, + 'decisionExpr': decisionExpr, + 'actions': actions.map((x) => x.toMap()).toList(), + }; + } + + factory CreateSceneModel.fromMap(Map map) { + return CreateSceneModel( + unitUuid: map['unitUuid'] ?? '', + sceneName: map['sceneName'] ?? '', + decisionExpr: map['decisionExpr'] ?? '', + actions: List.from( + map['actions']?.map((x) => CreateSceneAction.fromMap(x))), + ); + } + + String toJson([String? sceneId]) => json.encode(toMap(sceneId)); + + factory CreateSceneModel.fromJson(String source) => + CreateSceneModel.fromMap(json.decode(source)); + + @override + String toString() { + return 'CreateSceneModel(unitUuid: $unitUuid, sceneName: $sceneName, decisionExpr: $decisionExpr, actions: $actions)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneModel && + other.unitUuid == unitUuid && + other.sceneName == sceneName && + other.decisionExpr == decisionExpr && + listEquals(other.actions, actions); + } + + @override + int get hashCode { + return unitUuid.hashCode ^ + sceneName.hashCode ^ + decisionExpr.hashCode ^ + actions.hashCode; + } +} + +class CreateSceneAction { + String entityId; + String actionExecutor; + CreateSceneExecutorProperty? executorProperty; + + CreateSceneAction({ + required this.entityId, + required this.actionExecutor, + required this.executorProperty, + }); + + CreateSceneAction copyWith({ + String? entityId, + String? actionExecutor, + CreateSceneExecutorProperty? executorProperty, + }) { + return CreateSceneAction( + entityId: entityId ?? this.entityId, + actionExecutor: actionExecutor ?? this.actionExecutor, + executorProperty: executorProperty ?? this.executorProperty, + ); + } + + Map toMap() { + if (executorProperty != null) { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + 'executorProperty': executorProperty?.toMap(actionExecutor), + }; + } else { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + }; + } + } + + factory CreateSceneAction.fromMap(Map map) { + return CreateSceneAction( + entityId: map['entityId'] ?? '', + actionExecutor: map['actionExecutor'] ?? '', + executorProperty: + CreateSceneExecutorProperty.fromMap(map['executorProperty']), + ); + } + + String toJson() => json.encode(toMap()); + + factory CreateSceneAction.fromJson(String source) => + CreateSceneAction.fromMap(json.decode(source)); + + @override + String toString() => + 'CreateSceneAction(entityId: $entityId, actionExecutor: $actionExecutor, executorProperty: $executorProperty)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneAction && + other.entityId == entityId && + other.actionExecutor == actionExecutor && + other.executorProperty == executorProperty; + } + + @override + int get hashCode => + entityId.hashCode ^ actionExecutor.hashCode ^ executorProperty.hashCode; +} + +class CreateSceneExecutorProperty { + String functionCode; + dynamic functionValue; + int delaySeconds; + + CreateSceneExecutorProperty({ + required this.functionCode, + required this.functionValue, + required this.delaySeconds, + }); + + CreateSceneExecutorProperty copyWith({ + String? functionCode, + dynamic functionValue, + int? delaySeconds, + }) { + return CreateSceneExecutorProperty( + functionCode: functionCode ?? this.functionCode, + functionValue: functionValue ?? this.functionValue, + delaySeconds: delaySeconds ?? this.delaySeconds, + ); + } + + Map toMap(String actionExecutor) { + final map = {}; + if (functionCode.isNotEmpty) map['functionCode'] = functionCode; + if (functionValue != null) map['functionValue'] = functionValue; + if (actionExecutor == 'delay' && delaySeconds > 0) { + map['delaySeconds'] = delaySeconds; + } + return map; + } + + factory CreateSceneExecutorProperty.fromMap(Map map) { + return CreateSceneExecutorProperty( + functionCode: map['functionCode'] ?? '', + functionValue: map['functionValue'] ?? '', + delaySeconds: map['delaySeconds']?.toInt() ?? 0, + ); + } + + String toJson(String actionExecutor) => json.encode(toMap(actionExecutor)); + + factory CreateSceneExecutorProperty.fromJson(String source) => + CreateSceneExecutorProperty.fromMap(json.decode(source)); + + @override + String toString() => + 'CreateSceneExecutorProperty(functionCode: $functionCode, functionValue: $functionValue, delaySeconds: $delaySeconds)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneExecutorProperty && + other.functionCode == functionCode && + other.functionValue == functionValue && + other.delaySeconds == delaySeconds; + } + + @override + int get hashCode => + functionCode.hashCode ^ functionValue.hashCode ^ delaySeconds.hashCode; +} diff --git a/lib/features/scene/model/scene_details_model.dart b/lib/features/scene/model/scene_details_model.dart new file mode 100644 index 0000000..646b3b1 --- /dev/null +++ b/lib/features/scene/model/scene_details_model.dart @@ -0,0 +1,222 @@ +import 'dart:convert'; + +class SceneDetailsModel { + final String id; + final String name; + final String status; + final String type; + final List actions; + final List? conditions; + final String? decisionExpr; + final EffectiveTime? effectiveTime; + + SceneDetailsModel({ + required this.id, + required this.name, + required this.status, + required this.type, + required this.actions, + this.conditions, + this.decisionExpr, + this.effectiveTime, + }); + + factory SceneDetailsModel.fromRawJson(String str) => + SceneDetailsModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory SceneDetailsModel.fromJson(Map json) => + SceneDetailsModel( + id: json["id"], + name: json["name"], + status: json["status"], + type: json["type"], + actions: (json["actions"] as List) + .map((x) => Action.fromJson(x)) + .where((x) => x != null) + .toList() + .cast(), + conditions: json["conditions"] != null + ? (json["conditions"] as List) + .map((x) => Condition.fromJson(x)) + .toList() + : null, + decisionExpr: json["decisionExpr"], + effectiveTime: json["effectiveTime"] != null + ? EffectiveTime.fromJson(json["effectiveTime"]) + : null, + ); + + Map toJson() => { + "id": id, + "name": name, + "status": status, + "type": type, + "actions": List.from(actions.map((x) => x.toJson())), + "conditions": conditions != null + ? List.from(conditions!.map((x) => x.toJson())) + : null, + "decisionExpr": decisionExpr, + "effectiveTime": effectiveTime?.toJson(), + }; +} + +class Action { + final String actionExecutor; + final String entityId; + ExecutorProperty? executorProperty; + String? name; + String? type; + + Action({ + required this.actionExecutor, + required this.entityId, + this.executorProperty, + this.name, + this.type, + }); + + String toRawJson() => json.encode(toJson()); + + static Action? fromJson(Map json) { + if (json['name'] != null && json['type'] != null) { + return Action( + actionExecutor: json["actionExecutor"], + entityId: json["entityId"], + name: json['name'], + type: json['type'], + ); + } + if (json["executorProperty"] == null) { + return null; + } + + return Action( + actionExecutor: json["actionExecutor"], + entityId: json["entityId"], + executorProperty: ExecutorProperty.fromJson(json["executorProperty"]), + ); + } + + Map toJson() => { + "actionExecutor": actionExecutor, + "entityId": entityId, + "executorProperty": executorProperty?.toJson(), + }; +} + +class ExecutorProperty { + final String? functionCode; + final dynamic functionValue; + final dynamic delaySeconds; + + ExecutorProperty({ + this.functionCode, + this.functionValue, + this.delaySeconds, + }); + + factory ExecutorProperty.fromJson(Map json) => + ExecutorProperty( + functionCode: json["functionCode"] ?? '', + functionValue: json["functionValue"] ?? '', + delaySeconds: json["delaySeconds"] ?? 0, + ); + + Map toJson() => { + "functionCode": functionCode, + "functionValue": functionValue, + "delaySeconds": delaySeconds, + }; +} + +class Condition { + final int code; + final String entityId; + final String entityType; + final Expr expr; + + Condition({ + required this.code, + required this.entityId, + required this.entityType, + required this.expr, + }); + + factory Condition.fromRawJson(String str) => + Condition.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Condition.fromJson(Map json) => Condition( + code: json["code"], + entityId: json["entityId"], + entityType: json["entityType"], + expr: Expr.fromJson(json["expr"]), + ); + + Map toJson() => { + "code": code, + "entityId": entityId, + "entityType": entityType, + "expr": expr.toJson(), + }; +} + +class Expr { + final String comparator; + final String statusCode; + final dynamic statusValue; + + Expr({ + required this.comparator, + required this.statusCode, + required this.statusValue, + }); + + factory Expr.fromRawJson(String str) => Expr.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Expr.fromJson(Map json) => Expr( + comparator: json["comparator"], + statusCode: json["statusCode"], + statusValue: json["statusValue"], + ); + + Map toJson() => { + "comparator": comparator, + "statusCode": statusCode, + "statusValue": statusValue, + }; +} + +class EffectiveTime { + final String start; + final String end; + final String loops; + + EffectiveTime({ + required this.start, + required this.end, + required this.loops, + }); + + factory EffectiveTime.fromRawJson(String str) => + EffectiveTime.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory EffectiveTime.fromJson(Map json) => EffectiveTime( + start: json["start"], + end: json["end"], + loops: json["loops"], + ); + + Map toJson() => { + "start": start, + "end": end, + "loops": loops, + }; +} diff --git a/lib/features/scene/model/scene_settings_route_arguments.dart b/lib/features/scene/model/scene_settings_route_arguments.dart new file mode 100644 index 0000000..42291a1 --- /dev/null +++ b/lib/features/scene/model/scene_settings_route_arguments.dart @@ -0,0 +1,12 @@ +/// helper class for scene settings push route +class SceneSettingsRouteArguments { + final String sceneId; + final String sceneName; + final String sceneType; + + SceneSettingsRouteArguments({ + required this.sceneId, + required this.sceneName, + required this.sceneType, + }); +} diff --git a/lib/features/scene/model/scene_static_function.dart b/lib/features/scene/model/scene_static_function.dart new file mode 100644 index 0000000..8644f88 --- /dev/null +++ b/lib/features/scene/model/scene_static_function.dart @@ -0,0 +1,239 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:uuid/uuid.dart'; + +class SceneStaticFunction { + final String icon; + final String deviceName; + final String code; + final List operationalValues; + final String deviceId; + final String operationName; + final String? uniqueCustomId; + final dynamic functionValue; + final String? deviceIcon; + final OperationDialogType operationDialogType; + final String? comparator; + + SceneStaticFunction({ + required this.icon, + required this.deviceName, + required this.code, + required this.operationalValues, + required this.deviceId, + required this.operationName, + required this.functionValue, + this.deviceIcon, + required this.operationDialogType, + this.comparator, + String? uniqueCustomId, + }) : uniqueCustomId = uniqueCustomId ?? const Uuid().v4(); + + SceneStaticFunction copyWith({ + String? icon, + String? name, + String? code, + List? operationalValues, + String? deviceId, + String? operationName, + dynamic functionValue, + String? deviceIcon, + String? deviceName, + OperationDialogType? operationDialogType, + String? comparator, + String? uniqueCustomId, + }) { + return SceneStaticFunction( + icon: icon ?? this.icon, + deviceName: name ?? this.deviceName, + code: code ?? this.code, + operationalValues: operationalValues ?? this.operationalValues, + deviceId: deviceId ?? this.deviceId, + operationName: operationName ?? this.operationName, + functionValue: functionValue ?? this.functionValue, + deviceIcon: deviceIcon ?? this.deviceIcon, + operationDialogType: operationDialogType ?? this.operationDialogType, + comparator: comparator ?? this.comparator, + uniqueCustomId: uniqueCustomId ?? const Uuid().v4(), + ); + } + + Map toMap() { + return { + 'icon': icon, + 'name': deviceName, + 'code': code, + 'operationalValues': operationalValues.map((x) => x.toMap()).toList(), + 'deviceId': deviceId, + 'operationName': operationName, + 'functionValue': functionValue, + 'deviceIcon': deviceIcon, + 'operationDialogType': operationDialogType.name, + 'comparator': comparator, + 'uniqueCustomId': uniqueCustomId, + }; + } + + factory SceneStaticFunction.fromMap(Map map) { + return SceneStaticFunction( + icon: map['icon'] ?? '', + deviceName: map['name'] ?? '', + code: map['code'] ?? '', + operationalValues: List.from( + map['operationalValues']?.map((x) => SceneOperationalValue.fromMap(x)), + ), + deviceId: map['deviceId'] ?? '', + operationName: map['operationName'] ?? '', + functionValue: map['functionValue'] ?? '', + deviceIcon: map['deviceIcon'] ?? '', + operationDialogType: map['operationDialogType'] != null + ? OperationDialogType.values.byName(map['operationDialogType']) + : OperationDialogType.none, + comparator: map['comparator'], + uniqueCustomId: map['uniqueCustomId'] ?? const Uuid().v4(), + ); + } + + String toJson() => json.encode(toMap()); + + factory SceneStaticFunction.fromJson(String source) => + SceneStaticFunction.fromMap(json.decode(source)); + + @override + String toString() { + return 'SceneStaticFunction(icon: $icon, name: $deviceName, code: $code, operationalValues: $operationalValues, deviceId: $deviceId, operationName: $operationName, functionValue: $functionValue, deviceIcon: $deviceIcon, operationDialogType: $operationDialogType, comparator: $comparator, uniqueCustomId: $uniqueCustomId)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SceneStaticFunction && + other.icon == icon && + other.deviceName == deviceName && + other.code == code && + other.operationName == operationName && + other.functionValue == functionValue && + other.deviceIcon == deviceIcon && + other.comparator == comparator && + other.uniqueCustomId == uniqueCustomId && + other.operationDialogType == operationDialogType && + listEquals(other.operationalValues, operationalValues) && + other.deviceId == deviceId; + } + + @override + int get hashCode { + return icon.hashCode ^ + deviceName.hashCode ^ + code.hashCode ^ + deviceId.hashCode ^ + operationName.hashCode ^ + functionValue.hashCode ^ + deviceIcon.hashCode ^ + comparator.hashCode ^ + uniqueCustomId.hashCode ^ + operationDialogType.hashCode ^ + operationalValues.hashCode; + } +} + +class SceneOperationalValue { + final String icon; + final dynamic value; + final String? description; + final String? iconValue; + final double? minValue; + final double? maxValue; + final double? stepValue; + + SceneOperationalValue({ + required this.icon, + required this.value, + this.description, + this.iconValue, + this.minValue, + this.maxValue, + this.stepValue, + }); + + SceneOperationalValue copyWith({ + String? icon, + dynamic value, + String? description, + String? iconValue, + double? minValue, + double? maxValue, + double? stepValue, + }) { + return SceneOperationalValue( + icon: icon ?? this.icon, + value: value ?? this.value, + description: description ?? this.description, + iconValue: iconValue ?? this.iconValue, + minValue: minValue ?? this.minValue, + maxValue: maxValue ?? this.maxValue, + stepValue: stepValue ?? this.stepValue, + ); + } + + Map toMap() { + return { + 'icon': icon, + 'value': value, + 'description': description, + 'iconValue': iconValue, + 'minValue': minValue, + 'maxValue': maxValue, + 'stepValue': stepValue + }; + } + + factory SceneOperationalValue.fromMap(Map map) { + return SceneOperationalValue( + icon: map['icon'] ?? '', + value: map['value'], + description: map['description'], + iconValue: map['iconValue'] ?? '', + minValue: map['minValue'], + maxValue: map['maxValue'], + stepValue: map['stepValue'], + ); + } + + String toJson() => json.encode(toMap()); + + factory SceneOperationalValue.fromJson(String source) => + SceneOperationalValue.fromMap(json.decode(source)); + + @override + String toString() => + 'StaticFunctionOperationHelper(icon: $icon, value: $value, description: $description, iconValue: $iconValue, minValue: $minValue, maxValue: $maxValue, stepValue: $stepValue)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SceneOperationalValue && + other.icon == icon && + other.description == description && + other.iconValue == iconValue && + other.minValue == minValue && + other.maxValue == maxValue && + other.stepValue == stepValue && + other.value == value; + } + + @override + int get hashCode => + icon.hashCode ^ + value.hashCode ^ + description.hashCode ^ + iconValue.hashCode ^ + minValue.hashCode ^ + maxValue.hashCode ^ + stepValue.hashCode ^ + description.hashCode; +} diff --git a/lib/features/scene/model/scenes_model.dart b/lib/features/scene/model/scenes_model.dart new file mode 100644 index 0000000..4c0066c --- /dev/null +++ b/lib/features/scene/model/scenes_model.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +class ScenesModel { + final String id; + final String name; + final String status; + final String type; + + ScenesModel({ + required this.id, + required this.name, + required this.status, + required this.type, + }); + + factory ScenesModel.fromRawJson(String str) => + ScenesModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory ScenesModel.fromJson(Map json) => ScenesModel( + id: json["id"], + name: json["name"] ?? '', + status: json["status"] ?? '', + type: json["type"] ?? '', + ); + + Map toJson() => { + "id": id, + "name": name, + "status": status, + "type": type, + }; +} diff --git a/lib/features/scene/model/smart_scene_enable.dart b/lib/features/scene/model/smart_scene_enable.dart new file mode 100644 index 0000000..0195b7f --- /dev/null +++ b/lib/features/scene/model/smart_scene_enable.dart @@ -0,0 +1,35 @@ +class SmartSceneEnable { + final String entityId; + final String actionExecutor; + final String sceneORAutomationName; + final bool isAutomation; + final String type; + + SmartSceneEnable({ + required this.entityId, + required this.actionExecutor, + required this.sceneORAutomationName, + required this.isAutomation, + required this.type, + }); + + factory SmartSceneEnable.fromJson(Map json) { + return SmartSceneEnable( + entityId: json['entityId'], + actionExecutor: json['actionExecutor'], + sceneORAutomationName: json['sceneORAutomationName'], + isAutomation: json['isAutomation'], + type: json['type'], + ); + } + + Map toJson() { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + 'sceneORAutomationName': sceneORAutomationName, + 'isAutomation': isAutomation, + 'type': type, + }; + } +} diff --git a/lib/features/scene/model/update_automation.dart b/lib/features/scene/model/update_automation.dart new file mode 100644 index 0000000..d15d191 --- /dev/null +++ b/lib/features/scene/model/update_automation.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; + +class AutomationStatusUpdate { + final String unitUuid; + final bool isEnable; + + AutomationStatusUpdate({ + required this.unitUuid, + required this.isEnable, + }); + + factory AutomationStatusUpdate.fromRawJson(String str) => + AutomationStatusUpdate.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AutomationStatusUpdate.fromJson(Map json) => + AutomationStatusUpdate( + unitUuid: json["unitUuid"], + isEnable: json["isEnable"], + ); + + Map toJson() => { + "unitUuid": unitUuid, + "isEnable": isEnable, + }; + + factory AutomationStatusUpdate.fromMap(Map map) => + AutomationStatusUpdate( + unitUuid: map["unitUuid"], + isEnable: map["isEnable"], + ); + + Map toMap() => { + "unitUuid": unitUuid, + "isEnable": isEnable, + }; +} diff --git a/lib/features/scene/view/create_scene_view.dart b/lib/features/scene/view/create_scene_view.dart new file mode 100644 index 0000000..283f87f --- /dev/null +++ b/lib/features/scene/view/create_scene_view.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/navigate_to_route.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class CreateSceneView extends StatelessWidget { + const CreateSceneView({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: StringsManager.createScene, + padding: const EdgeInsets.only(top: 24), + leading: IconButton( + onPressed: () { + navigateToRoute(context, Routes.sceneTasksRoute); + }, + icon: const Icon( + Icons.arrow_back_ios, + )), + child: Column( + children: [ + DefaultContainer( + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: 4), + child: const SceneListTile( + assetPath: Assets.handClickIcon, + titleString: StringsManager.tapToRun, + subtitleString: StringsManager.turnOffAllLights, + ), + onTap: () { + Navigator.pushNamed( + context, + Routes.sceneTasksRoute, + arguments: SceneSettingsRouteArguments( + sceneType: CreateSceneEnum.tabToRun.name, + sceneId: '', + sceneName: '', + ), + ); + context.read().add(const ClearTaskListEvent()); + context + .read() + .add(const SceneTypeEvent(CreateSceneEnum.tabToRun)); + }, + ), + DefaultContainer( + width: double.infinity, + margin: const EdgeInsets.symmetric(vertical: 4), + child: const SceneListTile( + assetPath: Assets.refreshIcon, + titleString: StringsManager.whenDeviceStatusChanges, + subtitleString: StringsManager.whenUnusualActivityIsDetected, + ), + onTap: () { + Navigator.pushNamed( + context, + Routes.sceneControlDevicesRoute, + arguments: SceneSettingsRouteArguments( + sceneType: CreateSceneEnum.deviceStatusChanges.name, + sceneId: '', + sceneName: '', + ), + ); + context + .read() + .add(const ClearTaskListEvent(isAutomation: true)); + context.read().add( + const SceneTypeEvent(CreateSceneEnum.deviceStatusChanges)); + }, + ), + ], + ), + ); + } +} diff --git a/lib/features/scene/view/device_functions_view.dart b/lib/features/scene/view/device_functions_view.dart new file mode 100644 index 0000000..ec22d2e --- /dev/null +++ b/lib/features/scene/view/device_functions_view.dart @@ -0,0 +1,277 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/helper/scene_logic_helper.dart'; +import 'package:syncrow_app/features/scene/helper/scene_operations_data_helper.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/light_divider.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/navigation/navigate_to_route.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DeviceFunctionsView extends StatelessWidget + with SceneOperationsDataHelper, SceneLogicHelper { + DeviceFunctionsView({super.key}); + + @override + Widget build(BuildContext context) { + final device = (ModalRoute.of(context)?.settings.arguments as Map)['device'] + as DeviceModel; + + final isAutomation = (ModalRoute.of(context)?.settings.arguments + as Map)['isAutomationDeviceStatus'] as bool; + + List functions = []; + if (device.functions.isNotEmpty) { + functions = getFunctionsWithIcons( + type: device.productType, + functions: device.functions, + deviceId: device.uuid ?? '', + deviceName: device.name ?? '', + isAutomation: isAutomation, + ); + } + + return DefaultScaffold( + title: getTitle(type: device.productType), + actions: [ + TextButton( + onPressed: () { + context + .read() + .add(AddTaskEvent(isAutomation: isAutomation)); + navigateToRoute(context, Routes.sceneTasksRoute); + }, + child: BodyMedium( + text: 'Save', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.secondaryColor.withOpacity(0.6), + ), + ), + ], + leading: TextButton( + onPressed: () { + _cancelOperation(context, device, isAutomation); + }, + child: BodyMedium( + text: 'Cancel', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.textPrimaryColor.withOpacity(0.6), + ), + ), + leadingWidth: 80, + padding: EdgeInsets.zero, + child: ListView.builder( + shrinkWrap: true, + itemCount: functions.length, + padding: const EdgeInsets.only(top: 24.0), + itemBuilder: (context, index) { + return DefaultContainer( + padding: index == 0 + ? const EdgeInsets.only(top: 8) + : index == functions.length - 1 + ? const EdgeInsets.only(bottom: 8) + : EdgeInsets.zero, + margin: EdgeInsets.zero, + borderRadius: index == 0 + ? const BorderRadius.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20)) + : index == functions.length - 1 + ? const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20)) + : BorderRadius.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + BlocBuilder( + builder: (context, state) { + return SceneListTile( + iconsSize: 22, + minLeadingWidth: 20, + assetPath: functions[index].icon, + titleString: functions[index].operationName, + trailingWidget: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.arrow_forward_ios_rounded, + color: ColorsManager.greyColor, + size: 16, + ), + ], + ), + onPressed: () { + if (isAutomation) { + _showAutomationDialog( + context, + functions[index], + device, + ); + } else { + _showTabToRunDialog( + context, + functions[index], + device, + ); + } + }, + ); + }, + ), + index != functions.length - 1 + ? SizedBox( + width: context.width * 0.8, child: const LightDivider()) + : const SizedBox(), + ], + ), + ); + }, + ), + ); + } + + void _cancelOperation( + BuildContext context, DeviceModel device, bool isAutomation) { + final createSceneBloc = context.read(); + final automationSelectedValue = createSceneBloc.automationSelectedValues; + if (automationSelectedValue.isNotEmpty) { + for (var element in device.functions) { + if (automationSelectedValue.containsKey(element.code)) { + createSceneBloc.add( + RemoveTempTaskByIdEvent(code: element.code!, isAutomation: true)); + createSceneBloc.add(RemoveFromSelectedValueById( + code: element.code!, isAutomation: true)); + } + } + } + + final selectedValue = createSceneBloc.selectedValues; + if (selectedValue.isNotEmpty) { + for (var element in device.functions) { + if (selectedValue.containsKey(element.code)) { + createSceneBloc.add(RemoveTempTaskByIdEvent(code: element.code!)); + createSceneBloc.add(RemoveFromSelectedValueById(code: element.code!)); + } + } + } + + Navigator.pop(context); + + createSceneBloc.add(const ClearTempTaskListEvent(isAutomation: false)); + createSceneBloc.add(const ClearTempTaskListEvent(isAutomation: true)); + } + + void _showTabToRunDialog( + BuildContext context, + SceneStaticFunction function, + DeviceModel device, + ) { + final functionValues = + context.read().selectedValues[function.code]; + + context.customAlertDialog( + alertBody: getTheCorrectDialogBody( + function, + functionValues, + isAutomation: false, + ), + title: function.operationName, + onConfirm: () { + final selectedValue = + context.read().selectedValues[function.code]; + if (selectedValue == null) { + return; + } + context.read().add(TempHoldSceneTasksEvent( + deviceControlModel: DeviceControlModel( + deviceId: device.uuid, + code: function.code, + value: selectedValue, + ), + deviceId: device.uuid ?? '', + operation: function.operationName, + icon: device.icon ?? '', + deviceName: device.name ?? '', + uniqueId: function.uniqueCustomId!, + operationType: function.operationDialogType, + )); + Navigator.pop(context); + }, + onDismiss: () { + final tempTaskList = context.read().tempTasksList; + for (var element in tempTaskList) { + if (element.code == function.code) { + context + .read() + .add(RemoveTempTaskByIdEvent(code: function.code)); + context + .read() + .add(RemoveFromSelectedValueById(code: function.code)); + } + } + Navigator.pop(context); + }, + ); + } + + void _showAutomationDialog( + BuildContext context, SceneStaticFunction function, DeviceModel device) { + final automationFunctionValues = + context.read().automationSelectedValues[function.code]; + + context.customAlertDialog( + alertBody: getTheCorrectDialogBody( + function, + automationFunctionValues, + isAutomation: true, + ), + title: function.operationName, + onConfirm: () { + final automationFunctionValues = context + .read() + .automationSelectedValues[function.code]; + if (automationFunctionValues == null) { + return; + } + context.read().add(TempHoldSceneTasksEvent( + deviceControlModel: DeviceControlModel( + deviceId: device.uuid, + code: function.code, + value: automationFunctionValues, + ), + deviceId: device.uuid ?? '', + operation: function.operationName, + icon: device.icon ?? '', + deviceName: device.name ?? '', + uniqueId: function.uniqueCustomId!, + operationType: function.operationDialogType, + isAutomation: true, + )); + Navigator.pop(context); + }, + onDismiss: () { + final automationTempTaskList = + context.read().automationTempTasksList; + for (var element in automationTempTaskList) { + if (element.code == function.code) { + context.read().add(RemoveTempTaskByIdEvent( + code: function.code, isAutomation: true)); + context.read().add(RemoveFromSelectedValueById( + code: function.code, isAutomation: true)); + } + } + Navigator.pop(context); + }, + ); + } +} diff --git a/lib/features/scene/view/scene_auto_settings.dart b/lib/features/scene/view/scene_auto_settings.dart new file mode 100644 index 0000000..af9db38 --- /dev/null +++ b/lib/features/scene/view/scene_auto_settings.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/view/scene_tasks_view.dart'; +import 'package:syncrow_app/features/scene/widgets/effective_period_setting/effective_period_bottom_sheet.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SceneAutoSettings extends StatelessWidget { + const SceneAutoSettings({super.key}); + + @override + Widget build(BuildContext context) { + final sceneSettings = + ModalRoute.of(context)!.settings.arguments as Map? ?? + {}; + final sceneId = sceneSettings['sceneId'] as String? ?? ''; + final isAutomation = context.read().sceneType == + CreateSceneEnum.deviceStatusChanges; + final sceneName = sceneSettings['sceneName'] as String? ?? ''; + + return DefaultScaffold( + title: 'Settings', + padding: EdgeInsets.zero, + leading: IconButton( + onPressed: () { + Navigator.of(context).pop(); + }, + icon: const Icon( + Icons.arrow_back_ios, + )), + child: SizedBox( + height: MediaQuery.sizeOf(context).height, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: DefaultContainer( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox( + height: 8, + ), + Visibility( + visible: isAutomation, + child: SceneListTile( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + titleString: "Effective Period", + trailingWidget: + const Icon(Icons.arrow_forward_ios_rounded), + onPressed: () { + context.customBottomSheet( + child: const EffectPeriodBottomSheetContent(), + ); + }, + ), + ), + Visibility( + visible: sceneName.isNotEmpty && isAutomation, + child: SizedBox( + width: context.width * 0.9, + child: const Divider( + color: ColorsManager.greyColor, + ), + ), + ), + Visibility( + visible: sceneName.isNotEmpty, + child: DeleteBottomSheetContent( + isAutomation: isAutomation, + sceneId: sceneId, + ), + ), + const SizedBox( + height: 16, + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/scene/view/scene_rooms_tabbar.dart b/lib/features/scene/view/scene_rooms_tabbar.dart new file mode 100644 index 0000000..a97d668 --- /dev/null +++ b/lib/features/scene/view/scene_rooms_tabbar.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/room_model.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_event.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_devices/scene_devices_body.dart'; + +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/navigation/navigate_to_route.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; + +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class SceneRoomsTabBarDevicesView extends StatefulWidget { + const SceneRoomsTabBarDevicesView({super.key}); + + @override + State createState() => + _SceneRoomsTabBarDevicesViewState(); +} + +class _SceneRoomsTabBarDevicesViewState + extends State + with SingleTickerProviderStateMixin { + late final TabController _tabController; + List? rooms = []; + + @override + void initState() { + rooms = List.from(HomeCubit.getInstance().selectedSpace?.rooms ?? []); + if (rooms != null) { + if (rooms![0].id != '-1') { + rooms?.insert( + 0, + RoomModel( + name: 'All Devices', + devices: DevicesCubit.getInstance().allDevices, + id: '-1', + type: SpaceType.Room, + ), + ); + } + } + + _tabController = + TabController(length: rooms!.length, vsync: this, initialIndex: 0); + _tabController.addListener(_handleTabSwitched); + super.initState(); + } + + void _handleTabSwitched() { + if (_tabController.indexIsChanging) { + final value = _tabController.index; + + /// select tab + context.read().add( + TabChanged(selectedIndex: value, roomId: rooms?[value].id ?? '')); + return; + } + } + + @override + void dispose() { + super.dispose(); + _tabController.dispose(); + _tabController.removeListener(() {}); + } + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: StringsManager.createScene, + padding: EdgeInsets.zero, + leading: IconButton( + onPressed: () { + navigateToRoute(context, Routes.sceneTasksRoute); + }, + icon: const Icon( + Icons.arrow_back_ios, + ), + ), + child: SceneDevicesBody(tabController: _tabController, rooms: rooms), + ); + } +} diff --git a/lib/features/scene/view/scene_tasks_view.dart b/lib/features/scene/view/scene_tasks_view.dart new file mode 100644 index 0000000..35a7344 --- /dev/null +++ b/lib/features/scene/view/scene_tasks_view.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/widgets/create_scene_save_button.dart'; +import 'package:syncrow_app/features/scene/widgets/if_then_containers/if_container.dart'; +import 'package:syncrow_app/features/scene/widgets/if_then_containers/then_container.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/navigate_to_route.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class SceneTasksView extends StatelessWidget { + const SceneTasksView({super.key}); + + @override + Widget build(BuildContext context) { + final sceneSettings = ModalRoute.of(context)!.settings.arguments + as SceneSettingsRouteArguments; + + final isAutomation = + sceneSettings.sceneType == CreateSceneEnum.deviceStatusChanges.name; + // context.read().add(SceneTypeEvent(isAutomation + // ? CreateSceneEnum.deviceStatusChanges + // : CreateSceneEnum.tabToRun)); + + return DefaultScaffold( + title: sceneSettings.sceneName.isNotEmpty + ? sceneSettings.sceneName + : StringsManager.createScene, + padding: EdgeInsets.zero, + leading: IconButton( + onPressed: () { + navigateToRoute(context, Routes.homeRoute); + }, + icon: const Icon( + Icons.arrow_back_ios, + )), + actions: [ + BlocBuilder( + builder: (context, state) { + final sceneType = context.read().sceneType; + return Visibility( + visible: sceneType != CreateSceneEnum.none, + child: SizedBox( + width: 40, + child: GestureDetector( + onTap: () { + Navigator.pushNamed( + context, + Routes.sceneAutoSettingsRoute, + arguments: { + "sceneId": sceneSettings.sceneId, + "isAutomation": isAutomation, + "sceneName": sceneSettings.sceneName, + }, + ); + }, + child: SvgPicture.asset( + Assets.assetsIconsSettings, + colorFilter: const ColorFilter.mode( + ColorsManager.textPrimaryColor, + BlendMode.srcIn, + ), + ), + ), + ), + ); + }, + ), + ], + child: Stack( + children: [ + SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 24, + ), + // IF + const IFDefaultContainer(), + const SizedBox( + height: 8, + ), + // THEN + ThenDefaultContainer(sceneId: sceneSettings.sceneId), + const SizedBox( + height: 100, + ), + ], + ), + ), + Positioned( + bottom: 16, + right: 40, + left: 40, + child: SizedBox( + width: context.width * 0.8, + child: CreateSceneSaveButton( + sceneName: sceneSettings.sceneName, + sceneId: sceneSettings.sceneId, + ), + ), + ) + ], + ), + ); + } +} + +class DeleteBottomSheetContent extends StatelessWidget { + const DeleteBottomSheetContent( + {super.key, required this.sceneId, required this.isAutomation}); + + final String sceneId; + final bool isAutomation; + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is DeleteSceneSuccess) { + if (state.success) { + navigateToRoute(context, Routes.homeRoute); + BlocProvider.of(context) + .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); + BlocProvider.of(context).add( + LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); + } + } + }, + builder: (context, state) { + return SceneListTile( + onPressed: () { + context.read().add(DeleteSceneEvent( + sceneId: sceneId, + unitUuid: HomeCubit.getInstance().selectedSpace!.id!, + )); + }, + padding: const EdgeInsets.symmetric(horizontal: 8), + titleString: isAutomation + ? StringsManager.deleteAutomation + : StringsManager.deleteScene, + leadingWidget: (state is DeleteSceneLoading) + ? const SizedBox( + height: 24, width: 24, child: CircularProgressIndicator()) + : SvgPicture.asset( + Assets.assetsDeleteIcon, + color: ColorsManager.red, + ), + ); + }, + ); + } +} diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart new file mode 100644 index 0000000..ce297ce --- /dev/null +++ b/lib/features/scene/view/scene_view.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/view/widgets/scene_listview.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_grid_view.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_header.dart'; +import 'package:syncrow_app/features/shared_widgets/create_unit.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SceneView extends StatelessWidget { + final bool pageType; + const SceneView({super.key, this.pageType = false}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => SceneBloc() + ..add(LoadScenes(HomeCubit.getInstance().selectedSpace?.id ?? '')) + ..add(LoadAutomation(HomeCubit.getInstance().selectedSpace?.id ?? '')), + child: BlocBuilder( + builder: (context, state) { + if (state is DeleteSceneSuccess) { + if (state.success) { + BlocProvider.of(context) + .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); + BlocProvider.of(context).add( + LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); + } + } + if (state is CreateSceneWithTasks) { + if (state.success == true) { + BlocProvider.of(context) + .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); + BlocProvider.of(context).add( + LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); + context + .read() + .add(const SmartSceneClearEvent()); + } + } + return BlocListener( + listener: (context, state) { + if (state is SceneTriggerSuccess) { + context.showCustomSnackbar( + message: + 'Scene ${state.sceneName} triggered successfully!'); + } + }, + child: HomeCubit.getInstance().spaces?.isEmpty ?? true + ? const CreateUnitWidget() + : Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (pageType == false) const SceneHeader(), + if (pageType == false) const SizedBox(height: 8), + BlocBuilder( + builder: (context, state) { + if (state is SceneLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + if (state is SceneError) { + return Center( + child: Text(state.message), + ); + } + if (state is SceneLoaded) { + final scenes = state.scenes; + final automationList = state.automationList; + + return pageType + ? Expanded( + child: SceneListview( + scenes: scenes, + loadingSceneId: state.loadingSceneId, + )) + : Expanded( + child: ListView( + children: [ + Theme( + data: ThemeData().copyWith( + dividerColor: Colors.transparent), + child: ExpansionTile( + tilePadding: + const EdgeInsets.symmetric( + horizontal: 6), + initiallyExpanded: true, + iconColor: ColorsManager.grayColor, + title: const BodyMedium( + text: 'Tap to run routines'), + children: [ + scenes.isNotEmpty + ? SceneGrid( + scenes: scenes, + loadingSceneId: + state.loadingSceneId, + disablePlayButton: false, + loadingStates: state + .loadingStates, // Add this line + ) + : const Center( + child: BodyMedium( + text: + 'No scenes have been added yet', + ), + ), + const SizedBox( + height: 10, + ), + ], + ), + ), + Theme( + data: ThemeData().copyWith( + dividerColor: Colors.transparent), + child: ExpansionTile( + initiallyExpanded: true, + iconColor: ColorsManager.grayColor, + tilePadding: + const EdgeInsets.symmetric( + horizontal: 6), + title: const BodyMedium( + text: 'Automation'), + children: [ + automationList.isNotEmpty + ? SceneGrid( + scenes: automationList, + loadingSceneId: + state.loadingSceneId, + disablePlayButton: true, + loadingStates: state + .loadingStates, // Add this line + ) + : const Center( + child: BodyMedium( + text: + 'No automations have been added yet', + ), + ), + const SizedBox( + height: 10, + ), + ], + ), + ), + const SizedBox( + height: 15, + ), + ], + ), + ); + } + return const SizedBox(); + }, + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/scene/view/smart_automation_select_route.dart b/lib/features/scene/view/smart_automation_select_route.dart new file mode 100644 index 0000000..13a5ad9 --- /dev/null +++ b/lib/features/scene/view/smart_automation_select_route.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/scene/widgets/select_smart_scene/smart_enable_autoamtion.dart'; +import 'package:syncrow_app/features/scene/widgets/select_smart_scene/smart_enable_tab_run.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/navigate_to_route.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SmartAutomationSelectView extends StatelessWidget { + const SmartAutomationSelectView({super.key}); + + @override + Widget build(BuildContext context) { + final sceneType = context.read().sceneType; + final sceneId = ModalRoute.of(context)!.settings.arguments as String; + return DefaultScaffold( + title: "Select Smart Scene", + padding: const EdgeInsets.only(top: 24), + leading: IconButton( + onPressed: () { + navigateToRoute(context, Routes.sceneTasksRoute); + }, + icon: const Icon( + Icons.arrow_back_ios, + )), + // height: sceneType.name == CreateSceneEnum.deviceStatusChanges.name + // ? 260 + // : 200, + height: MediaQuery.sizeOf(context).height, + child: Column( + children: [ + DefaultContainer( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: sceneType.name == + CreateSceneEnum.deviceStatusChanges.name, + child: SceneListTile( + assetPath: Assets.handClickIcon, + titleString: "Tap To Run", + subtitleString: '', + trailingWidget: const Icon( + Icons.arrow_forward_ios_rounded, + color: ColorsManager.greyColor, + ), + onPressed: () { + context.customBottomSheet( + child: const SmartEnableTabRun(), + ); + }, + ), + ), + Visibility( + visible: sceneType.name == + CreateSceneEnum.deviceStatusChanges.name, + child: const Divider( + color: ColorsManager.dividerColor, + ), + ), + SceneListTile( + assetPath: Assets.refreshIcon, + titleString: "Automation", + subtitleString: '', + trailingWidget: const Icon( + Icons.arrow_forward_ios_rounded, + color: ColorsManager.greyColor, + ), + onPressed: () { + context.customBottomSheet( + child: SmartEnableAutomation(automationId: sceneId), + ); + }, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart b/lib/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart new file mode 100644 index 0000000..35f1d37 --- /dev/null +++ b/lib/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart @@ -0,0 +1,73 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; + +class AlertDialogCountdown extends StatefulWidget { + AlertDialogCountdown({ + super.key, + required this.durationValue, + this.functionValue, + required this.function, + }); + + final int durationValue; + dynamic functionValue; + SceneStaticFunction function; + + @override + State createState() => _AlertDialogCountdownState(); +} + +class _AlertDialogCountdownState extends State { + @override + didChangeDependencies() { + super.didChangeDependencies(); + + final tempTaskList = context.read().tempTasksList; + + for (var element in tempTaskList) { + if (element.code == widget.function.code) { + durationInSeconds = element.functionValue; + } else { + context + .read() + .add(RemoveFromSelectedValueById(code: widget.function.code)); + } + } + if (widget.functionValue != null) { + setState(() { + durationInSeconds = widget.functionValue; + }); + } + } + + int durationInSeconds = 0; + // Convert seconds to Duration. + Duration get duration => Duration(seconds: widget.durationValue); + @override + Widget build(BuildContext context) { + return Container( + height: 120, + color: Colors.white, + child: CupertinoTimerPicker( + itemExtent: 120, + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: duration, + onTimerDurationChanged: (newDuration) { + setState(() { + durationInSeconds = newDuration.inSeconds; + }); + context.read().add(SelectedValueEvent( + value: newDuration.inSeconds, + code: widget.function.deviceId == 'delay' + ? 'delay' + : widget.function.code)); + }, + ), + ); + } +} diff --git a/lib/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart b/lib/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart new file mode 100644 index 0000000..58ef349 --- /dev/null +++ b/lib/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart @@ -0,0 +1,125 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; + +class AlertDialogFunctionsOperationsBody extends StatefulWidget { + AlertDialogFunctionsOperationsBody({ + super.key, + this.functionValue, + required this.taskItem, + required this.isAutomation, + }); + + dynamic functionValue; + final SceneStaticFunction taskItem; + final bool isAutomation; + + @override + State createState() => + _AlertDialogFunctionsOperationsBodyState(); +} + +class _AlertDialogFunctionsOperationsBodyState + extends State { + @override + didChangeDependencies() { + super.didChangeDependencies(); + if (widget.isAutomation) { + final automationTempTasksList = + context.read().automationTempTasksList; + + if (automationTempTasksList.isNotEmpty) { + for (var element in automationTempTasksList) { + if (element.code == widget.taskItem.code) { + groupValue = element.functionValue; + } else { + context.read().add(RemoveFromSelectedValueById( + code: widget.taskItem.code, isAutomation: widget.isAutomation)); + } + } + } + + if (widget.functionValue != null) { + setState(() { + groupValue = widget.functionValue; + }); + } + } else { + final tempTaskList = context.read().tempTasksList; + + if (tempTaskList.isNotEmpty) { + for (var element in tempTaskList) { + if (element.code == widget.taskItem.code) { + groupValue = element.functionValue; + } else { + context + .read() + .add(RemoveFromSelectedValueById(code: widget.taskItem.code)); + } + } + } + + if (widget.functionValue != null) { + setState(() { + groupValue = widget.functionValue; + }); + } + } + } + + dynamic groupValue; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...widget.taskItem.operationalValues.map( + (operation) => BlocBuilder( + builder: (context, state) { + return SceneListTile( + iconsSize: 25, + minLeadingWidth: 15, + padding: const EdgeInsets.symmetric(horizontal: 16), + assetPath: operation.icon, + assetHeaderValue: operation.iconValue, + titleString: operation.description.toString(), + textAlign: TextAlign.start, + trailingWidget: Radio( + value: operation.value, + groupValue: (state is SelectedTaskValueState) + ? state.value + : groupValue, + onChanged: (value) { + setState(() { + groupValue = value; + }); + context.read().add(SelectedValueEvent( + value: value!, + code: widget.taskItem.code, + isAutomation: widget.isAutomation)); + }, + ), + onPressed: () { + setState(() { + groupValue = operation.value; + }); + context.read().add(SelectedValueEvent( + value: groupValue, + code: widget.taskItem.code, + isAutomation: widget.isAutomation)); + }, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/scene/widgets/alert_dialogs/alert_dialog_slider_steps.dart b/lib/features/scene/widgets/alert_dialogs/alert_dialog_slider_steps.dart new file mode 100644 index 0000000..3503e46 --- /dev/null +++ b/lib/features/scene/widgets/alert_dialogs/alert_dialog_slider_steps.dart @@ -0,0 +1,260 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class AlertDialogSliderSteps extends StatefulWidget { + const AlertDialogSliderSteps({ + super.key, + this.functionValue, + required this.taskItem, + required this.isAutomation, + }); + + final dynamic functionValue; + final SceneStaticFunction taskItem; + final bool isAutomation; + + @override + State createState() => _AlertDialogSliderStepsState(); +} + +class _AlertDialogSliderStepsState extends State { + double? groupValue; + int selectedToggleIndex = 1; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final createSceneBloc = context.read(); + + if (widget.taskItem.comparator != null) { + selectedToggleIndex = _comparatorToIndex(widget.taskItem.comparator); + } + + if (widget.isAutomation) { + final automationTempTaskList = createSceneBloc.automationTempTasksList; + final automationComparatorValues = + createSceneBloc.automationComparatorValues; + + for (var element in automationTempTaskList) { + if (element.code == widget.taskItem.code) { + groupValue = element.functionValue; + selectedToggleIndex = + _comparatorToIndex(automationComparatorValues[element.code]); + } + } + } + if (widget.taskItem.code == 'temp_current') { + if (widget.functionValue != null) { + groupValue = _normalizeValue( + double.tryParse(widget.functionValue.toString()) ?? + widget.taskItem.operationalValues[0].minValue); + } else { + groupValue = widget.taskItem.operationalValues[0].minValue; + } + } else { + if (widget.functionValue != null) { + groupValue = _normalizeValue(widget.functionValue); + } else { + groupValue = + _normalizeValue(widget.taskItem.operationalValues[0].minValue); + } + } + + setState(() {}); + + context.read().add( + SelectedValueEvent( + value: _deNormalizeValue(groupValue), + code: widget.taskItem.code, + isAutomation: widget.isAutomation, + comparator: _indexToComparator(selectedToggleIndex), + ), + ); + } + + int _comparatorToIndex(String? comparator) { + switch (comparator) { + case "<": + return 0; + case "==": + return 1; + case ">": + return 2; + default: + return 1; + } + } + + String _indexToComparator(int index) { + switch (index) { + case 0: + return "<"; + case 1: + return "=="; + case 2: + return ">"; + default: + return "=="; + } + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ToggleButtons( + isSelected: [ + selectedToggleIndex == 0, + selectedToggleIndex == 1, + selectedToggleIndex == 2 + ], + onPressed: (index) { + setState(() { + selectedToggleIndex = index; + }); + context.read().add( + SelectedValueEvent( + value: _deNormalizeValue(groupValue), + code: widget.taskItem.code, + isAutomation: widget.isAutomation, + comparator: _indexToComparator(selectedToggleIndex), + ), + ); + }, + borderRadius: BorderRadius.circular(30), + selectedColor: Colors.white, + color: ColorsManager.blackColor, + fillColor: ColorsManager.primaryColorWithOpacity, + borderColor: ColorsManager.greyColor, + constraints: BoxConstraints.tight(const Size(70, 30)), + children: [ + SizedBox( + width: 70, + height: 30, + child: Container( + color: selectedToggleIndex == 0 + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.greyColor, + alignment: Alignment.center, + child: const Text("<"), + ), + ), + SizedBox( + width: 70, + height: 30, + child: Container( + color: selectedToggleIndex == 1 + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.greyColor, + alignment: Alignment.center, + child: const Text("="), + ), + ), + SizedBox( + width: 70, + height: 30, + child: Container( + color: selectedToggleIndex == 2 + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.greyColor, + alignment: Alignment.center, + child: const Text(">"), + ), + ), + ], + ), + const SizedBox(height: 12), + ...widget.taskItem.operationalValues.map( + (operation) => BlocBuilder( + builder: (context, state) { + return Column( + children: [ + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TitleMedium( + text: groupValue != null + ? (groupValue! % 1 == 0 + ? groupValue!.toStringAsFixed(0) + : groupValue!.toStringAsFixed(1)) + : "0", + style: context.titleMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontSize: 30, + ), + ), + const SizedBox(width: 8), + TitleMedium( + text: operation.description.toString(), + style: context.titleMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontSize: 30, + ), + ), + ], + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Slider( + value: groupValue ?? 0, + min: operation.minValue ?? 0, + max: operation.maxValue ?? 0, + inactiveColor: ColorsManager.primaryColorWithOpacity + .withOpacity(0.2), + divisions: operation.stepValue != null + ? ((operation.maxValue!.toDouble() - + operation.minValue!.toDouble()) / + operation.stepValue!.toDouble()) + .round() + : null, + onChanged: (value) { + setState(() { + groupValue = normalizeToDoubleValue(value); + }); + context.read().add(SelectedValueEvent( + value: _deNormalizeValue(groupValue), + code: widget.taskItem.code, + isAutomation: widget.isAutomation, + comparator: + _indexToComparator(selectedToggleIndex))); + }, + ), + ), + const SizedBox(height: 12), + ], + ); + }, + ), + ), + ], + ); + } + + double _normalizeValue(dynamic value) { + if ((widget.taskItem.code == "temp_set" && value > 199) || + (widget.taskItem.code == "temp_current" && value >= -99.0)) { + return (value) / 10; + } + return value.toDouble(); + } + + double? normalizeToDoubleValue(double value) { + return double.tryParse(value.toStringAsFixed(1)); + } + + double _deNormalizeValue(double? value) { + if (widget.taskItem.code == "temp_set" || + widget.taskItem.code == "temp_current") { + return (value ?? 0) * 10; + } + return value ?? 0; + } +} diff --git a/lib/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart b/lib/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart new file mode 100644 index 0000000..7e80b48 --- /dev/null +++ b/lib/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart @@ -0,0 +1,130 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class AlertDialogTemperatureBody extends StatefulWidget { + AlertDialogTemperatureBody({ + super.key, + this.functionValue, + required this.taskItem, + }); + + final SceneStaticFunction taskItem; + dynamic functionValue; + + @override + State createState() => + _AlertDialogTemperatureBodyState(); +} + +class _AlertDialogTemperatureBodyState + extends State { + int temperature = 24; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final tempTaskList = context.read().tempTasksList; + + for (var element in tempTaskList) { + if (element.code == widget.taskItem.code) { + temperature = _normalizeTemperature(element.functionValue); + } else { + context + .read() + .add(RemoveFromSelectedValueById(code: widget.taskItem.code)); + } + } + if (widget.functionValue != null) { + setState(() { + temperature = _normalizeTemperature(widget.functionValue); + }); + } + + context.read().add(SelectedValueEvent( + value: temperature * 10, + code: widget.taskItem.code, + )); + } + + int _normalizeTemperature(dynamic value) { + if (value >= 100) { + return value ~/ 10; + } + return value ?? 24; + } + + @override + Widget build(BuildContext context) { + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 32), + leading: IconButton( + onPressed: () { + setState(() { + if (temperature > 20) { + temperature--; + } + }); + context.read().add(SelectedValueEvent( + value: temperature * 10, + code: widget.taskItem.code, + )); + }, + icon: const Icon( + Icons.remove, + size: 32, + color: ColorsManager.greyColor, + )), + title: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TitleMedium( + text: temperature.toString(), + style: context.titleMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontSize: 30, + )), + const SizedBox(width: 4), + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: SvgPicture.asset( + Assets.assetsCelsiusDegrees, + alignment: Alignment.topCenter, + width: 32, + ), + ), + ], + ), + subtitle: BodyLarge( + text: widget.taskItem.operationalValues[0].description.toString(), + textAlign: TextAlign.center, + ), + trailing: IconButton( + onPressed: () { + setState(() { + if (temperature < 30) { + temperature++; + } + }); + context.read().add(SelectedValueEvent( + value: temperature * 10, code: widget.taskItem.code)); + }, + icon: const Icon( + Icons.add, + size: 32, + color: ColorsManager.greyColor, + )), + ); + } +} diff --git a/lib/features/scene/widgets/bottom_sheet_widget.dart b/lib/features/scene/widgets/bottom_sheet_widget.dart new file mode 100644 index 0000000..7722498 --- /dev/null +++ b/lib/features/scene/widgets/bottom_sheet_widget.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/operation_dialog_type.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/light_divider.dart'; + +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class CustomBottomSheetWidget extends StatelessWidget { + const CustomBottomSheetWidget({ + super.key, + required this.sceneId, + }); + final String sceneId; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8), + child: BodyLarge( + text: 'Add Task', + style: context.bodyLarge.copyWith( + fontWeight: FontWeight.w700, + color: ColorsManager.primaryColorWithOpacity, + ), + textAlign: TextAlign.center, + ), + ), + SizedBox( + width: context.width * 0.7, + child: const LightDivider(), + ), + SceneListTile( + assetPath: Assets.assetsIconsLight, + assetHeight: 24, + minLeadingWidth: 30, + titleString: 'Control Single Device', + padding: const EdgeInsets.symmetric(horizontal: 20), + trailingWidget: const Icon( + Icons.arrow_forward_ios_rounded, + size: 16, + color: ColorsManager.greyColor, + ), + onPressed: () { + Navigator.pushNamed(context, Routes.sceneControlDevicesRoute); + }, + ), + SceneListTile( + assetPath: Assets.player, + titleString: 'Select Smart Scene', + minLeadingWidth: 30, + assetHeight: 24, + padding: const EdgeInsets.symmetric(horizontal: 20), + trailingWidget: const Icon( + Icons.arrow_forward_ios_rounded, + size: 16, + color: ColorsManager.greyColor, + ), + onPressed: () { + Navigator.pushNamed(context, Routes.smartAutomationSelectRoute, arguments: sceneId); + }, + ), + SceneListTile( + assetPath: Assets.delay, + titleString: 'Delay The Action', + assetHeight: 24, + minLeadingWidth: 30, + padding: const EdgeInsets.symmetric(horizontal: 20), + trailingWidget: const Icon( + Icons.arrow_forward_ios_rounded, + size: 16, + color: ColorsManager.greyColor, + ), + onPressed: () => _onDelayActionPressed(context), + ), + ], + ), + ); + } + + void _onDelayActionPressed(BuildContext context) { + final functionValues = context.read().selectedValues['delay']; + final functions = [ + SceneStaticFunction( + deviceId: 'delay', + deviceName: 'Delay The Action', + icon: '', + operationName: 'Delay The Action', + code: '', + functionValue: 0, + operationDialogType: OperationDialogType.delay, + operationalValues: [ + SceneOperationalValue(icon: '', value: 0), + ], + ), + ]; + context.customAlertDialog( + alertBody: AlertDialogCountdown( + durationValue: functions[0].operationalValues.first.value, + function: functions[0], + functionValue: functionValues, + ), + title: functions[0].operationName, + onConfirm: () { + final selectedValue = context.read().selectedValues['delay']; + context.read().add(TempHoldSceneTasksEvent( + deviceControlModel: DeviceControlModel( + deviceId: '', + code: '', + value: selectedValue, + ), + deviceId: 'delay', + operation: functions[0].operationName, + icon: Assets.delay, + deviceName: 'Delay The Action', + uniqueId: functions[0].uniqueCustomId!, + operationType: functions[0].operationDialogType, + )); + context.read().add(const AddTaskEvent()); + Navigator.pop(context); + Navigator.pop(context); + }, + onDismiss: () { + final tempTaskList = context.read().tempTasksList; + + for (var element in tempTaskList) { + if (element.code == functions[0].code) { + context.read().add(RemoveTempTaskByIdEvent(code: functions[0].code)); + context + .read() + .add(RemoveFromSelectedValueById(code: functions[0].code)); + } + } + // } + Navigator.pop(context); + }, + ); + } +} diff --git a/lib/features/scene/widgets/create_scene_save_button.dart b/lib/features/scene/widgets/create_scene_save_button.dart new file mode 100644 index 0000000..985bfb3 --- /dev/null +++ b/lib/features/scene/widgets/create_scene_save_button.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/helper/scene_logic_helper.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/navigation/navigate_to_route.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class CreateSceneSaveButton extends StatefulWidget { + const CreateSceneSaveButton({ + super.key, + required this.sceneName, + required this.sceneId, + }); + final String sceneName; + final String sceneId; + + @override + State createState() => _CreateSceneSaveButtonState(); +} + +class _CreateSceneSaveButtonState extends State with SceneLogicHelper { + late TextEditingController sceneNameController; + + @override + void initState() { + sceneNameController = + TextEditingController(text: widget.sceneName.isNotEmpty ? widget.sceneName : ''); + super.initState(); + } + + @override + void dispose() { + sceneNameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is CreateSceneWithTasks) { + if (state.success) { + navigateToRoute(context, Routes.homeRoute); + sceneNameController.text = ''; + } + } else if (state is CreateSceneError) { + CustomSnackBar.displaySnackBar(state.message); + } + }, + builder: (context, state) { + return DefaultButton( + onPressed: () { + final sceneBloc = context.read(); + if (sceneBloc.tasksList.isEmpty && sceneBloc.automationTasksList.isEmpty) { + return; + } + final isAutomation = sceneBloc.sceneType == CreateSceneEnum.deviceStatusChanges; + + if (isAutomation && sceneBloc.automationTasksList.isEmpty) { + CustomSnackBar.displaySnackBar( + 'Conditions Must not be empty!', + ); + return; + } + + if (isAutomation && sceneBloc.tasksList.isEmpty) { + CustomSnackBar.displaySnackBar( + 'Actions Must not be empty!', + ); + return; + } + + if (widget.sceneName.isNotEmpty) { + handleSaveButtonPress( + context, + sceneName: sceneNameController, + actions: sceneBloc.tasksList, + updateScene: true, + sceneId: widget.sceneId, + isAutomation: isAutomation, + conditions: sceneBloc.automationTasksList, + ); + } else { + context.customAlertDialog( + alertBody: Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), + child: SizedBox( + height: 40, + child: SearchBar( + controller: sceneNameController, + elevation: WidgetStateProperty.all(0), + textStyle: WidgetStateProperty.all(context.bodyMedium), + hintStyle: WidgetStateProperty.all( + context.bodyMedium + .copyWith(fontSize: 14, color: ColorsManager.secondaryTextColor), + ), + hintText: 'Enter scene name', + backgroundColor: WidgetStateProperty.all(ColorsManager.backgroundColor), + ), + ), + ), + title: 'Scene Name', + onConfirm: () { + Navigator.pop(context); + if (sceneNameController.text.isNotEmpty) { + handleSaveButtonPress( + context, + sceneName: sceneNameController, + actions: sceneBloc.tasksList, + updateScene: false, + sceneId: widget.sceneId, + isAutomation: isAutomation, + conditions: sceneBloc.automationTasksList, + ); + } + }, + ); + } + }, + customButtonStyle: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + ColorsManager.primaryColor, + ), + ), + isLoading: state is CreateSceneLoading, + child: BodyLarge( + text: widget.sceneName.isNotEmpty ? 'Update' : 'Save', + style: context.bodyLarge.copyWith(color: Colors.white), + ), + ); + }, + ); + } +} diff --git a/lib/features/scene/widgets/effective_period_setting/effective_period_bottom_sheet.dart b/lib/features/scene/widgets/effective_period_setting/effective_period_bottom_sheet.dart new file mode 100644 index 0000000..784fb4b --- /dev/null +++ b/lib/features/scene/widgets/effective_period_setting/effective_period_bottom_sheet.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/scene/helper/effect_period_helper.dart'; +import 'package:syncrow_app/features/scene/widgets/effective_period_setting/period_options.dart'; +import 'package:syncrow_app/features/scene/widgets/effective_period_setting/repeat_days.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class EffectPeriodBottomSheetContent extends StatelessWidget { + const EffectPeriodBottomSheetContent({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Spacer(), + Expanded( + child: BodyMedium( + text: 'Effective Period', + fontColor: ColorsManager.primaryColorWithOpacity, + fontWeight: FontsManager.bold, + textAlign: TextAlign.center, + ), + ), + const Spacer(), + ], + ), + const Divider( + color: ColorsManager.backgroundColor, + ), + const PeriodOptions( + showCustomTimePicker: EffectPeriodHelper.showCustomTimePicker, + ), + const SizedBox(height: 16), + const RepeatDays(), + const SizedBox(height: 24), + ], + ), + ); + } +} diff --git a/lib/features/scene/widgets/effective_period_setting/period_options.dart b/lib/features/scene/widgets/effective_period_setting/period_options.dart new file mode 100644 index 0000000..4fed34c --- /dev/null +++ b/lib/features/scene/widgets/effective_period_setting/period_options.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_app/features/scene/enum/effective_period_options.dart'; +import 'package:syncrow_app/features/scene/helper/effect_period_helper.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class PeriodOptions extends StatelessWidget { + final Future?> Function(BuildContext) showCustomTimePicker; + + const PeriodOptions({ + required this.showCustomTimePicker, + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + children: [ + _buildRadioOption( + context, EnumEffectivePeriodOptions.allDay, '24 Hours'), + _buildRadioOption(context, EnumEffectivePeriodOptions.daytime, + 'Sunrise to Sunset'), + _buildRadioOption( + context, EnumEffectivePeriodOptions.night, 'Sunset to Sunrise'), + ListTile( + contentPadding: EdgeInsets.zero, + onTap: () => showCustomTimePicker(context), + title: const BodyMedium( + text: 'Custom', + ), + subtitle: + state.customStartTime != null && state.customEndTime != null + ? BodySmall( + text: + '${"${state.customStartTime}"} - ${"${state.customEndTime}"}', + style: context.bodySmall.copyWith(fontSize: 10), + ) + : BodySmall( + text: '00:00 - 23:59', + style: context.bodySmall.copyWith(fontSize: 10), + ), + trailing: Radio( + value: EnumEffectivePeriodOptions.custom, + groupValue: state.selectedPeriod, + onChanged: (value) async { + if (value != null) { + context.read().add(SetPeriod(value)); + } + }, + ), + ), + ], + ); + }, + ); + } + + Widget _buildRadioOption( + BuildContext context, EnumEffectivePeriodOptions value, String subtitle) { + return BlocBuilder( + builder: (context, state) { + return ListTile( + contentPadding: EdgeInsets.zero, + onTap: () { + context.read().add(SetPeriod(value)); + }, + title: BodyMedium(text: EffectPeriodHelper.formatEnumValue(value)), + subtitle: BodySmall( + text: subtitle, + style: context.bodySmall.copyWith(fontSize: 10), + ), + trailing: Radio( + value: value, + groupValue: state.selectedPeriod, + onChanged: (value) { + if (value != null) { + context.read().add(SetPeriod(value)); + } + }, + ), + ); + }, + ); + } +} diff --git a/lib/features/scene/widgets/effective_period_setting/repeat_days.dart b/lib/features/scene/widgets/effective_period_setting/repeat_days.dart new file mode 100644 index 0000000..47d3a01 --- /dev/null +++ b/lib/features/scene/widgets/effective_period_setting/repeat_days.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_state.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class RepeatDays extends StatelessWidget { + const RepeatDays({super.key}); + + @override + Widget build(BuildContext context) { + final effectiveBloc = context.read(); + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const BodyMedium(text: 'Repeat'), + const SizedBox(width: 8), + BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: effectiveBloc.daysMap.entries.map((entry) { + final day = entry.key; + final abbreviation = entry.value; + final dayIndex = effectiveBloc.getDayIndex(day); + final isSelected = state.selectedDaysBinary[dayIndex] == '1'; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 3.0), + child: GestureDetector( + onTap: () { + effectiveBloc.add(ToggleDay(day)); + }, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? Colors.grey : Colors.grey.shade300, + width: 1, + ), + ), + child: CircleAvatar( + radius: 15, + backgroundColor: Colors.white, + child: Text( + abbreviation, + style: TextStyle( + fontSize: 16, + color: isSelected ? Colors.grey : Colors.grey.shade300, + ), + ), + ), + ), + ), + ); + }).toList(), + ), + const SizedBox( + height: 8, + ), + if (state.selectedDaysBinary == '0000000') + BodySmall( + text: 'At least one day must be selected', + style: context.bodyMedium.copyWith(color: ColorsManager.red), + ), + ], + ); + }, + ), + ], + ); + } +} diff --git a/lib/features/scene/widgets/if_then_containers/if_container.dart b/lib/features/scene/widgets/if_then_containers/if_container.dart new file mode 100644 index 0000000..32b832b --- /dev/null +++ b/lib/features/scene/widgets/if_then_containers/if_container.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/view/create_scene_view.dart'; +import 'package:syncrow_app/features/scene/widgets/if_then_containers/then_added_tasks.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/light_divider.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class IFDefaultContainer extends StatelessWidget { + const IFDefaultContainer({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final sceneType = context.read().sceneType; + return DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SceneListTile( + leadingWidget: InkWell( + onTap: () { + if (sceneType.name == + CreateSceneEnum.deviceStatusChanges.name) { + context.customAlertDialog( + hideConfirmButton: true, + alertBody: Column( + children: [ + ListTile( + title: const BodyMedium( + text: "When all conditions are met"), + onTap: () { + context.read().add( + const SelectConditionEvent( + "When all conditions are met")); + Navigator.pop(context); + }, + ), + ListTile( + title: const BodyMedium( + text: "When any condition is met"), + onTap: () { + context.read().add( + const SelectConditionEvent( + "When any condition is met")); + Navigator.pop(context); + }, + ), + ], + ), + title: 'Conditions Rule', + onConfirm: () {}, + ); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + BodyLarge( + text: 'IF', + style: context.bodyLarge.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.primaryTextColor, + ), + ), + Visibility( + visible: sceneType.name == + CreateSceneEnum.deviceStatusChanges.name, + child: BlocBuilder( + builder: (context, state) { + String conditionText = "When any condition is met"; + if (state is AddSceneTask) { + if (state.condition == 'or') { + conditionText = "When any condition is met"; + } else { + conditionText = "When all conditions are met"; + } + } + return SizedBox( + width: context.width * 0.6, + child: Row(children: [ + BodySmall(text: conditionText), + const Icon(Icons.keyboard_arrow_down) + ]), + ); + }, + ), + ), + ], + ), + ), + trailingWidget: BlocBuilder( + builder: (context, state) { + bool isClickable = false; + if (state is AddSceneTask) { + isClickable = + state.automationTasksList?.isNotEmpty ?? false; + } + return GestureDetector( + onTap: isClickable + ? () => Navigator.pushNamed( + context, + Routes.sceneControlDevicesRoute, + arguments: SceneSettingsRouteArguments( + sceneType: + CreateSceneEnum.deviceStatusChanges.name, + sceneId: '', + sceneName: '', + ), + ) + : null, + child: SvgPicture.asset( + Assets.addIcon, + colorFilter: ColorFilter.mode( + isClickable + ? ColorsManager.primaryColor + : ColorsManager.greyColor, + BlendMode.srcIn, + ), + ), + ); + }, + ), + padding: EdgeInsets.zero, + ), + const LightDivider(), + BlocBuilder( + builder: (context, state) { + if (state is AddSceneTask) { + final automationTasksList = state.automationTasksList; + if (automationTasksList?.isNotEmpty == true) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: automationTasksList?.length, + reverse: true, + itemBuilder: (context, index) { + return ThenAddedTasksContainer( + taskItem: automationTasksList![index], + isAutomation: true, + ); + }, + ); + } + } + final sceneType = context.read().sceneType; + if (sceneType.name == CreateSceneEnum.tabToRun.name) { + return const SceneListTile( + padding: EdgeInsets.symmetric(horizontal: 2), + assetPath: Assets.handClickIcon, + titleString: StringsManager.tapToRun, + subtitleString: '', + ); + } + return SceneListTile( + titleString: '+ Add Condition', + textAlign: TextAlign.center, + onPressed: () { + final sceneType = + context.read().sceneType; + if (sceneType.name == CreateSceneEnum.none.name) { + Navigator.push( + context, + CustomPageRoute( + builder: (context) => + const CreateSceneView())); + } else { + Navigator.pushNamed( + context, + Routes.sceneControlDevicesRoute, + arguments: SceneSettingsRouteArguments( + sceneType: + CreateSceneEnum.deviceStatusChanges.name, + sceneId: '', + sceneName: '', + ), + ); + } + }); + }, + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/scene/widgets/if_then_containers/then_added_tasks.dart b/lib/features/scene/widgets/if_then_containers/then_added_tasks.dart new file mode 100644 index 0000000..a291886 --- /dev/null +++ b/lib/features/scene/widgets/if_then_containers/then_added_tasks.dart @@ -0,0 +1,236 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/helper/scene_logic_helper.dart'; +import 'package:syncrow_app/features/scene/helper/scene_operations_data_helper.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; + +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/scene/widgets/select_smart_scene/smart_automation_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +import '../../../../navigation/navigation_service.dart'; + +class ThenAddedTasksContainer extends StatelessWidget + with SceneOperationsDataHelper, SceneLogicHelper { + ThenAddedTasksContainer({ + super.key, + required this.taskItem, + this.sceneId, + this.index, + this.isAutomation, + }); + + final SceneStaticFunction taskItem; + String? sceneId; + int? index; + bool? isAutomation; + + @override + Widget build(BuildContext context) { + final createSceneBloc = context.read(); + String operationValue = ''; + if ((taskItem.code.contains('countdown') && isAutomation != true) || + taskItem.deviceId.contains('delay')) { + final functionValue = + taskItem.functionValue ?? taskItem.operationalValues.first.value; + final duration = + Duration(seconds: int.tryParse(functionValue.toString()) ?? 0); + operationValue = + "${duration.inHours}h ${duration.inMinutes.remainder(60)}m "; + } else if (taskItem.code.contains('temp_set') || + taskItem.code.contains('temp_current')) { + if ((taskItem.functionValue != null || taskItem.functionValue != 0)) { + operationValue = + '${((taskItem.functionValue / 10) as double).toStringAsFixed(1)}°C'; + } else { + operationValue = '0°C'; + } + } else { + var functionValue = + taskItem.functionValue ?? taskItem.operationalValues.first.value; + + if (functionValue is double) { + functionValue = double.parse(functionValue.toStringAsFixed(1)); + } + + operationValue = functionValue.toString(); + } + return DefaultContainer( + onTap: taskItem.operationName == 'tap_to_run' || + taskItem.operationName == 'scene' + ? null + : () { + List functionOperation = []; + + /// get the task functions + functionOperation = List.from(getOperationsForOneFunction( + taskItem: taskItem, + deviceId: taskItem.deviceId, + isAutomation: isAutomation ?? false)); + + if (taskItem.code == CreateSceneEnum.smartSceneSelect.name) { + context.customAlertDialog( + alertBody: EnableDisableAutomationDialog( + automationId: taskItem.deviceId, + descriptionSelected: taskItem.functionValue == 'rule_enable' + ? 'Enable' + : "Disable", + sceneORAutomationName: taskItem.deviceName, + type: taskItem.operationName, + ), + title: taskItem.deviceName, + onConfirm: () { + context + .read() + .add(const SmartSceneConfirmSelectionEvent()); + Navigator.pop(context); + }, + onDismiss: () { + context + .read() + .add(const SmartSceneClearEvent()); + Navigator.pop(context); + }, + ); + } else { + /// show alert dialog based on type + context.customAlertDialog( + alertBody: getTheCorrectDialogBody( + functionOperation.first, null, + isAutomation: isAutomation ?? false), + title: functionOperation.first.operationName, + onConfirm: () { + final savedCode = + functionOperation.first.deviceId.contains('delay') + ? 'delay' + : functionOperation.first.code; + if (isAutomation == true) { + final automationSelectedValue = + createSceneBloc.automationSelectedValues[savedCode]; + + try { + createSceneBloc.add( + UpdateTaskEvent( + newValue: automationSelectedValue, + taskId: taskItem.uniqueCustomId!, + isAutomation: true, + ), + ); + } catch (e) { + debugPrint('Error adding UpdateTaskEvent: $e'); + } + } else { + final selectedValue = + createSceneBloc.selectedValues[savedCode]; + + try { + createSceneBloc.add( + UpdateTaskEvent( + newValue: selectedValue, + taskId: taskItem.uniqueCustomId!, + ), + ); + } catch (e) { + debugPrint('Error adding UpdateTaskEvent: $e'); + } + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.pop( + NavigationService.navigatorKey.currentContext!); + }); + }, + ); + } + }, + padding: EdgeInsets.zero, + child: Dismissible( + key: Key(taskItem.uniqueCustomId.toString()), + background: Container( + padding: const EdgeInsets.only(right: 10), + alignment: AlignmentDirectional.centerEnd, + decoration: const ShapeDecoration( + color: Color(0xFFFF0000), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), + ), + child: SvgPicture.asset( + Assets.assetsDeleteIcon, + width: 20, + height: 22, + ), + ), + direction: DismissDirection.endToStart, + onDismissed: (direction) { + String removeFunctionById = taskItem.uniqueCustomId!; + + if (isAutomation == true) { + context.read().add(RemoveTaskByIdEvent( + taskId: removeFunctionById, + isAutomation: true, + )); + } else { + context.read().add(RemoveTaskByIdEvent( + taskId: removeFunctionById, + )); + } + context + .read() + .add(const SmartSceneClearEvent()); + + String removeFunction = + "${taskItem.operationName} with value ${taskItem.operationalValues.first.value}"; + + CustomSnackBar.displaySnackBar('$removeFunction removed'); + }, + child: SceneListTile( + padding: EdgeInsets.zero, + leadingWidget: SvgPicture.asset( + taskItem.deviceIcon ?? taskItem.icon, + fit: BoxFit.fill, + ), + minLeadingWidth: 32, + titleWidget: BodyMedium( + text: taskItem.deviceName, + style: context.bodyMedium.copyWith( + fontWeight: FontWeight.bold, + ), + ), + subtitleWidget: Row( + children: [ + BodyMedium( + text: getTaskDescription(taskItem), + fontColor: ColorsManager.secondaryTextColor, + fontWeight: FontWeight.normal, + ), + BodyMedium( + text: taskItem.code == CreateSceneEnum.smartSceneSelect.name + ? taskItem.functionValue == 'rule_enable' + ? 'Enable' + : "Disable" + : operationValue, + fontColor: ColorsManager.secondaryTextColor, + fontWeight: FontWeight.normal, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/scene/widgets/if_then_containers/then_container.dart b/lib/features/scene/widgets/if_then_containers/then_container.dart new file mode 100644 index 0000000..193ef78 --- /dev/null +++ b/lib/features/scene/widgets/if_then_containers/then_container.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/view/create_scene_view.dart'; +import 'package:syncrow_app/features/scene/widgets/if_then_containers/then_added_tasks.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/scene/widgets/bottom_sheet_widget.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/light_divider.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class ThenDefaultContainer extends StatelessWidget { + const ThenDefaultContainer({ + super.key, + required this.sceneId, + }); + + final String sceneId; + + @override + Widget build(BuildContext context) { + return DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SceneListTile( + leadingWidget: BodyLarge( + text: 'Then', + style: context.bodyLarge.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.primaryTextColor, + ), + ), + trailingWidget: BlocBuilder( + builder: (context, state) { + bool isClickable = false; + if (state is AddSceneTask) { + isClickable = state.tasksList.isNotEmpty; + } + return GestureDetector( + onTap: isClickable + ? () => context.customBottomSheet( + child: CustomBottomSheetWidget(sceneId: sceneId), + ) + : null, + child: SvgPicture.asset( + Assets.addIcon, + colorFilter: ColorFilter.mode( + isClickable + ? ColorsManager.primaryColor + : ColorsManager.greyColor, + BlendMode.srcIn, + ), + ), + ); + }, + ), + padding: EdgeInsets.zero, + ), + const LightDivider(), + BlocBuilder( + builder: (context, state) { + if (state is CreateSceneLoading) { + return const Center(child: LinearProgressIndicator()); + } + + if (state is AddSceneTask) { + final taskLists = state.tasksList; + if (taskLists.isNotEmpty) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: taskLists.length, + reverse: true, + itemBuilder: (context, index) { + return ThenAddedTasksContainer( + taskItem: taskLists[index], + ); + }, + ); + } + } + return SceneListTile( + titleString: '+ Add Task', + textAlign: TextAlign.center, + onPressed: () { + final sceneType = context.read().sceneType; + if (sceneType.name == CreateSceneEnum.none.name) { + Navigator.push( + context, + CustomPageRoute( + builder: (context) => const CreateSceneView())); + } else { + context.customBottomSheet( + child: CustomBottomSheetWidget( + sceneId: sceneId, + ), + ); + } + }, + ); + }, + ) + ], + ), + ); + } +} diff --git a/lib/features/scene/widgets/scene_devices/scene_devices_body.dart b/lib/features/scene/widgets/scene_devices/scene_devices_body.dart new file mode 100644 index 0000000..b5296b7 --- /dev/null +++ b/lib/features/scene/widgets/scene_devices/scene_devices_body.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_state.dart'; +import 'package:syncrow_app/features/devices/model/room_model.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_state.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SceneDevicesBody extends StatelessWidget { + const SceneDevicesBody({ + super.key, + required TabController tabController, + required this.rooms, + }) : _tabController = tabController; + + final TabController _tabController; + final List? rooms; + + @override + Widget build(BuildContext context) { + final isAutomationDeviceStatus = ((ModalRoute.of(context) + ?.settings + .arguments as SceneSettingsRouteArguments?) + ?.sceneType == + CreateSceneEnum.deviceStatusChanges.name); + return BlocBuilder( + builder: (context, tabState) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + controller: _tabController, + dividerColor: Colors.transparent, + indicatorColor: Colors.transparent, + tabs: [ + ...rooms!.map((e) => Tab( + child: BodyLarge( + text: e.name ?? '', + textAlign: TextAlign.start, + style: context.bodyLarge.copyWith( + color: (tabState is TabSelected) && + tabState.roomId == e.id + ? ColorsManager.textPrimaryColor + : ColorsManager.textPrimaryColor.withOpacity(0.2), + ), + ), + )), + ], + isScrollable: true, + tabAlignment: TabAlignment.start, + ), + Expanded( + child: TabBarView( + controller: _tabController, + physics: const NeverScrollableScrollPhysics(), + children: rooms! + .map((e) => + _buildRoomTab(e, context, isAutomationDeviceStatus)) + .toList(), + ), + ), + ], + ); + }, + ); + } + + Widget _buildRoomTab( + RoomModel room, BuildContext context, bool isAutomationDeviceStatus) { + return BlocBuilder( + builder: (context, state) { + if (state.loading && state.devices == null) { + return const Center(child: CircularProgressIndicator()); + } else if (state.devices != null && state.devices!.isNotEmpty) { + return ListView.builder( + itemCount: state.devices!.length, + itemBuilder: (context, index) { + final device = state.devices![index]; + return DefaultContainer( + child: SceneListTile( + minLeadingWidth: 40, + leadingWidget: SvgPicture.asset(device.icon ?? ''), + titleWidget: BodyMedium( + text: device.name ?? '', + style: context.titleSmall.copyWith( + color: ColorsManager.secondaryTextColor, + fontWeight: FontWeight.w400, + fontSize: 20, + ), + ), + trailingWidget: const Icon( + Icons.arrow_forward_ios_rounded, + color: ColorsManager.greyColor, + size: 16, + ), + onPressed: () { + Navigator.pushNamed( + context, + Routes.deviceFunctionsRoute, + arguments: { + "device": device, + "isAutomationDeviceStatus": isAutomationDeviceStatus + }, + ); + }, + ), + ); + }, + ); + } else if (state.error != null) { + return const Center(child: Text('Something went wrong')); + } else { + return const SizedBox(); + } + }, + ); + } +} diff --git a/lib/features/scene/widgets/scene_list_tile.dart b/lib/features/scene/widgets/scene_list_tile.dart new file mode 100644 index 0000000..b97c977 --- /dev/null +++ b/lib/features/scene/widgets/scene_list_tile.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SceneListTile extends StatelessWidget { + const SceneListTile({ + super.key, + this.assetPath, + this.titleString, + this.leadingWidget, + this.trailingWidget, + this.padding, + this.textAlign, + this.onPressed, + this.assetHeight, + this.minLeadingWidth, + this.titleWidget, + this.subtitleString, + this.subtitleWidget, + this.iconsSize, + this.assetHeaderValue, + }); + final String? assetPath; + final String? titleString; + final Widget? subtitleWidget; + final String? subtitleString; + final Widget? leadingWidget; + final Widget? trailingWidget; + final EdgeInsetsGeometry? padding; + final TextAlign? textAlign; + final void Function()? onPressed; + final double? assetHeight; + final double? minLeadingWidth; + final Widget? titleWidget; + final double? iconsSize; + final String? assetHeaderValue; + + @override + Widget build(BuildContext context) { + return ListTile( + minLeadingWidth: minLeadingWidth ?? 40, + leading: leadingWidget ?? + (assetPath != null + ? SizedBox( + width: iconsSize, + child: SvgPicture.asset( + assetPath ?? Assets.assetsImagesLogo, + width: iconsSize, + height: assetHeight ?? 35, + alignment: Alignment.center, + fit: BoxFit.fitWidth, + ), + ) + : null), + trailing: trailingWidget, + contentPadding: padding, + title: titleWidget ?? + BodyMedium( + text: titleString ?? '', + textAlign: textAlign, + style: context.bodyMedium.copyWith(fontSize: 15), + ), + subtitle: subtitleWidget ?? + (subtitleString?.isNotEmpty == true + ? BodySmall( + text: subtitleString ?? '', + style: context.bodySmall.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.greyColor), + ) + : null), + onTap: onPressed, + ); + } +} diff --git a/lib/features/scene/widgets/scene_view_no_scenes.dart b/lib/features/scene/widgets/scene_view_no_scenes.dart new file mode 100644 index 0000000..d3496dc --- /dev/null +++ b/lib/features/scene/widgets/scene_view_no_scenes.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class SceneViewNoScenes extends StatelessWidget { + const SceneViewNoScenes({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + Assets.assetsImagesAutomation, + scale: 1, + opacity: const AlwaysStoppedAnimation(.5), + width: 140, + ), + const SizedBox(height: 15), + const Text( + 'Home automation saves your time and effort by automating routine tasks.', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey), + ), + const SizedBox(height: 20), + const DefaultButton( + child: Text( + 'Add a scene', + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/scene/widgets/scene_view_widget/scene_grid_view.dart b/lib/features/scene/widgets/scene_view_widget/scene_grid_view.dart new file mode 100644 index 0000000..f690fbb --- /dev/null +++ b/lib/features/scene/widgets/scene_view_widget/scene_grid_view.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_item.dart'; + +class SceneGrid extends StatelessWidget { + final List scenes; + final String? loadingSceneId; + final bool disablePlayButton; + final Map loadingStates; + + const SceneGrid({ + required this.scenes, + required this.loadingSceneId, + required this.disablePlayButton, + required this.loadingStates, + super.key, + }); + + @override + Widget build(BuildContext context) { + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + childAspectRatio: 1.5, + ), + itemCount: scenes.length, + itemBuilder: (context, index) { + final scene = scenes[index]; + final isLoading = loadingSceneId == scene.id; + final isSwitchLoading = loadingStates[scene.id] ?? false; + + return SceneItem( + scene: scene, + isLoading: isLoading, + disablePlayButton: disablePlayButton, + isSwitchLoading: isSwitchLoading, + ); + }, + ); + } +} diff --git a/lib/features/scene/widgets/scene_view_widget/scene_header.dart b/lib/features/scene/widgets/scene_view_widget/scene_header.dart new file mode 100644 index 0000000..1be7542 --- /dev/null +++ b/lib/features/scene/widgets/scene_view_widget/scene_header.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; + +import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; + +class SceneHeader extends StatelessWidget { + const SceneHeader({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleMedium( + text: StringsManager.routine, + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + ), + ), + // SizedBox(height: 20), + // BodySmall( + // text: StringsManager.tapToRunRoutine, + // ), + ], + ); + } +} diff --git a/lib/features/scene/widgets/scene_view_widget/scene_item.dart b/lib/features/scene/widgets/scene_view_widget/scene_item.dart new file mode 100644 index 0000000..53c1c1d --- /dev/null +++ b/lib/features/scene/widgets/scene_view_widget/scene_item.dart @@ -0,0 +1,136 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; +import 'package:syncrow_app/features/scene/model/scenes_model.dart'; +import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/model/update_automation.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; + +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SceneItem extends StatelessWidget { + final ScenesModel scene; + final bool isLoading; + final bool isSwitchLoading; + final bool disablePlayButton; + + const SceneItem({ + required this.scene, + required this.isLoading, + super.key, + required this.disablePlayButton, + required this.isSwitchLoading, + }); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + onTap: () { + context.read().add(const SmartSceneClearEvent()); + if (disablePlayButton == false) { + BlocProvider.of(context).add( + FetchSceneTasksEvent(sceneId: scene.id, isAutomation: false)); + + /// the state to set the scene type must be after the fetch + BlocProvider.of(context) + .add(const SceneTypeEvent(CreateSceneEnum.tabToRun)); + } else { + BlocProvider.of(context) + .add(FetchSceneTasksEvent(sceneId: scene.id, isAutomation: true)); + + /// the state to set the scene type must be after the fetch + BlocProvider.of(context) + .add(const SceneTypeEvent(CreateSceneEnum.deviceStatusChanges)); + } + + Navigator.pushNamed( + context, + Routes.sceneTasksRoute, + arguments: SceneSettingsRouteArguments( + sceneType: disablePlayButton == false + ? CreateSceneEnum.tabToRun.name + : CreateSceneEnum.deviceStatusChanges.name, + sceneId: scene.id, + sceneName: scene.name, + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset( + height: 32, + width: 32, + Assets.assetsIconsLogo, + fit: BoxFit.fill, + ), + disablePlayButton == false + ? IconButton( + padding: EdgeInsets.zero, + onPressed: () { + context + .read() + .add(SceneTrigger(scene.id, scene.name)); + }, + icon: isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : const Icon( + Icons.play_circle, + size: 40, + color: ColorsManager.greyColor, + ), + ) + : isSwitchLoading + ? Center( + child: CircularProgressIndicator( + color: ColorsManager.primaryColorWithOpacity, + ), + ) + : CupertinoSwitch( + activeColor: ColorsManager.primaryColor, + value: scene.status == 'enable' ? true : false, + onChanged: (value) { + context.read().add( + UpdateAutomationStatus( + automationStatusUpdate: + AutomationStatusUpdate( + isEnable: value, + unitUuid: HomeCubit.getInstance() + .selectedSpace! + .id!), + automationId: scene.id)); + }, + ), + ], + ), + const SizedBox(height: 10), + FittedBox( + child: BodyMedium( + text: scene.name, + maxLines: 1, + overflow: TextOverflow.fade, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/scene/widgets/select_smart_scene/smart_automation_list.dart b/lib/features/scene/widgets/select_smart_scene/smart_automation_list.dart new file mode 100644 index 0000000..66d9df0 --- /dev/null +++ b/lib/features/scene/widgets/select_smart_scene/smart_automation_list.dart @@ -0,0 +1,314 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/features/scene/model/scenes_model.dart'; +import 'package:syncrow_app/features/scene/model/smart_scene_enable.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SmartSceneSelectAutomationList extends StatefulWidget { + const SmartSceneSelectAutomationList({ + super.key, + required this.automationList, + required this.automationId, + }); + + final List automationList; + final String automationId; + + @override + State createState() => + _SmartSceneSelectAutomationListState(); +} + +class _SmartSceneSelectAutomationListState + extends State { + final List colorList = _generateDarkColors(100); + + static List _generateDarkColors(int count) { + final random = Random(); + final colors = []; + + while (colors.length < count) { + final color = + Color((random.nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0); + if (_isDark(color)) { + colors.add(color); + } + } + return colors; + } + + static bool _isDark(Color color) { + final brightness = + ((color.red * 299) + (color.green * 587) + (color.blue * 114)) / 1000; + return brightness < 128; + } + + @override + Widget build(BuildContext context) { + // Filter the automation list to exclude the sceneId + final filteredAutomationList = widget.automationList + .where((e) => e.id != widget.automationId.toString()) + .toList(); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Expanded( + // child: TextButton( + // onPressed: () => Navigator.pop(context), + // child: BodySmall( + // text: 'Save', + // style: context.bodySmall + // .copyWith(color: ColorsManager.primaryColorWithOpacity), + // )), + // ), + const Spacer(), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: BodyMedium( + text: 'Automation', + textAlign: TextAlign.center, + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold), + ), + ), + ), + const Spacer(), + ], + ), + const Divider( + color: ColorsManager.dividerColor, + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + final smartSceneEnable = ((state is SmartSceneSelected)) + ? state.smartSceneEnable + : context.read().smartSceneEnable; + + return ListView.builder( + shrinkWrap: true, + itemCount: filteredAutomationList.length, + itemBuilder: (context, index) { + final automation = filteredAutomationList[index]; + final isSelected = + smartSceneEnable?.entityId == automation.id; + final descriptionSelected = isSelected + ? (smartSceneEnable?.actionExecutor == 'rule_enable' + ? 'Enable' + : 'Disable') + : automation.status == 'enable' + ? 'Enable' + : 'Disable'; + + return SceneListTile( + padding: const EdgeInsets.symmetric(horizontal: 10), + leadingWidget: CircleAvatar( + backgroundColor: colorList[index % colorList.length], + radius: 15, + child: Text( + index.toString(), + style: context.bodyMedium.copyWith(color: Colors.white), + ), + ), + titleString: automation.name, + trailingWidget: TextButton.icon( + iconAlignment: IconAlignment.end, + onPressed: () { + context.customAlertDialog( + alertBody: EnableDisableAutomationDialog( + automationId: automation.id, + descriptionSelected: descriptionSelected, + sceneORAutomationName: automation.name, + type: automation.type, + ), + title: automation.name, + onConfirm: () { + context + .read() + .add(const SmartSceneConfirmSelectionEvent()); + Navigator.pop(context); + }, + onDismiss: () { + context + .read() + .add(const SmartSceneClearEvent()); + Navigator.pop(context); + }, + ); + }, + label: BodyMedium( + text: _capitalizeFirst(descriptionSelected), + style: context.bodyMedium + .copyWith(color: ColorsManager.greyColor), + ), + icon: const Icon( + Icons.arrow_forward_ios_rounded, + color: ColorsManager.greyColor, + size: 14, + ), + style: const ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.zero), + ), + ), + onPressed: () { + context.customAlertDialog( + alertBody: EnableDisableAutomationDialog( + automationId: automation.id, + descriptionSelected: descriptionSelected, + sceneORAutomationName: automation.name, + type: automation.type, + ), + title: automation.name, + onConfirm: () { + context + .read() + .add(const SmartSceneConfirmSelectionEvent()); + Navigator.pop(context); + }, + onDismiss: () { + context + .read() + .add(const SmartSceneClearEvent()); + Navigator.pop(context); + }, + ); + }, + ); + }, + ); + }, + ), + ), + ], + ); + } + + String _capitalizeFirst(String description) { + if (description.isEmpty) return description; + return '${description[0].toUpperCase()}${description.substring(1)}'; + } +} + +class EnableDisableAutomationDialog extends StatefulWidget { + const EnableDisableAutomationDialog({ + super.key, + required this.automationId, + required this.descriptionSelected, + required this.sceneORAutomationName, + required this.type, + }); + + final String automationId; + final String descriptionSelected; + final String sceneORAutomationName; + final String type; + + @override + State createState() => + _EnableDisableAutomationDialogState(); +} + +class _EnableDisableAutomationDialogState + extends State { + String? groupValue; + final List values = [ + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: 'Enable', + value: 'rule_enable', + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: 'Disable', + value: 'rule_disable', + ), + ]; + + @override + void initState() { + super.initState(); + groupValue = + widget.descriptionSelected == 'Enable' ? 'rule_enable' : 'rule_disable'; + + context.read().add(SmartSceneEnableEvent( + SmartSceneEnable( + entityId: widget.automationId, + actionExecutor: groupValue!, + sceneORAutomationName: widget.sceneORAutomationName, + type: widget.type, + isAutomation: true))); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...values.map( + (operation) => SceneListTile( + iconsSize: 25, + minLeadingWidth: 15, + padding: const EdgeInsets.symmetric(horizontal: 16), + assetPath: operation.icon, + assetHeaderValue: operation.iconValue, + titleString: operation.description.toString(), + textAlign: TextAlign.start, + trailingWidget: Radio( + value: operation.value, + groupValue: groupValue, + onChanged: (value) { + setState(() { + groupValue = value; + }); + context + .read() + .add(SmartSceneEnableEvent( + SmartSceneEnable( + entityId: widget.automationId, + actionExecutor: value!, + sceneORAutomationName: + widget.sceneORAutomationName, + type: widget.type, + isAutomation: true), + )); + }, + ), + onPressed: () { + setState(() { + groupValue = operation.value; + }); + context + .read() + .add(SmartSceneEnableEvent( + SmartSceneEnable( + entityId: widget.automationId, + actionExecutor: operation.value, + sceneORAutomationName: widget.sceneORAutomationName, + type: widget.type, + isAutomation: true, + ), + )); + }, + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/features/scene/widgets/select_smart_scene/smart_enable_autoamtion.dart b/lib/features/scene/widgets/select_smart_scene/smart_enable_autoamtion.dart new file mode 100644 index 0000000..6560c63 --- /dev/null +++ b/lib/features/scene/widgets/select_smart_scene/smart_enable_autoamtion.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; + +import 'package:syncrow_app/features/scene/widgets/select_smart_scene/smart_automation_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; + +import 'package:syncrow_app/utils/context_extension.dart'; + +class SmartEnableAutomation extends StatelessWidget { + const SmartEnableAutomation({super.key, required this.automationId}); + + final String automationId; + + @override + Widget build(BuildContext context) { + return DefaultContainer( + height: context.height * 0.5, + width: double.infinity, + child: BlocBuilder( + bloc: context.read() + ..add( + LoadAutomation(HomeCubit.getInstance().selectedSpace?.id ?? '')), + builder: (context, state) { + if (state is SceneLoading) { + return const Align( + alignment: Alignment.topCenter, + child: LinearProgressIndicator()); + } + if (state is SceneLoaded) { + return SmartSceneSelectAutomationList( + automationId: automationId, + automationList: state.automationList); + } + return const SizedBox.shrink(); + }, + ), + ); + } +} diff --git a/lib/features/scene/widgets/select_smart_scene/smart_enable_tab_run.dart b/lib/features/scene/widgets/select_smart_scene/smart_enable_tab_run.dart new file mode 100644 index 0000000..37a5e7c --- /dev/null +++ b/lib/features/scene/widgets/select_smart_scene/smart_enable_tab_run.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; +import 'package:syncrow_app/features/scene/widgets/select_smart_scene/smart_tab_run_list.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class SmartEnableTabRun extends StatelessWidget { + const SmartEnableTabRun({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + height: context.height * 0.5, + width: double.infinity, + child: BlocBuilder( + bloc: context.read() + ..add(LoadScenes(HomeCubit.getInstance().selectedSpace?.id ?? '')), + builder: (context, state) { + if (state is SceneLoading) { + return const Align( + alignment: Alignment.topCenter, + child: LinearProgressIndicator()); + } + if (state is SceneLoaded) { + return SmartSceneSelectTabToRunList(scenes: state.scenes); + } + return const SizedBox.shrink(); + }, + ), + ); + } +} + diff --git a/lib/features/scene/widgets/select_smart_scene/smart_tab_run_list.dart b/lib/features/scene/widgets/select_smart_scene/smart_tab_run_list.dart new file mode 100644 index 0000000..660b58e --- /dev/null +++ b/lib/features/scene/widgets/select_smart_scene/smart_tab_run_list.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/features/scene/model/scenes_model.dart'; +import 'package:syncrow_app/features/scene/model/smart_scene_enable.dart'; +import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SmartSceneSelectTabToRunList extends StatefulWidget { + const SmartSceneSelectTabToRunList({ + super.key, + required this.scenes, + }); + final List scenes; + + @override + State createState() => _SmartSceneSelectTabToRunListState(); +} + +class _SmartSceneSelectTabToRunListState extends State { + String groupValue = ''; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Expanded( + // child: TextButton( + // onPressed: () => Navigator.pop(context), + // child: BodySmall( + // text: 'Save', + // style: context.bodySmall + // .copyWith(color: ColorsManager.primaryColorWithOpacity), + // )), + // ), + const Spacer(), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: BodyMedium( + text: 'Tab To Run', + textAlign: TextAlign.center, + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, fontWeight: FontWeight.bold), + ), + ), + ), + const Spacer(), + ], + ), + const Divider( + color: ColorsManager.dividerColor, + ), + Expanded( + child: ListView.builder( + shrinkWrap: true, + itemCount: widget.scenes.length, + itemBuilder: (context, index) { + final scene = widget.scenes[index]; + final selectedScene = context.read().smartSceneEnable; + if (selectedScene != null) { + if (scene.id == selectedScene.entityId) { + groupValue = scene.id; + } + } + return SceneListTile( + padding: const EdgeInsets.symmetric(horizontal: 10), + leadingWidget: Image.asset( + height: 32, + width: 32, + Assets.assetsIconsLogo, + fit: BoxFit.fill, + ), + titleString: scene.name, + trailingWidget: Radio( + value: scene.id, + groupValue: groupValue, + onChanged: (String? value) { + if (value != null) { + setState(() { + groupValue = value; + }); + + context + .read() + .add(SmartSceneEnableEvent(SmartSceneEnable( + entityId: scene.id, + actionExecutor: 'rule_trigger', + sceneORAutomationName: scene.name, + type: scene.type, + isAutomation: false, + ))); + context + .read() + .add(const SmartSceneConfirmSelectionEvent()); + } + }), + onPressed: () { + setState(() { + groupValue = scene.id; + }); + context.read().add(SmartSceneEnableEvent(SmartSceneEnable( + entityId: scene.id, + actionExecutor: 'rule_trigger', + sceneORAutomationName: scene.name, + type: scene.type, + isAutomation: false, + ))); + context + .read() + .add(const SmartSceneConfirmSelectionEvent()); + }, + ); + }), + ), + ], + ); + } +} diff --git a/lib/features/shared_widgets/bottom_sheet/custom_bottom_sheet.dart b/lib/features/shared_widgets/bottom_sheet/custom_bottom_sheet.dart new file mode 100644 index 0000000..9124286 --- /dev/null +++ b/lib/features/shared_widgets/bottom_sheet/custom_bottom_sheet.dart @@ -0,0 +1,45 @@ + +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class CustomBottomSheet extends StatelessWidget { + const CustomBottomSheet({ + super.key, + this.child, + this.height, + this.maxHeight, + this.radius = 20, + this.withClosed = true, + }); + final Widget? child; + final double? height; + final double? maxHeight; + final double radius; + final bool withClosed; + @override + Widget build(BuildContext context) { + final bottom = MediaQuery.of(context).viewInsets.bottom; + return SafeArea( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: height ?? 250, + maxHeight: maxHeight ?? context.height * 0.8, + ), + child: SingleChildScrollView( + keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, + physics: bottom == 0 + ? const NeverScrollableScrollPhysics() + : const BouncingScrollPhysics(), + child: AnimatedContainer( + duration: const Duration(milliseconds: 50), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.only(bottom: bottom), + child: child ?? const SizedBox(), + ), + ), + ), + ); + } +} diff --git a/lib/features/shared_widgets/create_unit.dart b/lib/features/shared_widgets/create_unit.dart new file mode 100644 index 0000000..6ecaf49 --- /dev/null +++ b/lib/features/shared_widgets/create_unit.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; + +class CreateUnitWidget extends StatelessWidget { + const CreateUnitWidget({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.noUnitsIconDashboard, + width: 100, + height: 100, + ), + const SizedBox( + height: 50, + ), + Flexible( + child: GestureDetector( + onTap: () { + Navigator.pushNamed(context, Routes.createUnit); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 34, vertical: 14), + decoration: ShapeDecoration( + color: const Color(0x99023DFE), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: const TitleMedium( + text: 'Create a unit', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.white), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/shared_widgets/custom_switch.dart b/lib/features/shared_widgets/custom_switch.dart new file mode 100644 index 0000000..85262d0 --- /dev/null +++ b/lib/features/shared_widgets/custom_switch.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:collection/collection.dart'; + +class CustomSwitch extends StatelessWidget { + const CustomSwitch({super.key, required this.device}); + + final DeviceModel device; + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + bool? status; + if (device.status.isNotEmpty) { + status = device.status + .firstWhereOrNull((status) => status.code == "switch") + ?.value; + } + return status == null + ? const SizedBox() + : GestureDetector( + onTap: () { + DevicesCubit.getInstance().deviceControl( + DeviceControlModel( + deviceId: device.uuid, + code: device.status + .firstWhere((status) => status.code == "switch") + .code, + value: !device.status + .firstWhere((status) => status.code == "switch") + .value!, + ), + device.uuid!, + ); + }, + child: Container( + width: 45.0, + height: 28.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24.0), + color: status + ? ColorsManager.primaryColor + : const Color(0xFFD9D9D9)), + child: Center( + child: Container( + width: 40.0, + height: 23.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24.0), + color: Colors.white, + ), + child: Padding( + padding: const EdgeInsets.all(2.0), + child: Container( + alignment: status + ? Alignment.centerRight + : Alignment.centerLeft, + child: Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: status + ? ColorsManager.primaryColor + : Colors.grey, + ), + ), + ), + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/shared_widgets/default_button.dart b/lib/features/shared_widgets/default_button.dart new file mode 100644 index 0000000..09ad431 --- /dev/null +++ b/lib/features/shared_widgets/default_button.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DefaultButton extends StatelessWidget { + const DefaultButton({ + super.key, + this.enabled = true, + this.onPressed, + required this.child, + this.isSecondary = false, + this.isLoading = false, + this.isDone = false, + this.customTextStyle, + this.customButtonStyle, + this.backgroundColor, + this.foregroundColor, + this.borderRadius, + this.height, + this.padding, + }); + + final void Function()? onPressed; + final Widget child; + final double? height; + final bool isSecondary; + final double? borderRadius; + final bool enabled; + final double? padding; + final bool isDone; + final bool isLoading; + + final TextStyle? customTextStyle; + + final ButtonStyle? customButtonStyle; + + final Color? backgroundColor; + + final Color? foregroundColor; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: enabled ? onPressed : null, + style: isSecondary + ? null + : customButtonStyle ?? + ButtonStyle( + textStyle: MaterialStateProperty.all( + customTextStyle ?? + context.bodyMedium.copyWith( + fontSize: 16, + color: foregroundColor, + ), + ), + foregroundColor: MaterialStateProperty.all( + isSecondary + ? Colors.black + : enabled + ? foregroundColor ?? Colors.white + : Colors.black, + ), + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return enabled + ? backgroundColor ?? ColorsManager.primaryColor + : Colors.grey; + }), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 20), + ), + ), + fixedSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + padding: MaterialStateProperty.all( + EdgeInsets.all(padding ?? 10), + ), + minimumSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + ), + child: SizedBox( + height: height ?? 50, + child: Center( + child: isLoading + ? const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : isDone + ? const Icon( + Icons.check_circle_outline, + color: Colors.white, + ) + : child, + ), + ), + ); + } +} diff --git a/lib/features/shared_widgets/default_container.dart b/lib/features/shared_widgets/default_container.dart new file mode 100644 index 0000000..e0a71b0 --- /dev/null +++ b/lib/features/shared_widgets/default_container.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class DefaultContainer extends StatelessWidget { + const DefaultContainer({ + super.key, + required this.child, + this.height, + this.width, + this.color, + this.boxConstraints, + this.margin, + this.padding, + this.onTap, + this.borderRadius, + }); + + final double? height; + final double? width; + final Widget child; + final BoxConstraints? boxConstraints; + final EdgeInsets? margin; + final EdgeInsets? padding; + final Color? color; + final Function()? onTap; + final BorderRadius? borderRadius; + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(20), + child: Container( + height: height, + width: width, + margin: margin ?? const EdgeInsets.only(right: 3, bottom: 3), + constraints: boxConstraints, + decoration: BoxDecoration( + color: color ?? Colors.white, + borderRadius: borderRadius ?? BorderRadius.circular(20), + ), + padding: padding ?? const EdgeInsets.all(10), + child: child, + ), + ); + } +} diff --git a/lib/features/shared_widgets/default_scaffold.dart b/lib/features/shared_widgets/default_scaffold.dart new file mode 100644 index 0000000..a88e828 --- /dev/null +++ b/lib/features/shared_widgets/default_scaffold.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class DefaultScaffold extends StatelessWidget { + const DefaultScaffold({ + super.key, + required this.child, + this.title, + this.actions, + this.appBar, + this.bottomNavBar, + this.padding, + this.leading, + this.leadingWidth, + this.height, + }); + + final Widget child; + final String? title; + final List? actions; + final PreferredSizeWidget? appBar; + final Widget? bottomNavBar; + final EdgeInsetsGeometry? padding; + final Widget? leading; + final double? leadingWidth; + final double? height; + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: ColorsManager.primaryColor.withOpacity(0.5), + statusBarIconBrightness: Brightness.dark, // For Android (dark icons) + statusBarBrightness: Brightness.dark, // For iOS (dark icons) + ), + child: Scaffold( + backgroundColor: ColorsManager.backgroundColor, + extendBodyBehindAppBar: true, + extendBody: true, + appBar: appBar ?? + AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: BodyLarge( + text: title ?? "", + fontColor: ColorsManager.secondaryColor, + fontWeight: FontsManager.bold, + ), + actions: actions, + leading: leading, + leadingWidth: leadingWidth, + ), + body: Container( + width: MediaQuery.sizeOf(context).width, + height: height ?? MediaQuery.sizeOf(context).height, + padding: padding ?? + const EdgeInsets.symmetric(horizontal: Constants.defaultPadding), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + opacity: 0.4, + ), + ), + child: SafeArea(child: child), + ), + bottomNavigationBar: bottomNavBar, + ), + ); + } +} diff --git a/lib/features/shared_widgets/devices_default_switch.dart b/lib/features/shared_widgets/devices_default_switch.dart new file mode 100644 index 0000000..54c88bb --- /dev/null +++ b/lib/features/shared_widgets/devices_default_switch.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DevicesDefaultSwitch extends StatelessWidget { + const DevicesDefaultSwitch( + {super.key, required this.switchValue, required this.action, this.secondAction}); + + final bool switchValue; + final Function action; + final Function? secondAction; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: InkWell( + onTap: () { + action(); + }, + child: Container( + height: 60, + decoration: BoxDecoration( + color: switchValue ? ColorsManager.primaryColor : Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + bottomLeft: Radius.circular(15), + ), + ), + child: Center( + child: BodyMedium( + text: "ON", + fontColor: switchValue ? Colors.white : null, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + Expanded( + child: InkWell( + onTap: () { + if (secondAction != null) { + secondAction!(); + } else { + action(); + } + }, + child: Container( + height: 60, + decoration: BoxDecoration( + color: switchValue ? Colors.white : ColorsManager.primaryColor, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + ), + child: Center( + child: BodyMedium( + text: "OFF", + fontColor: switchValue ? null : Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/features/shared_widgets/door_lock_button.dart b/lib/features/shared_widgets/door_lock_button.dart new file mode 100644 index 0000000..a178ea1 --- /dev/null +++ b/lib/features/shared_widgets/door_lock_button.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DoorLockButton extends StatelessWidget { + const DoorLockButton({ + super.key, + this.enabled = true, + this.onPressed, + required this.child, + this.isSecondary = false, + this.isLoading = false, + this.isDone = false, + this.customTextStyle, + this.customButtonStyle, + this.backgroundColor, + this.foregroundColor, + this.borderRadius, + this.height, + this.padding, + }); + + final void Function()? onPressed; + final Widget child; + final double? height; + final bool isSecondary; + final double? borderRadius; + final bool enabled; + final double? padding; + final bool isDone; + final bool isLoading; + final TextStyle? customTextStyle; + + final ButtonStyle? customButtonStyle; + + final Color? backgroundColor; + + final Color? foregroundColor; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: enabled ? onPressed : null, + style: isSecondary + ? null + : customButtonStyle ?? + ButtonStyle( + textStyle: MaterialStateProperty.all( + customTextStyle ?? + context.bodyMedium.copyWith( + fontSize: 16, + color: foregroundColor, + ), + ), + foregroundColor: MaterialStateProperty.all( + isSecondary + ? Colors.black + : enabled + ? foregroundColor ?? Colors.white + : Colors.black, + ), + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return enabled + ? backgroundColor ?? ColorsManager.primaryColor + : Colors.grey; + }), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 20), + ), + ), + fixedSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + padding: MaterialStateProperty.all( + EdgeInsets.all(padding ?? 10), + ), + minimumSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + ), + child: SizedBox( + height: height ?? 50, + child: Center( + child: isLoading + ? const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : isDone + ? const Text('Done') :child , + ), + ), + ); + } +} diff --git a/lib/features/shared_widgets/light_divider.dart b/lib/features/shared_widgets/light_divider.dart new file mode 100644 index 0000000..7bc81bd --- /dev/null +++ b/lib/features/shared_widgets/light_divider.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class LightDivider extends StatelessWidget { + const LightDivider({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return const Divider( + color: ColorsManager.greyColor, + thickness: 0.3, + ); + } +} diff --git a/lib/features/shared_widgets/syncrow_logo.dart b/lib/features/shared_widgets/syncrow_logo.dart new file mode 100644 index 0000000..17166fe --- /dev/null +++ b/lib/features/shared_widgets/syncrow_logo.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class SyncrowLogo extends StatelessWidget { + const SyncrowLogo({ + super.key, + this.isDark = true, + this.width = 150, + }); + + final bool isDark; + + final double width; + + @override + Widget build(BuildContext context) { + return Image.asset( + isDark ? Assets.assetsImagesBlackLogo : Assets.assetsImagesWhiteLogo, + scale: 1, + width: width); + } +} diff --git a/lib/features/shared_widgets/text_widgets/body_large.dart b/lib/features/shared_widgets/text_widgets/body_large.dart new file mode 100644 index 0000000..5cc0819 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/body_large.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +import 'custom_text_widget.dart'; + +class BodyLarge extends StatelessWidget { + const BodyLarge( + {required this.text, + super.key, + this.textAlign, + this.style, + this.height, + this.fontWeight, + this.fontColor, + this.fontSize, + this.textOverflow}); + + final String text; + final TextAlign? textAlign; + + final TextStyle? style; + + final double? height; + + final FontWeight? fontWeight; + + final Color? fontColor; + + final double? fontSize; + + final TextOverflow? textOverflow; + + @override + Widget build(BuildContext context) => CustomText( + text, + textAlign: textAlign, + style: style ?? + context.bodyLarge.copyWith( + height: height, + fontWeight: fontWeight, + color: fontColor, + fontSize: fontSize, + overflow: textOverflow), + ); +} diff --git a/lib/features/shared_widgets/text_widgets/body_medium.dart b/lib/features/shared_widgets/text_widgets/body_medium.dart new file mode 100644 index 0000000..9301849 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/body_medium.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/custom_text_widget.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class BodyMedium extends StatelessWidget { + const BodyMedium({ + required this.text, + super.key, + this.style, + this.maxLines, + this.overflow, + this.textAlign, + this.fontSize, + this.fontColor, + this.fontWeight, + }); + + final String text; + final TextStyle? style; + final int? maxLines; + final TextOverflow? overflow; + + final TextAlign? textAlign; + final double? fontSize; + + final Color? fontColor; + final FontWeight? fontWeight; + + @override + Widget build(BuildContext context) => CustomText( + text, + style: style ?? + context.bodyMedium.copyWith( + fontSize: fontSize, color: fontColor, fontWeight: fontWeight), + // softWrap: true, + maxLines: maxLines, + // overflow: overflow, + textAlign: textAlign, + ); +} diff --git a/lib/features/shared_widgets/text_widgets/body_small.dart b/lib/features/shared_widgets/text_widgets/body_small.dart new file mode 100644 index 0000000..ec04027 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/body_small.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/custom_text_widget.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class BodySmall extends StatelessWidget { + const BodySmall({ + required this.text, + super.key, + this.style, + this.fontColor, + this.fontSize, + this.fontWeight, + this.textAlign, + this.textOverflow, + }); + + final String text; + final TextStyle? style; + + final Color? fontColor; + + final double? fontSize; + final FontWeight? fontWeight; + + final TextAlign? textAlign; + final TextOverflow? textOverflow; + + @override + Widget build(BuildContext context) => CustomText( + text, + style: style ?? context.bodySmall, + fontColor: fontColor, + fontSize: fontSize, + fontWeight: fontWeight, + textAlign: textAlign, + textOverflow: textOverflow, + ); +} diff --git a/lib/features/shared_widgets/text_widgets/custom_text_widget.dart b/lib/features/shared_widgets/text_widgets/custom_text_widget.dart new file mode 100644 index 0000000..42b755d --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/custom_text_widget.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +class CustomText extends StatelessWidget { + const CustomText(this.text, + {super.key, + this.style, + this.textAlign, + this.onTap, + this.minLines, + this.maxLines, + this.textDirection, + this.textOverflow, + this.fontSize, + this.fontColor, + this.fontWeight}); + + final String text; + final TextStyle? style; + final TextAlign? textAlign; + final Function()? onTap; + final int? minLines; + final int? maxLines; + final TextDirection? textDirection; + final TextOverflow? textOverflow; + + final double? fontSize; + final Color? fontColor; + + final FontWeight? fontWeight; + + @override + Widget build(BuildContext context) { + //was SelectableText + return Text( + softWrap: true, + text, + style: style, + textAlign: textAlign, + // onTap: onTap, + // minLines: minLines, + overflow: textOverflow, + maxLines: maxLines, + textDirection: textDirection, + ); + } +} diff --git a/lib/features/shared_widgets/text_widgets/display_large.dart b/lib/features/shared_widgets/text_widgets/display_large.dart new file mode 100644 index 0000000..3bd9888 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/display_large.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/custom_text_widget.dart'; + +class DisplayLarge extends StatelessWidget { + const DisplayLarge({ + required this.text, + super.key, + this.style, + this.textAlign, + }); + + final String text; + final TextStyle? style; + + final TextAlign? textAlign; + + @override + Widget build(BuildContext context) => CustomText( + text, + style: style ?? Theme.of(context).textTheme.displayLarge, + textAlign: textAlign, + // softWrap: true, + ); +} diff --git a/lib/features/shared_widgets/text_widgets/display_medium.dart b/lib/features/shared_widgets/text_widgets/display_medium.dart new file mode 100644 index 0000000..2561dc3 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/display_medium.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +import 'custom_text_widget.dart'; + +class DisplayMedium extends StatelessWidget { + const DisplayMedium({ + required this.text, + super.key, + this.style, + }); + + final String text; + final TextStyle? style; + + @override + Widget build(BuildContext context) => CustomText( + text, + style: style ?? Theme.of(context).textTheme.displayMedium, + ); +} diff --git a/lib/features/shared_widgets/text_widgets/display_small.dart b/lib/features/shared_widgets/text_widgets/display_small.dart new file mode 100644 index 0000000..f94d0e4 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/display_small.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +import 'custom_text_widget.dart'; + +class DisplaySmall extends StatelessWidget { + const DisplaySmall({ + required this.text, + super.key, + this.textAlign, + }); + + final String text; + final TextAlign? textAlign; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.only(bottom: 20), + child: CustomText( + text, + style: context.displaySmall, + textAlign: textAlign, + ), + ); +} diff --git a/lib/features/shared_widgets/text_widgets/title_large.dart b/lib/features/shared_widgets/text_widgets/title_large.dart new file mode 100644 index 0000000..8edba90 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/title_large.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +class TitleLarge extends StatelessWidget { + const TitleLarge({ + required this.text, + super.key, + }); + + final String text; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.only(top: 20, bottom: 10), + child: SelectableText( + text, + style: context.titleLarge, + ), + ); +} diff --git a/lib/features/shared_widgets/text_widgets/title_medium.dart b/lib/features/shared_widgets/text_widgets/title_medium.dart new file mode 100644 index 0000000..4a26916 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/title_medium.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +import 'custom_text_widget.dart'; + +class TitleMedium extends StatelessWidget { + const TitleMedium({ + required this.text, + super.key, + this.maxLines, + this.textAlign, + this.style, + }); + + final String text; + final int? maxLines; + final TextAlign? textAlign; + + final TextStyle? style; + + @override + Widget build(BuildContext context) => CustomText( + text, + style: style ?? context.titleMedium, + maxLines: maxLines, + textAlign: textAlign, + ); +} diff --git a/lib/features/shared_widgets/text_widgets/title_small.dart b/lib/features/shared_widgets/text_widgets/title_small.dart new file mode 100644 index 0000000..73a5f85 --- /dev/null +++ b/lib/features/shared_widgets/text_widgets/title_small.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +import 'custom_text_widget.dart'; + +class TitleSmall extends StatelessWidget { + const TitleSmall({ + required this.text, + super.key, + this.style, + this.textAlign, + }); + + final String text; + final TextStyle? style; + final TextAlign? textAlign; + + @override + Widget build(BuildContext context) => CustomText( + text, + style: style ?? context.titleSmall, + textAlign: textAlign, + ); +} diff --git a/lib/features/shared_widgets/united_text.dart b/lib/features/shared_widgets/united_text.dart new file mode 100644 index 0000000..461d146 --- /dev/null +++ b/lib/features/shared_widgets/united_text.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; + +class UnitedText extends StatelessWidget { + const UnitedText({ + super.key, + required this.value, + required this.unit, + this.valueStyle, + this.unitStyle, + this.valueSize, + this.valueWeight, + this.valueColor, + this.unitSize, + this.unitWeight, + this.unitColor, + }); + + final String value; + + final TextStyle? valueStyle; + final double? valueSize; + final FontWeight? valueWeight; + + final Color? valueColor; + final String unit; + + final TextStyle? unitStyle; + final double? unitSize; + final FontWeight? unitWeight; + final Color? unitColor; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + BodyLarge( + text: value, + style: valueStyle ?? + TextStyle( + fontSize: valueSize ?? 20, + fontWeight: valueWeight ?? FontWeight.bold, + color: valueColor, + height: 0, + ), + ), + BodySmall( + text: unit, + style: unitStyle ?? + TextStyle( + fontSize: unitSize ?? 10, + fontWeight: unitWeight, + color: unitColor, + ), + ), + ], + ); + } +} diff --git a/lib/features/splash/view/splash_view.dart b/lib/features/splash/view/splash_view.dart new file mode 100644 index 0000000..bc81f87 --- /dev/null +++ b/lib/features/splash/view/splash_view.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/auth/view/login_view.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; + +class SplashView extends StatefulWidget { + const SplashView({super.key}); + + @override + State createState() => _SplashViewState(); +} + +class _SplashViewState extends State { + @override + void initState() { + AuthCubit.get(context).getTokenAndValidate(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is AuthTokenSuccess) { + Navigator.pushNamedAndRemoveUntil(context, Routes.homeRoute, (route) => false); + } else if (state is AuthTokenError) { + Future.delayed(const Duration(seconds: 2), () { + Navigator.pushReplacement( + context, + CustomPageRoute(builder: (context) => const LoginView()), + ); + }); + } + }, + builder: (context, state) { + return Scaffold( + body: Stack( + alignment: Alignment.center, + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.assetsImagesBackground, + ), + fit: BoxFit.cover, + ), + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.assetsImagesVector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + SvgPicture.asset( + Assets.assetsImagesLogo, + width: 240, + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/features/splash/view/widgets/user_agreement_dialog.dart b/lib/features/splash/view/widgets/user_agreement_dialog.dart new file mode 100644 index 0000000..f178ee5 --- /dev/null +++ b/lib/features/splash/view/widgets/user_agreement_dialog.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; + +class UserAgreementDialog extends StatelessWidget { + const UserAgreementDialog({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + elevation: 40, + title: const Text('User Agreement'), + content: + const Text('By using this app you agree to the terms and conditions'), + actions: [ + DefaultButton( + child: const Text( + 'Login', + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + DefaultButton( + onPressed: () => Navigator.of(context).pop(), + isSecondary: true, + child: const Text( + 'Cancel', + ), + ), + ], + ); + } +} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..6a97585 --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,76 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0', + appId: '1:427332280600:android:bb6047adeeb80fb00c7e6d', + messagingSenderId: '427332280600', + projectId: 'test2-8a3d2', + storageBucket: 'test2-8a3d2.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw', + appId: '1:427332280600:ios:373a65cce55a3af40c7e6d', + messagingSenderId: '427332280600', + projectId: 'test2-8a3d2', + storageBucket: 'test2-8a3d2.appspot.com', + iosBundleId: 'com.example.syncrowApp', + ); + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyCVEvKsJYzhWDFM-9Od68FE0nPpP933st0', + appId: '1:427332280600:web:ad50516a87a35a1a0c7e6d', + messagingSenderId: '427332280600', + projectId: 'test2-8a3d2', + authDomain: 'test2-8a3d2.firebaseapp.com', + storageBucket: 'test2-8a3d2.appspot.com', + measurementId: 'G-Z1RTTTV5H9', + ); + +} \ No newline at end of file diff --git a/lib/generated/Assets.assetsIconsDart b/lib/generated/Assets.assetsIconsDart new file mode 100644 index 0000000..e11bfb2 --- /dev/null +++ b/lib/generated/Assets.assetsIconsDart @@ -0,0 +1,636 @@ +class Assets { + Assets._(); + + /// Assets for assetsFontsAftikaRegular + /// assets/fonts/AftikaRegular.ttf + static const String assetsFontsAftikaRegular = "assets/fonts/AftikaRegular.ttf"; + + /// Assets for assetsIcons3GangSwitch + /// assets/icons/3GangSwitch.svg + static const String assetsIcons3GangSwitch = "assets/icons/3GangSwitch.svg"; + + /// Assets for assetsIconsAC + /// assets/icons/AC.svg + static const String assetsIconsAC = "assets/icons/AC.svg"; + + /// Assets for assetsIconsActive + /// assets/icons/active.svg + static const String assetsIconsActive = "assets/icons/active.svg"; + + /// Assets for assetsIconsAutomatedClock + /// assets/icons/automated_clock.svg + static const String assetsIconsAutomatedClock = "assets/icons/automated_clock.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstChargeddmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstChargeddmOff = "assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstDefaultdmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstDefaultdmOff = "assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOffpmOnstChargeddmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOffpmOnstChargeddmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOfflowOffpmOnstChargeddmOff = "assets/icons/battery/dmOff/perOffchargOfflowOffpmOnstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOnpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOnpmOffstDefaultdmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOfflowOnpmOffstDefaultdmOff = "assets/icons/battery/dmOff/perOffchargOfflowOnpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOnpmOnstDefaultdmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOnpmOnstDefaultdmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOfflowOnpmOnstDefaultdmOff = "assets/icons/battery/dmOff/perOffchargOfflowOnpmOnstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOnlowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOffchargOnlowOffpmOffstChargeddmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOnlowOffpmOffstChargeddmOff = "assets/icons/battery/dmOff/perOffchargOnlowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOnlowOnpmOffstlowBatterydmOff + /// assets/icons/battery/dmOff/perOffchargOnlowOnpmOffstlowBatterydmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOnlowOnpmOffstlowBatterydmOff = "assets/icons/battery/dmOff/perOffchargOnlowOnpmOffstlowBatterydmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOnlowOnpmOnstlowpmdmOff + /// assets/icons/battery/dmOff/perOffchargOnlowOnpmOnstlowpmdmOff.svg + static const String assetsIconsBatteryDmOffPerOffchargOnlowOnpmOnstlowpmdmOff = "assets/icons/battery/dmOff/perOffchargOnlowOnpmOnstlowpmdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstChargeddmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstChargeddmOff = "assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstDefaultdmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstDefaultdmOff = "assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOffpmOnstChargeddmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOffpmOnstChargeddmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOfflowOffpmOnstChargeddmOff = "assets/icons/battery/dmOff/perOnchargOfflowOffpmOnstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOnpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOnpmOffstDefaultdmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOfflowOnpmOffstDefaultdmOff = "assets/icons/battery/dmOff/perOnchargOfflowOnpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOnpmOnstDefaultdmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOnpmOnstDefaultdmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOfflowOnpmOnstDefaultdmOff = "assets/icons/battery/dmOff/perOnchargOfflowOnpmOnstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOnlowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOnchargOnlowOffpmOffstChargeddmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOnlowOffpmOffstChargeddmOff = "assets/icons/battery/dmOff/perOnchargOnlowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOnlowOnpmOffstlowBatterydmOff + /// assets/icons/battery/dmOff/perOnchargOnlowOnpmOffstlowBatterydmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOnlowOnpmOffstlowBatterydmOff = "assets/icons/battery/dmOff/perOnchargOnlowOnpmOffstlowBatterydmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOnlowOnpmOnstlowpmdmOff + /// assets/icons/battery/dmOff/perOnchargOnlowOnpmOnstlowpmdmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOnlowOnpmOnstlowpmdmOff = "assets/icons/battery/dmOff/perOnchargOnlowOnpmOnstlowpmdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstChargeddmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstChargeddmOn = "assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstDefaultdmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstDefaultdmOn = "assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOffpmOnstChargeddmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOffpmOnstChargeddmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOfflowOffpmOnstChargeddmOn = "assets/icons/battery/dmOn/perOffchargOfflowOffpmOnstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOnpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOnpmOffstDefaultdmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOfflowOnpmOffstDefaultdmOn = "assets/icons/battery/dmOn/perOffchargOfflowOnpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOnpmOnstDefaultdmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOnpmOnstDefaultdmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOfflowOnpmOnstDefaultdmOn = "assets/icons/battery/dmOn/perOffchargOfflowOnpmOnstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOnlowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOffchargOnlowOffpmOffstChargeddmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOnlowOffpmOffstChargeddmOn = "assets/icons/battery/dmOn/perOffchargOnlowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOnlowOnpmOffstlowBatterydmOn + /// assets/icons/battery/dmOn/perOffchargOnlowOnpmOffstlowBatterydmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOnlowOnpmOffstlowBatterydmOn = "assets/icons/battery/dmOn/perOffchargOnlowOnpmOffstlowBatterydmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOnlowOnpmOnstlowpmdmOn + /// assets/icons/battery/dmOn/perOffchargOnlowOnpmOnstlowpmdmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOnlowOnpmOnstlowpmdmOn = "assets/icons/battery/dmOn/perOffchargOnlowOnpmOnstlowpmdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstChargeddmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstChargeddmOn = "assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstDefaultdmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstDefaultdmOn = "assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOffpmOnstChargeddmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOffpmOnstChargeddmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOfflowOffpmOnstChargeddmOn = "assets/icons/battery/dmOn/perOnchargOfflowOffpmOnstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOnpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOnpmOffstDefaultdmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOfflowOnpmOffstDefaultdmOn = "assets/icons/battery/dmOn/perOnchargOfflowOnpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOnpmOnstDefaultdmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOnpmOnstDefaultdmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOfflowOnpmOnstDefaultdmOn = "assets/icons/battery/dmOn/perOnchargOfflowOnpmOnstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOnlowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOnchargOnlowOffpmOffstChargeddmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOnlowOffpmOffstChargeddmOn = "assets/icons/battery/dmOn/perOnchargOnlowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOnlowOnpmOffstlowBatterydmOn + /// assets/icons/battery/dmOn/perOnchargOnlowOnpmOffstlowBatterydmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOnlowOnpmOffstlowBatterydmOn = "assets/icons/battery/dmOn/perOnchargOnlowOnpmOffstlowBatterydmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOnlowOnpmOnstlowpmdmOn + /// assets/icons/battery/dmOn/perOnchargOnlowOnpmOnstlowpmdmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOnlowOnpmOnstlowpmdmOn = "assets/icons/battery/dmOn/perOnchargOnlowOnpmOnstlowpmdmOn.svg"; + + /// Assets for assetsIconsCO2 + /// assets/icons/CO2.svg + static const String assetsIconsCO2 = "assets/icons/CO2.svg"; + + /// Assets for assetsIconsColdMode + /// assets/icons/coldMode.svg + static const String assetsIconsColdMode = "assets/icons/coldMode.svg"; + + /// Assets for assetsIconsColorWheel + /// assets/icons/color_wheel.svg + static const String assetsIconsColorWheel = "assets/icons/color_wheel.svg"; + + /// Assets for assetsIconsCurtain + /// assets/icons/Curtain.svg + static const String assetsIconsCurtain = "assets/icons/Curtain.svg"; + + /// Assets for assetsIconsCurtainsIconCloseCurtain + /// assets/icons/curtainsIcon/closeCurtain.svg + static const String assetsIconsCurtainsIconCloseCurtain = "assets/icons/curtainsIcon/closeCurtain.svg"; + + /// Assets for assetsIconsCurtainsIconCurtainHolder + /// assets/icons/curtainsIcon/curtainHolder.svg + static const String assetsIconsCurtainsIconCurtainHolder = "assets/icons/curtainsIcon/curtainHolder.svg"; + + /// Assets for assetsIconsCurtainsIconOpenCurtain + /// assets/icons/curtainsIcon/openCurtain.svg + static const String assetsIconsCurtainsIconOpenCurtain = "assets/icons/curtainsIcon/openCurtain.svg"; + + /// Assets for assetsIconsCurtainsIconVerticalBlade + /// assets/icons/curtainsIcon/verticalBlade.svg + static const String assetsIconsCurtainsIconVerticalBlade = "assets/icons/curtainsIcon/verticalBlade.svg"; + + /// Assets for assetsIconsDashboard + /// assets/icons/dashboard.svg + static const String assetsIconsDashboard = "assets/icons/dashboard.svg"; + + /// Assets for assetsIconsDashboardFill + /// assets/icons/dashboard-fill.svg + static const String assetsIconsDashboardFill = "assets/icons/dashboard-fill.svg"; + + /// Assets for assetsIconsDevices + /// assets/icons/Devices.svg + static const String assetsIconsDevices = "assets/icons/Devices.svg"; + + /// Assets for assetsIconsDevicesFill + /// assets/icons/Devices-fill.svg + static const String assetsIconsDevicesFill = "assets/icons/Devices-fill.svg"; + + /// Assets for assetsIconsDoorLock + /// assets/icons/doorLock.svg + static const String assetsIconsDoorLock = "assets/icons/doorLock.svg"; + + /// Assets for assetsIconsDoorLockLinkage + /// assets/icons/DoorLockLinkage.svg + static const String assetsIconsDoorLockLinkage = "assets/icons/DoorLockLinkage.svg"; + + /// Assets for assetsIconsDoorLockLock + /// assets/icons/DoorLockLock.svg + static const String assetsIconsDoorLockLock = "assets/icons/DoorLockLock.svg"; + + /// Assets for assetsIconsDoorLockMembers + /// assets/icons/DoorLockMembers.svg + static const String assetsIconsDoorLockMembers = "assets/icons/DoorLockMembers.svg"; + + /// Assets for assetsIconsDoorLockPassword + /// assets/icons/DoorLockPassword.svg + static const String assetsIconsDoorLockPassword = "assets/icons/DoorLockPassword.svg"; + + /// Assets for assetsIconsDoorLockRecords + /// assets/icons/DoorLockRecords.svg + static const String assetsIconsDoorLockRecords = "assets/icons/DoorLockRecords.svg"; + + /// Assets for assetsIconsDoorlockAssetsBatteryIndicator + /// assets/icons/doorlock-assets/BatteryIndicator.svg + static const String assetsIconsDoorlockAssetsBatteryIndicator = "assets/icons/doorlock-assets/BatteryIndicator.svg"; + + /// Assets for assetsIconsDoorlockAssetsDoorlockButtonOne + /// assets/icons/doorlock-assets/doorlock-button-one.svg + static const String assetsIconsDoorlockAssetsDoorlockButtonOne = "assets/icons/doorlock-assets/doorlock-button-one.svg"; + + /// Assets for assetsIconsDoorlockAssetsDoorlockButtonTwo + /// assets/icons/doorlock-assets/doorlock-button-two.svg + static const String assetsIconsDoorlockAssetsDoorlockButtonTwo = "assets/icons/doorlock-assets/doorlock-button-two.svg"; + + /// Assets for assetsIconsDoorlockAssetsLockIcon + /// assets/icons/doorlock-assets/lockIcon.svg + static const String assetsIconsDoorlockAssetsLockIcon = "assets/icons/doorlock-assets/lockIcon.svg"; + + /// Assets for assetsIconsDoorlockAssetsMembersManagement + /// assets/icons/doorlock-assets/members-management.svg + static const String assetsIconsDoorlockAssetsMembersManagement = "assets/icons/doorlock-assets/members-management.svg"; + + /// Assets for assetsIconsDoorlockAssetsSmartLinkage + /// assets/icons/doorlock-assets/smart-linkage.svg + static const String assetsIconsDoorlockAssetsSmartLinkage = "assets/icons/doorlock-assets/smart-linkage.svg"; + + /// Assets for assetsIconsDoorlockAssetsTemporaryPassword + /// assets/icons/doorlock-assets/temporary-password.svg + static const String assetsIconsDoorlockAssetsTemporaryPassword = "assets/icons/doorlock-assets/temporary-password.svg"; + + /// Assets for assetsIconsDoorlockAssetsUnlockingRecords + /// assets/icons/doorlock-assets/unlocking-records.svg + static const String assetsIconsDoorlockAssetsUnlockingRecords = "assets/icons/doorlock-assets/unlocking-records.svg"; + + /// Assets for assetsIconsFacebook + /// assets/icons/Facebook.svg + static const String assetsIconsFacebook = "assets/icons/Facebook.svg"; + + /// Assets for assetsIconsFan0 + /// assets/icons/fan-0.svg + static const String assetsIconsFan0 = "assets/icons/fan-0.svg"; + + /// Assets for assetsIconsFan1 + /// assets/icons/fan-1.svg + static const String assetsIconsFan1 = "assets/icons/fan-1.svg"; + + /// Assets for assetsIconsFan2 + /// assets/icons/fan-2.svg + static const String assetsIconsFan2 = "assets/icons/fan-2.svg"; + + /// Assets for assetsIconsFan3 + /// assets/icons/fan-3.svg + static const String assetsIconsFan3 = "assets/icons/fan-3.svg"; + + /// Assets for assetsIconsFilter + /// assets/icons/filter.png + static const String assetsIconsFilter = "assets/icons/filter.png"; + + /// Assets for assetsIconsFrequency + /// assets/icons/frequency.svg + static const String assetsIconsFrequency = "assets/icons/frequency.svg"; + + /// Assets for assetsIconsGang + /// assets/icons/gang.svg + static const String assetsIconsGang = "assets/icons/gang.svg"; + + /// Assets for assetsIconsGateway + /// assets/icons/Gateway.svg + static const String assetsIconsGateway = "assets/icons/Gateway.svg"; + + /// Assets for assetsIconsGoogle + /// assets/icons/Google.svg + static const String assetsIconsGoogle = "assets/icons/Google.svg"; + + /// Assets for assetsIconsHome + /// assets/icons/home.svg + static const String assetsIconsHome = "assets/icons/home.svg"; + + /// Assets for assetsIconsHot1 + /// assets/icons/hot1.jpg + static const String assetsIconsHot1 = "assets/icons/hot1.jpg"; + + /// Assets for assetsIconsKalvin + /// assets/icons/kalvin.svg + static const String assetsIconsKalvin = "assets/icons/kalvin.svg"; + + /// Assets for assetsIconsLayout + /// assets/icons/Layout.svg + static const String assetsIconsLayout = "assets/icons/Layout.svg"; + + /// Assets for assetsIconsLayoutFill + /// assets/icons/Layout-fill.svg + static const String assetsIconsLayoutFill = "assets/icons/Layout-fill.svg"; + + /// Assets for assetsIconsLight + /// assets/icons/Light.svg + static const String assetsIconsLight = "assets/icons/Light.svg"; + + /// Assets for assetsIconsLightSwitchOff + /// assets/icons/lightSwitchOff.svg + static const String assetsIconsLightSwitchOff = "assets/icons/lightSwitchOff.svg"; + + /// Assets for assetsIconsLightSwitchOn + /// assets/icons/lightSwitchOn.svg + static const String assetsIconsLightSwitchOn = "assets/icons/lightSwitchOn.svg"; + + /// Assets for assetsIconsLinkageIconsDoorLockAlarm + /// assets/icons/linkageIcons/doorLockAlarm.svg + static const String assetsIconsLinkageIconsDoorLockAlarm = "assets/icons/linkageIcons/doorLockAlarm.svg"; + + /// Assets for assetsIconsLinkageIconsFamilyHome + /// assets/icons/linkageIcons/familyHome.svg + static const String assetsIconsLinkageIconsFamilyHome = "assets/icons/linkageIcons/familyHome.svg"; + + /// Assets for assetsIconsLock + /// assets/icons/lock.svg + static const String assetsIconsLock = "assets/icons/lock.svg"; + + /// Assets for assetsIconsLogo + /// assets/icons/logo.png + static const String assetsIconsLogo = "assets/icons/logo.png"; + + /// Assets for assetsIconsMenu + /// assets/icons/Menu.svg + static const String assetsIconsMenu = "assets/icons/Menu.svg"; + + /// Assets for assetsIconsMenuFill + /// assets/icons/Menu-fill.svg + static const String assetsIconsMenuFill = "assets/icons/Menu-fill.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsClearCach + /// assets/icons/MenuIcons/GeneralSettingsIcons/clearCach.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsClearCach = "assets/icons/MenuIcons/GeneralSettingsIcons/clearCach.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsLanguage + /// assets/icons/MenuIcons/GeneralSettingsIcons/language.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsLanguage = "assets/icons/MenuIcons/GeneralSettingsIcons/language.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsNetworkDiagnosis + /// assets/icons/MenuIcons/GeneralSettingsIcons/networkDiagnosis.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsNetworkDiagnosis = "assets/icons/MenuIcons/GeneralSettingsIcons/networkDiagnosis.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsTemperatureUnit + /// assets/icons/MenuIcons/GeneralSettingsIcons/temperatureUnit.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsTemperatureUnit = "assets/icons/MenuIcons/GeneralSettingsIcons/temperatureUnit.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsTouchTone + /// assets/icons/MenuIcons/GeneralSettingsIcons/touchTone.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsTouchTone = "assets/icons/MenuIcons/GeneralSettingsIcons/touchTone.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsVoiceAssistant + /// assets/icons/MenuIcons/GeneralSettingsIcons/voiceAssistant.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsVoiceAssistant = "assets/icons/MenuIcons/GeneralSettingsIcons/voiceAssistant.svg"; + + /// Assets for assetsIconsMenuIconsHomeManagementIconsCreateHome + /// assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg + static const String assetsIconsMenuIconsHomeManagementIconsCreateHome = "assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg"; + + /// Assets for assetsIconsMenuIconsHomeManagementIconsJoinAHome + /// assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg + static const String assetsIconsMenuIconsHomeManagementIconsJoinAHome = "assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg"; + + /// Assets for assetsIconsMenuIconsHomeManagementIconsManageYourHome + /// assets/icons/MenuIcons/HomeManagementIcons/ManageYourHome.svg + static const String assetsIconsMenuIconsHomeManagementIconsManageYourHome = "assets/icons/MenuIcons/HomeManagementIcons/ManageYourHome.svg"; + + /// Assets for assetsIconsMenuIconsLeagalInfoIconsAbout + /// assets/icons/MenuIcons/LeagalInfoIcons/About.svg + static const String assetsIconsMenuIconsLeagalInfoIconsAbout = "assets/icons/MenuIcons/LeagalInfoIcons/About.svg"; + + /// Assets for assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy + /// assets/icons/MenuIcons/LeagalInfoIcons/PrivacyPolicy.svg + static const String assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy = "assets/icons/MenuIcons/LeagalInfoIcons/PrivacyPolicy.svg"; + + /// Assets for assetsIconsMenuIconsLeagalInfoIconsUserAgreement + /// assets/icons/MenuIcons/LeagalInfoIcons/UserAgreement.svg + static const String assetsIconsMenuIconsLeagalInfoIconsUserAgreement = "assets/icons/MenuIcons/LeagalInfoIcons/UserAgreement.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsAlerts + /// assets/icons/MenuIcons/MessagesCenterIcons/Alerts.svg + static const String assetsIconsMenuIconsMessagesCenterIconsAlerts = "assets/icons/MenuIcons/MessagesCenterIcons/Alerts.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsFAQs + /// assets/icons/MenuIcons/MessagesCenterIcons/FAQs.svg + static const String assetsIconsMenuIconsMessagesCenterIconsFAQs = "assets/icons/MenuIcons/MessagesCenterIcons/FAQs.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsHelpAndFeedback + /// assets/icons/MenuIcons/MessagesCenterIcons/HelpAndFeedback.svg + static const String assetsIconsMenuIconsMessagesCenterIconsHelpAndFeedback = "assets/icons/MenuIcons/MessagesCenterIcons/HelpAndFeedback.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsMessages + /// assets/icons/MenuIcons/MessagesCenterIcons/Messages.svg + static const String assetsIconsMenuIconsMessagesCenterIconsMessages = "assets/icons/MenuIcons/MessagesCenterIcons/Messages.svg"; + + /// Assets for assetsIconsMenuIconsSecurityAndPrivacyIconsPrivacy + /// assets/icons/MenuIcons/SecurityAndPrivacyIcons/Privacy.svg + static const String assetsIconsMenuIconsSecurityAndPrivacyIconsPrivacy = "assets/icons/MenuIcons/SecurityAndPrivacyIcons/Privacy.svg"; + + /// Assets for assetsIconsMenuIconsSecurityAndPrivacyIconsSecurty + /// assets/icons/MenuIcons/SecurityAndPrivacyIcons/Securty.svg + static const String assetsIconsMenuIconsSecurityAndPrivacyIconsSecurty = "assets/icons/MenuIcons/SecurityAndPrivacyIcons/Securty.svg"; + + /// Assets for assetsIconsMinus + /// assets/icons/minus.svg + static const String assetsIconsMinus = "assets/icons/minus.svg"; + + /// Assets for assetsIconsPlus + /// assets/icons/plus.svg + static const String assetsIconsPlus = "assets/icons/plus.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsDistance + /// assets/icons/presence-sensor-assets/Distance.svg + static const String assetsIconsPresenceSensorAssetsDistance = "assets/icons/presence-sensor-assets/Distance.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsEmpty + /// assets/icons/presence-sensor-assets/Empty.svg + static const String assetsIconsPresenceSensorAssetsEmpty = "assets/icons/presence-sensor-assets/Empty.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsHelpDescription + /// assets/icons/presence-sensor-assets/help-description.svg + static const String assetsIconsPresenceSensorAssetsHelpDescription = "assets/icons/presence-sensor-assets/help-description.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsIlluminanceRecord + /// assets/icons/presence-sensor-assets/Illuminance-Record.svg + static const String assetsIconsPresenceSensorAssetsIlluminanceRecord = "assets/icons/presence-sensor-assets/Illuminance-Record.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsIlluminanceValue + /// assets/icons/presence-sensor-assets/Illuminance-Value.svg + static const String assetsIconsPresenceSensorAssetsIlluminanceValue = "assets/icons/presence-sensor-assets/Illuminance-Value.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsIndicator + /// assets/icons/presence-sensor-assets/Indicator.svg + static const String assetsIconsPresenceSensorAssetsIndicator = "assets/icons/presence-sensor-assets/Indicator.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsInductionRecording + /// assets/icons/presence-sensor-assets/induction-recording.svg + static const String assetsIconsPresenceSensorAssetsInductionRecording = "assets/icons/presence-sensor-assets/induction-recording.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsParameterSettings + /// assets/icons/presence-sensor-assets/parameter-settings.svg + static const String assetsIconsPresenceSensorAssetsParameterSettings = "assets/icons/presence-sensor-assets/parameter-settings.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsPresence + /// assets/icons/presence-sensor-assets/Presence.svg + static const String assetsIconsPresenceSensorAssetsPresence = "assets/icons/presence-sensor-assets/Presence.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsPresenceSensorMotion + /// assets/icons/presence-sensor-assets/presence-sensor-motion.svg + static const String assetsIconsPresenceSensorAssetsPresenceSensorMotion = "assets/icons/presence-sensor-assets/presence-sensor-motion.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsRecord + /// assets/icons/presence-sensor-assets/Record.svg + static const String assetsIconsPresenceSensorAssetsRecord = "assets/icons/presence-sensor-assets/Record.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsTime + /// assets/icons/presence-sensor-assets/Time.svg + static const String assetsIconsPresenceSensorAssetsTime = "assets/icons/presence-sensor-assets/Time.svg"; + + /// Assets for assetsIconsRoutines + /// assets/icons/Routines.svg + static const String assetsIconsRoutines = "assets/icons/Routines.svg"; + + /// Assets for assetsIconsRoutinesFill + /// assets/icons/Routines-fill.svg + static const String assetsIconsRoutinesFill = "assets/icons/Routines-fill.svg"; + + /// Assets for assetsIconsScan + /// assets/icons/Scan.svg + static const String assetsIconsScan = "assets/icons/Scan.svg"; + + /// Assets for assetsIconsScreen + /// assets/icons/Screen.svg + static const String assetsIconsScreen = "assets/icons/Screen.svg"; + + /// Assets for assetsIconsSensors + /// assets/icons/sensors.svg + static const String assetsIconsSensors = "assets/icons/sensors.svg"; + + /// Assets for assetsIconsSettings + /// assets/icons/settings.svg + static const String assetsIconsSettings = "assets/icons/settings.svg"; + + /// Assets for assetsIconsSettings + /// assets/icons/settings.png + static const String assetsIconsSettings = "assets/icons/settings.png"; + + /// Assets for assetsIconsSummer + /// assets/icons/Summer.svg + static const String assetsIconsSummer = "assets/icons/Summer.svg"; + + /// Assets for assetsIconsSummerMode + /// assets/icons/summer_mode.svg + static const String assetsIconsSummerMode = "assets/icons/summer_mode.svg"; + + /// Assets for assetsIconsSunnyMode + /// assets/icons/sunnyMode.svg + static const String assetsIconsSunnyMode = "assets/icons/sunnyMode.svg"; + + /// Assets for assetsIconsSustainability + /// assets/icons/sustainability.svg + static const String assetsIconsSustainability = "assets/icons/sustainability.svg"; + + /// Assets for assetsIconsUnlockingMethodsIconsFace + /// assets/icons/unlockingMethodsIcons/face.svg + static const String assetsIconsUnlockingMethodsIconsFace = "assets/icons/unlockingMethodsIcons/face.svg"; + + /// Assets for assetsIconsUnlockingMethodsIconsFingerprint + /// assets/icons/unlockingMethodsIcons/fingerprint.svg + static const String assetsIconsUnlockingMethodsIconsFingerprint = "assets/icons/unlockingMethodsIcons/fingerprint.svg"; + + /// Assets for assetsIconsUnlockingMethodsIconsRemote + /// assets/icons/unlockingMethodsIcons/remote.svg + static const String assetsIconsUnlockingMethodsIconsRemote = "assets/icons/unlockingMethodsIcons/remote.svg"; + + /// Assets for assetsIconsVector + /// assets/icons/Vector.svg + static const String assetsIconsVector = "assets/icons/Vector.svg"; + + /// Assets for assetsIconsVector1 + /// assets/icons/Vector-1.svg + static const String assetsIconsVector1 = "assets/icons/Vector-1.svg"; + + /// Assets for assetsIconsVoltMeter + /// assets/icons/volt-meter.svg + static const String assetsIconsVoltMeter = "assets/icons/volt-meter.svg"; + + /// Assets for assetsIconsWifi + /// assets/icons/Wifi.svg + static const String assetsIconsWifi = "assets/icons/Wifi.svg"; + + /// Assets for assetsIconsWindyMode + /// assets/icons/windyMode.svg + static const String assetsIconsWindyMode = "assets/icons/windyMode.svg"; + + /// Assets for assetsIconsWinter + /// assets/icons/Winter.svg + static const String assetsIconsWinter = "assets/icons/Winter.svg"; + + /// Assets for assetsIconsWinter1 + /// assets/icons/winter1.jpg + static const String assetsIconsWinter1 = "assets/icons/winter1.jpg"; + + /// Assets for assetsIconsWinterMode + /// assets/icons/Winter_mode.svg + static const String assetsIconsWinterMode = "assets/icons/Winter_mode.svg"; + + /// Assets for assetsImagesAutomation + /// assets/images/automation.jpg + static const String assetsImagesAutomation = "assets/images/automation.jpg"; + + /// Assets for assetsImagesBackground + /// assets/images/Background.png + static const String assetsImagesBackground = "assets/images/Background.png"; + + /// Assets for assetsImagesBlackLogo + /// assets/images/black-logo.png + static const String assetsImagesBlackLogo = "assets/images/black-logo.png"; + + /// Assets for assetsImagesBlind + /// assets/images/blind.png + static const String assetsImagesBlind = "assets/images/blind.png"; + + /// Assets for assetsImagesBoxEmpty + /// assets/images/box-empty.jpg + static const String assetsImagesBoxEmpty = "assets/images/box-empty.jpg"; + + /// Assets for assetsImagesCurtain + /// assets/images/curtain.png + static const String assetsImagesCurtain = "assets/images/curtain.png"; + + /// Assets for assetsImagesDown + /// assets/images/Down.png + static const String assetsImagesDown = "assets/images/Down.png"; + + /// Assets for assetsImagesHorizintalBlade + /// assets/images/HorizintalBlade.png + static const String assetsImagesHorizintalBlade = "assets/images/HorizintalBlade.png"; + + /// Assets for assetsImagesLogo + /// assets/images/Logo.svg + static const String assetsImagesLogo = "assets/images/Logo.svg"; + + /// Assets for assetsImagesLogoHorizontal + /// assets/images/logo_horizontal.png + static const String assetsImagesLogoHorizontal = "assets/images/logo_horizontal.png"; + + /// Assets for assetsImagesPause + /// assets/images/Pause.png + static const String assetsImagesPause = "assets/images/Pause.png"; + + /// Assets for assetsImagesTestDash + /// assets/images/test_dash.png + static const String assetsImagesTestDash = "assets/images/test_dash.png"; + + /// Assets for assetsImagesTestDash2 + /// assets/images/test_dash2.png + static const String assetsImagesTestDash2 = "assets/images/test_dash2.png"; + + /// Assets for assetsImagesUp + /// assets/images/Up.png + static const String assetsImagesUp = "assets/images/Up.png"; + + /// Assets for assetsImagesVector + /// assets/images/Vector.png + static const String assetsImagesVector = "assets/images/Vector.png"; + + /// Assets for assetsImagesWhiteLogo + /// assets/images/white-logo.png + static const String assetsImagesWhiteLogo = "assets/images/white-logo.png"; + + /// Assets for assetsImagesWindow + /// assets/images/Window.png + static const String assetsImagesWindow = "assets/images/Window.png"; +} + diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart new file mode 100644 index 0000000..caae2da --- /dev/null +++ b/lib/generated/assets.dart @@ -0,0 +1,1032 @@ +class Assets { + Assets._(); + + /// Assets for assetsFontsAftikaRegular + /// assets/fonts/AftikaRegular.ttf + static const String assetsFontsAftikaRegular = + "assets/fonts/AftikaRegular.ttf"; + + /// Assets for assetsIcons3GangSwitch + /// assets/icons/3GangSwitch.svg + static const String assetsIcons3GangSwitch = "assets/icons/3GangSwitch.svg"; + + /// Assets for assetsIconsAC + /// assets/icons/AC.svg + static const String assetsIconsAC = "assets/icons/AC.svg"; + + /// Assets for assetsIconsActive + /// assets/icons/active.svg + static const String assetsIconsActive = "assets/icons/active.svg"; + + /// Assets for assetsIconsAutomatedClock + /// assets/icons/automated_clock.svg + static const String assetsIconsAutomatedClock = + "assets/icons/automated_clock.svg"; + static const String acSwitchIcon = "assets/icons/ac_switch_ic.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstChargeddmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstChargeddmOff = + "assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstDefaultdmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOfflowOffpmOffstDefaultdmOff = + "assets/icons/battery/dmOff/perOffchargOfflowOffpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOffpmOnstChargeddmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOffpmOnstChargeddmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOfflowOffpmOnstChargeddmOff = + "assets/icons/battery/dmOff/perOffchargOfflowOffpmOnstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOnpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOnpmOffstDefaultdmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOfflowOnpmOffstDefaultdmOff = + "assets/icons/battery/dmOff/perOffchargOfflowOnpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOfflowOnpmOnstDefaultdmOff + /// assets/icons/battery/dmOff/perOffchargOfflowOnpmOnstDefaultdmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOfflowOnpmOnstDefaultdmOff = + "assets/icons/battery/dmOff/perOffchargOfflowOnpmOnstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOnlowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOffchargOnlowOffpmOffstChargeddmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOnlowOffpmOffstChargeddmOff = + "assets/icons/battery/dmOff/perOffchargOnlowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOnlowOnpmOffstlowBatterydmOff + /// assets/icons/battery/dmOff/perOffchargOnlowOnpmOffstlowBatterydmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOnlowOnpmOffstlowBatterydmOff = + "assets/icons/battery/dmOff/perOffchargOnlowOnpmOffstlowBatterydmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOffchargOnlowOnpmOnstlowpmdmOff + /// assets/icons/battery/dmOff/perOffchargOnlowOnpmOnstlowpmdmOff.svg + static const String + assetsIconsBatteryDmOffPerOffchargOnlowOnpmOnstlowpmdmOff = + "assets/icons/battery/dmOff/perOffchargOnlowOnpmOnstlowpmdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstChargeddmOff.svg + static const String + assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstChargeddmOff = + "assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstDefaultdmOff.svg + static const String + assetsIconsBatteryDmOffPerOnchargOfflowOffpmOffstDefaultdmOff = + "assets/icons/battery/dmOff/perOnchargOfflowOffpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOffpmOnstChargeddmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOffpmOnstChargeddmOff.svg + static const String + assetsIconsBatteryDmOffPerOnchargOfflowOffpmOnstChargeddmOff = + "assets/icons/battery/dmOff/perOnchargOfflowOffpmOnstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOnpmOffstDefaultdmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOnpmOffstDefaultdmOff.svg + static const String + assetsIconsBatteryDmOffPerOnchargOfflowOnpmOffstDefaultdmOff = + "assets/icons/battery/dmOff/perOnchargOfflowOnpmOffstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOfflowOnpmOnstDefaultdmOff + /// assets/icons/battery/dmOff/perOnchargOfflowOnpmOnstDefaultdmOff.svg + static const String + assetsIconsBatteryDmOffPerOnchargOfflowOnpmOnstDefaultdmOff = + "assets/icons/battery/dmOff/perOnchargOfflowOnpmOnstDefaultdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOnlowOffpmOffstChargeddmOff + /// assets/icons/battery/dmOff/perOnchargOnlowOffpmOffstChargeddmOff.svg + static const String + assetsIconsBatteryDmOffPerOnchargOnlowOffpmOffstChargeddmOff = + "assets/icons/battery/dmOff/perOnchargOnlowOffpmOffstChargeddmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOnlowOnpmOffstlowBatterydmOff + /// assets/icons/battery/dmOff/perOnchargOnlowOnpmOffstlowBatterydmOff.svg + static const String + assetsIconsBatteryDmOffPerOnchargOnlowOnpmOffstlowBatterydmOff = + "assets/icons/battery/dmOff/perOnchargOnlowOnpmOffstlowBatterydmOff.svg"; + + /// Assets for assetsIconsBatteryDmOffPerOnchargOnlowOnpmOnstlowpmdmOff + /// assets/icons/battery/dmOff/perOnchargOnlowOnpmOnstlowpmdmOff.svg + static const String assetsIconsBatteryDmOffPerOnchargOnlowOnpmOnstlowpmdmOff = + "assets/icons/battery/dmOff/perOnchargOnlowOnpmOnstlowpmdmOff.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstChargeddmOn.svg + static const String + assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstChargeddmOn = + "assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstDefaultdmOn.svg + static const String + assetsIconsBatteryDmOnPerOffchargOfflowOffpmOffstDefaultdmOn = + "assets/icons/battery/dmOn/perOffchargOfflowOffpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOffpmOnstChargeddmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOffpmOnstChargeddmOn.svg + static const String + assetsIconsBatteryDmOnPerOffchargOfflowOffpmOnstChargeddmOn = + "assets/icons/battery/dmOn/perOffchargOfflowOffpmOnstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOnpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOnpmOffstDefaultdmOn.svg + static const String + assetsIconsBatteryDmOnPerOffchargOfflowOnpmOffstDefaultdmOn = + "assets/icons/battery/dmOn/perOffchargOfflowOnpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOfflowOnpmOnstDefaultdmOn + /// assets/icons/battery/dmOn/perOffchargOfflowOnpmOnstDefaultdmOn.svg + static const String + assetsIconsBatteryDmOnPerOffchargOfflowOnpmOnstDefaultdmOn = + "assets/icons/battery/dmOn/perOffchargOfflowOnpmOnstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOnlowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOffchargOnlowOffpmOffstChargeddmOn.svg + static const String + assetsIconsBatteryDmOnPerOffchargOnlowOffpmOffstChargeddmOn = + "assets/icons/battery/dmOn/perOffchargOnlowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOnlowOnpmOffstlowBatterydmOn + /// assets/icons/battery/dmOn/perOffchargOnlowOnpmOffstlowBatterydmOn.svg + static const String + assetsIconsBatteryDmOnPerOffchargOnlowOnpmOffstlowBatterydmOn = + "assets/icons/battery/dmOn/perOffchargOnlowOnpmOffstlowBatterydmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOffchargOnlowOnpmOnstlowpmdmOn + /// assets/icons/battery/dmOn/perOffchargOnlowOnpmOnstlowpmdmOn.svg + static const String assetsIconsBatteryDmOnPerOffchargOnlowOnpmOnstlowpmdmOn = + "assets/icons/battery/dmOn/perOffchargOnlowOnpmOnstlowpmdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstChargeddmOn.svg + static const String + assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstChargeddmOn = + "assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstDefaultdmOn.svg + static const String + assetsIconsBatteryDmOnPerOnchargOfflowOffpmOffstDefaultdmOn = + "assets/icons/battery/dmOn/perOnchargOfflowOffpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOffpmOnstChargeddmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOffpmOnstChargeddmOn.svg + static const String + assetsIconsBatteryDmOnPerOnchargOfflowOffpmOnstChargeddmOn = + "assets/icons/battery/dmOn/perOnchargOfflowOffpmOnstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOnpmOffstDefaultdmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOnpmOffstDefaultdmOn.svg + static const String + assetsIconsBatteryDmOnPerOnchargOfflowOnpmOffstDefaultdmOn = + "assets/icons/battery/dmOn/perOnchargOfflowOnpmOffstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOfflowOnpmOnstDefaultdmOn + /// assets/icons/battery/dmOn/perOnchargOfflowOnpmOnstDefaultdmOn.svg + static const String + assetsIconsBatteryDmOnPerOnchargOfflowOnpmOnstDefaultdmOn = + "assets/icons/battery/dmOn/perOnchargOfflowOnpmOnstDefaultdmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOnlowOffpmOffstChargeddmOn + /// assets/icons/battery/dmOn/perOnchargOnlowOffpmOffstChargeddmOn.svg + static const String + assetsIconsBatteryDmOnPerOnchargOnlowOffpmOffstChargeddmOn = + "assets/icons/battery/dmOn/perOnchargOnlowOffpmOffstChargeddmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOnlowOnpmOffstlowBatterydmOn + /// assets/icons/battery/dmOn/perOnchargOnlowOnpmOffstlowBatterydmOn.svg + static const String + assetsIconsBatteryDmOnPerOnchargOnlowOnpmOffstlowBatterydmOn = + "assets/icons/battery/dmOn/perOnchargOnlowOnpmOffstlowBatterydmOn.svg"; + + /// Assets for assetsIconsBatteryDmOnPerOnchargOnlowOnpmOnstlowpmdmOn + /// assets/icons/battery/dmOn/perOnchargOnlowOnpmOnstlowpmdmOn.svg + static const String assetsIconsBatteryDmOnPerOnchargOnlowOnpmOnstlowpmdmOn = + "assets/icons/battery/dmOn/perOnchargOnlowOnpmOnstlowpmdmOn.svg"; + + /// Assets for assetsIconsCO2 + /// assets/icons/CO2.svg + static const String assetsIconsCO2 = "assets/icons/CO2.svg"; + + /// Assets for assetsIconsColdMode + /// assets/icons/coldMode.svg + static const String assetsIconsColdMode = "assets/icons/coldMode.svg"; + + /// Assets for assetsIconsColorWheel + /// assets/icons/color_wheel.svg + static const String assetsIconsColorWheel = "assets/icons/color_wheel.svg"; + + /// Assets for assetsIconsCurtain + /// assets/icons/Curtain.svg + static const String assetsIconsCurtain = "assets/icons/Curtain.svg"; + + /// Assets for assetsIconsCurtainsIconCloseCurtainsvg + /// assets/icons/curtainsIcon/closeCurtainsvg.svg + static const String assetsIconsCurtainsIconCloseCurtain = + "assets/icons/curtainsIcon/closeCurtain.svg"; + + /// Assets for assetsIconsCurtainsIconCurtainHolder + /// assets/icons/curtainsIcon/curtainHolder.svg + static const String assetsIconsCurtainsIconCurtainHolder = + "assets/icons/curtainsIcon/curtainHolder.svg"; + + /// Assets for assetsIconsCurtainsIconOpenCurtain + /// assets/icons/curtainsIcon/openCurtain.svg + static const String assetsIconsCurtainsIconOpenCurtain = + "assets/icons/curtainsIcon/openCurtain.svg"; + + /// Assets for assetsIconsCurtainsIconVerticalBlade + /// assets/icons/curtainsIcon/verticalBlade.svg + static const String assetsIconsCurtainsIconVerticalBlade = + "assets/icons/curtainsIcon/verticalBlade.svg"; + + /// Assets for assetsIconsDashboard + /// assets/icons/dashboard.svg + static const String assetsIconsDashboard = "assets/icons/dashboard.svg"; + + static const String noUnitsIconDashboard = "assets/icons/no_units_ic.svg"; + + /// Assets for assetsIconsDashboardFill + /// assets/icons/dashboard-fill.svg + static const String assetsIconsDashboardFill = + "assets/icons/dashboard-fill.svg"; + + /// Assets for assetsIconsDevices + /// assets/icons/Devices.svg + static const String assetsIconsDevices = "assets/icons/Devices.svg"; + + /// Assets for assetsIconsDevicesFill + /// assets/icons/Devices-fill.svg + static const String assetsIconsDevicesFill = "assets/icons/Devices-fill.svg"; + + /// Assets for assetsIconsDoorLock + /// assets/icons/doorLock.svg + static const String assetsIconsDoorLock = "assets/icons/doorLock.svg"; + + /// Assets for assetsIconsDoorLockLinkage + /// assets/icons/DoorLockLinkage.svg + static const String assetsIconsDoorLockLinkage = + "assets/icons/DoorLockLinkage.svg"; + + /// Assets for assetsIconsDoorLockLock + /// assets/icons/DoorLockLock.svg + static const String assetsIconsDoorLockLock = "assets/icons/DoorLockLock.svg"; + + /// Assets for assetsIconsDoorLockMembers + /// assets/icons/DoorLockMembers.svg + static const String assetsIconsDoorLockMembers = + "assets/icons/DoorLockMembers.svg"; + + /// Assets for assetsIconsDoorLockPassword + /// assets/icons/DoorLockPassword.svg + static const String assetsIconsDoorLockPassword = + "assets/icons/DoorLockPassword.svg"; + + /// Assets for assetsIconsDoorLockRecords + /// assets/icons/DoorLockRecords.svg + static const String assetsIconsDoorLockRecords = + "assets/icons/DoorLockRecords.svg"; + + /// Assets for assetsIconsDoorlockAssetsBatteryIndicator + /// assets/icons/doorlock-assets/BatteryIndicator.svg + static const String assetsIconsDoorlockAssetsBatteryIndicator = + "assets/icons/doorlock-assets/BatteryIndicator.svg"; + + /// Assets for assetsIconsDoorlockAssetsDoorlockButtonOne + /// assets/icons/doorlock-assets/doorlock-button-one.svg + static const String assetsIconsDoorlockAssetsDoorlockButtonOne = + "assets/icons/doorlock-assets/doorlock-button-one.svg"; + + /// Assets for assetsIconsDoorlockAssetsDoorlockButtonTwo + /// assets/icons/doorlock-assets/doorlock-button-two.svg + static const String assetsIconsDoorlockAssetsDoorlockButtonTwo = + "assets/icons/doorlock-assets/doorlock-button-two.svg"; + + /// Assets for assetsIconsDoorlockAssetsLockIcon + /// assets/icons/doorlock-assets/lockIcon.svg + static const String assetsIconsDoorlockAssetsLockIcon = + "assets/icons/doorlock-assets/lockIcon.svg"; + static const String doorUnlockIcon = + "assets/icons/doorlock-assets/door_un_look_ic.svg"; + + /// Assets for assetsIconsDoorlockAssetsMembersManagement + /// assets/icons/doorlock-assets/members-management.svg + static const String assetsIconsDoorlockAssetsMembersManagement = + "assets/icons/doorlock-assets/members-management.svg"; + + /// Assets for assetsIconsDoorlockAssetsSmartLinkage + /// assets/icons/doorlock-assets/smart-linkage.svg + static const String assetsIconsDoorlockAssetsSmartLinkage = + "assets/icons/doorlock-assets/smart-linkage.svg"; + + /// Assets for assetsIconsDoorlockAssetsTemporaryPassword + /// assets/icons/doorlock-assets/temporary-password.svg + static const String assetsIconsDoorlockAssetsTemporaryPassword = + "assets/icons/doorlock-assets/temporary-password.svg"; + + /// Assets for assetsIconsDoorlockAssetsUnlockingRecords + /// assets/icons/doorlock-assets/unlocking-records.svg + static const String assetsIconsDoorlockAssetsUnlockingRecords = + "assets/icons/doorlock-assets/unlocking-records.svg"; + + /// Assets for assetsIconsFacebook + /// assets/icons/Facebook.svg + static const String assetsIconsFacebook = "assets/icons/Facebook.svg"; + + /// Assets for assetsIconsFan0 + /// assets/icons/fan-0.svg + static const String assetsIconsFan0 = "assets/icons/fan-0.svg"; + + /// Assets for assetsIconsFan1 + /// assets/icons/fan-1.svg + static const String assetsIconsFan1 = "assets/icons/fan-1.svg"; + + /// Assets for assetsIconsFan2 + /// assets/icons/fan-2.svg + static const String assetsIconsFan2 = "assets/icons/fan-2.svg"; + + /// Assets for assetsIconsFan3 + /// assets/icons/fan-3.svg + static const String assetsIconsFan3 = "assets/icons/fan-3.svg"; + + /// Assets for assetsIconsFilter + /// assets/icons/filter.png + static const String assetsIconsFilter = "assets/icons/filter.png"; + + /// Assets for assetsIconsFrequency + /// assets/icons/frequency.svg + static const String assetsIconsFrequency = "assets/icons/frequency.svg"; + + /// Assets for assetsIconsGang + /// assets/icons/gang.svg + static const String assetsIconsGang = "assets/icons/gang.svg"; + + /// Assets for assetsIconsGateway + /// assets/icons/Gateway.svg + static const String assetsIconsGateway = "assets/icons/Gateway.svg"; + + /// Assets for assetsIconsGoogle + /// assets/icons/Google.svg + static const String assetsIconsGoogle = "assets/icons/Google.svg"; + + /// Assets for assetsIconsHome + /// assets/icons/home.svg + static const String assetsIconsHome = "assets/icons/home.svg"; + + /// Assets for assetsIconsHot1 + /// assets/icons/hot1.jpg + static const String assetsIconsHot1 = "assets/icons/hot1.jpg"; + + /// Assets for assetsIconsKalvin + /// assets/icons/kalvin.svg + static const String assetsIconsKalvin = "assets/icons/kalvin.svg"; + + /// Assets for assetsIconsLayout + /// assets/icons/Layout.svg + static const String assetsIconsLayout = "assets/icons/Layout.svg"; + + /// Assets for assetsIconsLayoutFill + /// assets/icons/Layout-fill.svg + static const String assetsIconsLayoutFill = "assets/icons/Layout-fill.svg"; + + /// Assets for assetsIconsLight + /// assets/icons/Light.svg + static const String assetsIconsLight = "assets/icons/Light.svg"; + + /// Assets for assetsIconsLightSwitchOff + /// assets/icons/lightSwitchOff.svg + static const String assetsIconsLightSwitchOff = + "assets/icons/lightSwitchOff.svg"; + + /// Assets for assetsIconsLightSwitchOn + /// assets/icons/lightSwitchOn.svg + static const String assetsIconsLightSwitchOn = + "assets/icons/lightSwitchOn.svg"; + + /// Assets for assetsIconsLinkageIconsDoorLockAlarm + /// assets/icons/linkageIcons/doorLockAlarm.svg + static const String assetsIconsLinkageIconsDoorLockAlarm = + "assets/icons/linkageIcons/doorLockAlarm.svg"; + + /// Assets for assetsIconsLinkTimeLimitedPasswordIcon + /// assets/icons/timeLimitedPasswordIcon.svg + static const String timeLimitedPasswordIcon = + "assets/icons/timeLimitedPasswordIcon.svg"; + + /// Assets for assetsIconsoneTimePassword + /// assets/icons/oneTimePassword.svg + static const String oneTimePassword = "assets/icons/oneTimePassword.svg"; + + /// Assets for assetsIconsTimeLimitedPassword + /// assets/icons/timeLimitedPassword.svg + static const String timeLimitedPassword = + "assets/icons/timeLimitedPassword.svg"; + + /// Assets for assetsIconsNoValidPasswords + /// assets/icons/noValidPasswords.svg + static const String noValidPasswords = "assets/icons/noValidPasswords.svg"; + + /// Assets for assetsIconsLinkageIconsFamilyHome + /// assets/icons/linkageIcons/familyHome.svg + static const String assetsIconsLinkageIconsFamilyHome = + "assets/icons/linkageIcons/familyHome.svg"; + + /// Assets for assetsIconsLock + /// assets/icons/lock.svg + static const String assetsIconsLock = "assets/icons/lock.svg"; + + static const String assetsIconsUnLock = "assets/icons/unlock_ic.svg"; + + /// Assets for assetsIconsLogo + /// assets/icons/logo.png + static const String assetsIconsLogo = "assets/icons/logo.png"; + + /// Assets for assetsIconsMenu + /// assets/icons/Menu.svg + static const String assetsIconsMenu = "assets/icons/Menu.svg"; + + /// Assets for assetsIconsMenuFill + /// assets/icons/Menu-fill.svg + static const String assetsIconsMenuFill = "assets/icons/Menu-fill.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsClearCach + /// assets/icons/MenuIcons/GeneralSettingsIcons/clearCach.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsClearCach = + "assets/icons/MenuIcons/GeneralSettingsIcons/clearCach.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsLanguage + /// assets/icons/MenuIcons/GeneralSettingsIcons/language.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsLanguage = + "assets/icons/MenuIcons/GeneralSettingsIcons/language.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsNetworkDiagnosis + /// assets/icons/MenuIcons/GeneralSettingsIcons/networkDiagnosis.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsNetworkDiagnosis = + "assets/icons/MenuIcons/GeneralSettingsIcons/networkDiagnosis.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsTemperatureUnit + /// assets/icons/MenuIcons/GeneralSettingsIcons/temperatureUnit.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsTemperatureUnit = + "assets/icons/MenuIcons/GeneralSettingsIcons/temperatureUnit.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsTouchTone + /// assets/icons/MenuIcons/GeneralSettingsIcons/touchTone.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsTouchTone = + "assets/icons/MenuIcons/GeneralSettingsIcons/touchTone.svg"; + + /// Assets for assetsIconsMenuIconsGeneralSettingsIconsVoiceAssistant + /// assets/icons/MenuIcons/GeneralSettingsIcons/voiceAssistant.svg + static const String assetsIconsMenuIconsGeneralSettingsIconsVoiceAssistant = + "assets/icons/MenuIcons/GeneralSettingsIcons/voiceAssistant.svg"; + + /// Assets for assetsIconsMenuIconsHomeManagementIconsCreateHome + /// assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg + static const String assetsIconsMenuIconsHomeManagementIconsCreateHome = + "assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg"; + + /// Assets for assetsIconsMenuIconsHomeManagementIconsJoinAHome + /// assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg + static const String assetsIconsMenuIconsHomeManagementIconsJoinAHome = + "assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg"; + + /// Assets for assetsIconsMenuIconsHomeManagementIconsManageYourHome + /// assets/icons/MenuIcons/HomeManagementIcons/ManageYourHome.svg + static const String assetsIconsMenuIconsHomeManagementIconsManageYourHome = + "assets/icons/MenuIcons/HomeManagementIcons/ManageYourHome.svg"; + + /// Assets for assetsIconsMenuIconsLeagalInfoIconsAbout + /// assets/icons/MenuIcons/LeagalInfoIcons/About.svg + static const String assetsIconsMenuIconsLeagalInfoIconsAbout = + "assets/icons/MenuIcons/LeagalInfoIcons/About.svg"; + + /// Assets for assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy + /// assets/icons/MenuIcons/LeagalInfoIcons/PrivacyPolicy.svg + static const String assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy = + "assets/icons/MenuIcons/LeagalInfoIcons/PrivacyPolicy.svg"; + + /// Assets for assetsIconsMenuIconsLeagalInfoIconsUserAgreement + /// assets/icons/MenuIcons/LeagalInfoIcons/UserAgreement.svg + static const String assetsIconsMenuIconsLeagalInfoIconsUserAgreement = + "assets/icons/MenuIcons/LeagalInfoIcons/UserAgreement.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsAlerts + /// assets/icons/MenuIcons/MessagesCenterIcons/Alerts.svg + static const String assetsIconsMenuIconsMessagesCenterIconsAlerts = + "assets/icons/MenuIcons/MessagesCenterIcons/Alerts.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsFAQs + /// assets/icons/MenuIcons/MessagesCenterIcons/FAQs.svg + static const String assetsIconsMenuIconsMessagesCenterIconsFAQs = + "assets/icons/MenuIcons/MessagesCenterIcons/FAQs.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsHelpAndFeedback + /// assets/icons/MenuIcons/MessagesCenterIcons/HelpAndFeedback.svg + static const String assetsIconsMenuIconsMessagesCenterIconsHelpAndFeedback = + "assets/icons/MenuIcons/MessagesCenterIcons/HelpAndFeedback.svg"; + + /// Assets for assetsIconsMenuIconsMessagesCenterIconsMessages + /// assets/icons/MenuIcons/MessagesCenterIcons/Messages.svg + static const String assetsIconsMenuIconsMessagesCenterIconsMessages = + "assets/icons/MenuIcons/MessagesCenterIcons/Messages.svg"; + + /// Assets for assetsIconsMenuIconsSecurityAndPrivacyIconsPrivacy + /// assets/icons/MenuIcons/SecurityAndPrivacyIcons/Privacy.svg + static const String assetsIconsMenuIconsSecurityAndPrivacyIconsPrivacy = + "assets/icons/MenuIcons/SecurityAndPrivacyIcons/Privacy.svg"; + + /// Assets for assetsIconsMenuIconsSecurityAndPrivacyIconsSecurty + /// assets/icons/MenuIcons/SecurityAndPrivacyIcons/Securty.svg + static const String assetsIconsMenuIconsSecurityAndPrivacyIconsSecurty = + "assets/icons/MenuIcons/SecurityAndPrivacyIcons/Securty.svg"; + + /// Assets for assetsIconsMinus + /// assets/icons/minus.svg + static const String assetsIconsMinus = "assets/icons/minus.svg"; + + /// Assets for assetsIconsPlus + /// assets/icons/plus.svg + static const String assetsIconsPlus = "assets/icons/plus.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsDistance + /// assets/icons/presence-sensor-assets/Distance.svg + static const String assetsIconsPresenceSensorAssetsDistance = + "assets/icons/presence-sensor-assets/Distance.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsEmpty + /// assets/icons/presence-sensor-assets/Empty.svg + static const String assetsIconsPresenceSensorAssetsEmpty = + "assets/icons/presence-sensor-assets/Empty.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsHelpDescription + /// assets/icons/presence-sensor-assets/help-description.svg + static const String assetsIconsPresenceSensorAssetsHelpDescription = + "assets/icons/presence-sensor-assets/help-description.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsIlluminanceRecord + /// assets/icons/presence-sensor-assets/Illuminance-Record.svg + static const String assetsIconsPresenceSensorAssetsIlluminanceRecord = + "assets/icons/presence-sensor-assets/Illuminance-Record.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsIlluminanceValue + /// assets/icons/presence-sensor-assets/Illuminance-Value.svg + static const String assetsIconsPresenceSensorAssetsIlluminanceValue = + "assets/icons/presence-sensor-assets/Illuminance-Value.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsIndicator + /// assets/icons/presence-sensor-assets/Indicator.svg + static const String assetsIconsPresenceSensorAssetsIndicator = + "assets/icons/presence-sensor-assets/Indicator.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsInductionRecording + /// assets/icons/presence-sensor-assets/induction-recording.svg + static const String assetsIconsPresenceSensorAssetsInductionRecording = + "assets/icons/presence-sensor-assets/induction-recording.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsParameterSettings + /// assets/icons/presence-sensor-assets/parameter-settings.svg + static const String assetsIconsPresenceSensorAssetsParameterSettings = + "assets/icons/presence-sensor-assets/parameter-settings.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsParameterSettings + /// assets/icons/presence-sensor-assets/space_type_icon.svg + static const String spaceTypeIcon = + "assets/icons/presence-sensor-assets/space_type_icon.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsParameterSettings + /// assets/icons/presence-sensor-assets/space_type_icon.svg + static const String sensitivityIcon = + "assets/icons/presence-sensor-assets/Sensitivity.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsParameterSettings + /// assets/icons/presence-sensor-assets/maximum_distance.svg + static const String maximumDistanceIcon = + "assets/icons/presence-sensor-assets/maximum_distance.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsPresence + /// assets/icons/presence-sensor-assets/Presence.svg + static const String assetsIconsPresenceSensorAssetsPresence = + "assets/icons/presence-sensor-assets/Presence.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsPresenceSensorMotion + /// assets/icons/presence-sensor-assets/presence-sensor-motion.svg + static const String assetsIconsPresenceSensorAssetsPresenceSensorMotion = + "assets/icons/presence-sensor-assets/presence-sensor-motion.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsRecord + /// assets/icons/presence-sensor-assets/Record.svg + static const String assetsIconsPresenceSensorAssetsRecord = + "assets/icons/presence-sensor-assets/Record.svg"; + + /// Assets for assetsIconsPresenceSensorAssetsTime + /// assets/icons/presence-sensor-assets/Time.svg + static const String assetsIconsPresenceSensorAssetsTime = + "assets/icons/presence-sensor-assets/Time.svg"; + + /// Assets for assetsIconsRoutines + /// assets/icons/Routines.svg + static const String assetsIconsRoutines = "assets/icons/Routines.svg"; + + /// Assets for assetsIconsRoutinesFill + /// assets/icons/Routines-fill.svg + static const String assetsIconsRoutinesFill = + "assets/icons/Routines-fill.svg"; + + /// Assets for assetsIconsScan + /// assets/icons/Scan.svg + static const String assetsIconsScan = "assets/icons/Scan.svg"; + + /// Assets for assetsIconsScreen + /// assets/icons/Screen.svg + static const String assetsIconsScreen = "assets/icons/Screen.svg"; + + /// Assets for assetsIconsSensors + /// assets/icons/sensors.svg + static const String assetsIconsSensors = "assets/icons/sensors.svg"; + + /// Assets for assetsIconsSettings + /// assets/icons/settings.svg + static const String assetsIconsSettings = "assets/icons/settings.svg"; + + /// Assets for assetsIconsSummer + /// assets/icons/Summer.svg + static const String assetsIconsSummer = "assets/icons/Summer.svg"; + + /// Assets for assetsIconsSummerMode + /// assets/icons/summer_mode.svg + static const String assetsIconsSummerMode = "assets/icons/summer_mode.svg"; + + /// Assets for assetsIconsSunnyMode + /// assets/icons/sunnyMode.svg + static const String assetsIconsSunnyMode = "assets/icons/sunnyMode.svg"; + + /// Assets for assetsIconsSustainability + /// assets/icons/sustainability.svg + static const String assetsIconsSustainability = + "assets/icons/sustainability.svg"; + + /// Assets for assetsIconsUnlockingMethodsIconsFace + /// assets/icons/unlockingMethodsIcons/face.svg + static const String assetsIconsUnlockingMethodsIconsFace = + "assets/icons/unlockingMethodsIcons/face.svg"; + + /// Assets for assetsIconsUnlockingMethodsIconsFingerprint + /// assets/icons/unlockingMethodsIcons/fingerprint.svg + static const String assetsIconsUnlockingMethodsIconsFingerprint = + "assets/icons/unlockingMethodsIcons/fingerprint.svg"; + + /// Assets for assetsIconsUnlockingMethodsIconsRemote + /// assets/icons/unlockingMethodsIcons/remote.svg + static const String assetsIconsUnlockingMethodsIconsRemote = + "assets/icons/unlockingMethodsIcons/remote.svg"; + + /// Assets for assetsIconsVector + /// assets/icons/Vector.svg + static const String assetsIconsVector = "assets/icons/Vector.svg"; + + /// Assets for assetsIconsVector1 + /// assets/icons/Vector-1.svg + static const String assetsIconsVector1 = "assets/icons/Vector-1.svg"; + + /// Assets for assetsIconsVoltMeter + /// assets/icons/volt-meter.svg + static const String assetsIconsVoltMeter = "assets/icons/volt-meter.svg"; + + /// Assets for assetsIconsWifi + /// assets/icons/Wifi.svg + static const String assetsIconsWifi = "assets/icons/Wifi.svg"; + + /// Assets for assetsIconsWindyMode + /// assets/icons/windyMode.svg + static const String assetsIconsWindyMode = "assets/icons/windyMode.svg"; + + /// Assets for assetsIconsWinter + /// assets/icons/Winter.svg + static const String assetsIconsWinter = "assets/icons/Winter.svg"; + + /// Assets for assetsIconsWinter1 + /// assets/icons/winter1.jpg + static const String assetsIconsWinter1 = "assets/icons/winter1.jpg"; + + /// Assets for assetsIconsWinterMode + /// assets/icons/Winter_mode.svg + static const String assetsIconsWinterMode = "assets/icons/Winter_mode.svg"; + + static const String assetsDeleteIcon = "assets/icons/delete_room_ic.svg"; + + static const String blueCheckboxIcon = "assets/icons/blue_checkbox_ic.svg"; + static const String emptyCheckboxIcon = "assets/icons/empty_checkbox_ic.svg"; + static const String handClickIcon = "assets/icons/hand_click.svg"; + static const String refreshIcon = "assets/icons/refresh.svg"; + static const String addIcon = "assets/icons/add.svg"; + static const String player = "assets/icons/player.svg"; + static const String delay = "assets/icons/delay.svg"; + static const String pauseIcon = "assets/icons/pause_ic.svg"; + static const String playIcon = "assets/icons/play_ic.svg"; + static const String gatewayIcon = "assets/icons/gateway_icon.svg"; + + /// Assets for assetsImagesAutomation + /// assets/images/automation.jpg + static const String assetsImagesAutomation = "assets/images/automation.jpg"; + + /// Assets for assetsImagesBackground + /// assets/images/Background.png + static const String assetsImagesBackground = "assets/images/Background.png"; + + /// Assets for assetsImagesBlackLogo + /// assets/images/black-logo.png + static const String assetsImagesBlackLogo = "assets/images/black-logo.png"; + + /// Assets for assetsImagesBlind + /// assets/images/blind.png + static const String assetsImagesBlind = "assets/images/blind.png"; + + /// Assets for assetsImagesBoxEmpty + /// assets/images/box-empty.jpg + static const String assetsImagesBoxEmpty = "assets/images/box-empty.jpg"; + + /// Assets for assetsImagesCurtain + /// assets/images/curtain.png + static const String assetsImagesCurtain = "assets/images/curtain.png"; + + /// Assets for assetsImagesDown + /// assets/images/Down.png + static const String assetsImagesDown = "assets/images/Down.png"; + + /// Assets for assetsImagesHorizintalBlade + /// assets/images/HorizintalBlade.png + static const String assetsImagesHorizintalBlade = + "assets/images/HorizintalBlade.png"; + + /// Assets for assetsImagesLogo + /// assets/images/Logo.svg + static const String assetsImagesLogo = "assets/images/Logo.svg"; + + /// Assets for assetsImagesLogoHorizontal + /// assets/images/logo_horizontal.png + static const String assetsImagesLogoHorizontal = + "assets/images/logo_horizontal.png"; + + /// Assets for assetsImagesPause + /// assets/images/Pause.png + static const String assetsImagesPause = "assets/images/Pause.png"; + + /// Assets for assetsImagesTestDash + /// assets/images/test_dash.png + static const String assetsImagesTestDash = "assets/images/test_dash.png"; + + /// Assets for assetsImagesTestDash2 + /// assets/images/test_dash2.png + static const String assetsImagesTestDash2 = "assets/images/test_dash2.png"; + + /// Assets for assetsImagesUp + /// assets/images/Up.png + static const String assetsImagesUp = "assets/images/Up.png"; + + /// Assets for assetsImagesVector + /// assets/images/Vector.png + static const String assetsImagesVector = "assets/images/Vector.png"; + + /// Assets for assetsImagesWhiteLogo + /// assets/images/white-logo.png + static const String assetsImagesWhiteLogo = "assets/images/white-logo.png"; + + /// Assets for assetsImagesWindow + /// assets/images/Window.png + static const String assetsImagesWindow = "assets/images/Window.png"; + + static const String assetsSensitivityFunction = + "assets/icons/functions_icons/sensitivity.svg"; + + //assets/icons/functions_icons/sesitivity_operation_icon.svg + static const String assetsSensitivityOperationIcon = + "assets/icons/functions_icons/sesitivity_operation_icon.svg"; + + //assets/icons/functions_icons/ac_power.svg + + static const String assetsAcPower = + "assets/icons/functions_icons/ac_power.svg"; + + //assets/icons/functions_icons/ac_power_off.svg + + static const String assetsAcPowerOFF = + "assets/icons/functions_icons/ac_power_off.svg"; + + //assets/icons/functions_icons/child_lock.svg + + static const String assetsChildLock = + "assets/icons/functions_icons/child_lock.svg"; + + //assets/icons/functions_icons/cooling.svg + + static const String assetsFreezing = + "assets/icons/functions_icons/freezing.svg"; + + //assets/icons/functions_icons/fan_speed.svg + + static const String assetsFanSpeed = + "assets/icons/functions_icons/fan_speed.svg"; + + //assets/icons/functions_icons/ac_cooling.svg + + static const String assetsAcCooling = + "assets/icons/functions_icons/ac_cooling.svg"; + + //assets/icons/functions_icons/ac_heating.svg + + static const String assetsAcHeating = + "assets/icons/functions_icons/ac_heating.svg"; + + //assets/icons/functions_icons/celsius_degrees.svg + + static const String assetsCelsiusDegrees = + "assets/icons/functions_icons/celsius_degrees.svg"; + + //assets/icons/functions_icons/tempreture.svg + + static const String assetsTempreture = + "assets/icons/functions_icons/tempreture.svg"; + + //assets/icons/functions_icons/ac_fan_low.svg + + static const String assetsAcFanLow = + "assets/icons/functions_icons/ac_fan_low.svg"; + + //assets/icons/functions_icons/ac_fan_middle.svg + + static const String assetsAcFanMiddle = + "assets/icons/functions_icons/ac_fan_middle.svg"; + + //assets/icons/functions_icons/ac_fan_high.svg + + static const String assetsAcFanHigh = + "assets/icons/functions_icons/ac_fan_high.svg"; + + //assets/icons/functions_icons/ac_fan_auto.svg + + static const String assetsAcFanAuto = + "assets/icons/functions_icons/ac_fan_auto.svg"; + + //assets/icons/functions_icons/scene_child_lock.svg + + static const String assetsSceneChildLock = + "assets/icons/functions_icons/scene_child_lock.svg"; + + //assets/icons/functions_icons/scene_child_unlock.svg + + static const String assetsSceneChildUnlock = + "assets/icons/functions_icons/scene_child_unlock.svg"; + + //assets/icons/functions_icons/scene_refresh.svg + + static const String assetsSceneRefresh = + "assets/icons/functions_icons/scene_refresh.svg"; + + //assets/icons/functions_icons/light_countdown.svg + + static const String assetsLightCountdown = + "assets/icons/functions_icons/light_countdown.svg"; + + //assets/icons/functions_icons/far_detection.svg + + static const String assetsFarDetection = + "assets/icons/functions_icons/far_detection.svg"; + + //assets/icons/functions_icons/far_detection_function.svg + + static const String assetsFarDetectionFunction = + "assets/icons/functions_icons/far_detection_function.svg"; + + //assets/icons/functions_icons/indicator.svg + + static const String assetsIndicator = + "assets/icons/functions_icons/indicator.svg"; + + //assets/icons/functions_icons/motion_detection.svg + + static const String assetsMotionDetection = + "assets/icons/functions_icons/motion_detection.svg"; + + //assets/icons/functions_icons/motionless_detection.svg + + static const String assetsMotionlessDetection = + "assets/icons/functions_icons/motionless_detection.svg"; + + //assets/icons/functions_icons/nobody_time.svg + + static const String assetsNobodyTime = + "assets/icons/functions_icons/nobody_time.svg"; + + //assets/icons/functions_icons/factory_reset.svg + + static const String assetsFactoryReset = + "assets/icons/functions_icons/factory_reset.svg"; + + //assets/icons/functions_icons/master_state.svg + + static const String assetsMasterState = + "assets/icons/functions_icons/master_state.svg"; + + //assets/icons/functions_icons/switch_alarm_sound.svg + + static const String assetsSwitchAlarmSound = + "assets/icons/functions_icons/switch_alarm_sound.svg"; + + //assets/icons/functions_icons/reset_off.svg + + static const String assetsResetOff = + "assets/icons/functions_icons/reset_off.svg"; + + //assets/icons/functions_icons/automation_functions/card_unlock.svg + + static const String assetsCardUnlock = + "assets/icons/functions_icons/automation_functions/card_unlock.svg"; + + //assets/icons/functions_icons/automation_functions/doorbell.svg + + static const String assetsDoorbell = + "assets/icons/functions_icons/automation_functions/doorbell.svg"; + + //assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg + + static const String assetsDoorlockNormalOpen = + "assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg"; + + //assets/icons/functions_icons/automation_functions/double_lock.svg + + static const String assetsDoubleLock = + "assets/icons/functions_icons/automation_functions/double_lock.svg"; + + //assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg + + static const String assetsFingerprintUnlock = + "assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg"; + + //assets/icons/functions_icons/automation_functions/hijack_alarm.svg + + static const String assetsHijackAlarm = + "assets/icons/functions_icons/automation_functions/hijack_alarm.svg"; + + //assets/icons/functions_icons/automation_functions/lock_alarm.svg + + static const String assetsLockAlarm = + "assets/icons/functions_icons/automation_functions/lock_alarm.svg"; + + //assets/icons/functions_icons/automation_functions/password_unlock.svg + + static const String assetsPasswordUnlock = + "assets/icons/functions_icons/automation_functions/password_unlock.svg"; + + //assets/icons/functions_icons/automation_functions/remote_unlock_req.svg + + static const String assetsRemoteUnlockReq = + "assets/icons/functions_icons/automation_functions/remote_unlock_req.svg"; + + //assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg + + static const String assetsRemoteUnlockViaApp = + "assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg"; + + //assets/icons/functions_icons/automation_functions/residual_electricity.svg + + static const String assetsResidualElectricity = + "assets/icons/functions_icons/automation_functions/residual_electricity.svg"; + + //assets/icons/functions_icons/automation_functions/temp_password_unlock.svg + + static const String assetsTempPasswordUnlock = + "assets/icons/functions_icons/automation_functions/temp_password_unlock.svg"; + + //assets/icons/functions_icons/automation_functions/self_test_result.svg + + static const String assetsSelfTestResult = + "assets/icons/functions_icons/automation_functions/self_test_result.svg"; + + ///assets/icons/functions_icons/automation_functions/presence.svg + + static const String assetsPresence = + "assets/icons/functions_icons/automation_functions/presence.svg"; + + //assets/icons/functions_icons/automation_functions/motion.svg + + static const String assetsMotion = + "assets/icons/functions_icons/automation_functions/motion.svg"; + + //assets/icons/functions_icons/automation_functions/current_temp.svg + + static const String assetsCurrentTemp = + "assets/icons/functions_icons/automation_functions/current_temp.svg"; + + //assets/icons/functions_icons/automation_functions/presence_state.svg + + static const String assetsPresenceState = + "assets/icons/functions_icons/automation_functions/presence_state.svg"; +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..05f1875 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,49 @@ +import 'dart:async'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:syncrow_app/firebase_options.dart'; +import 'package:syncrow_app/services/locator.dart'; +import 'package:syncrow_app/utils/bloc_observer.dart'; +import 'package:syncrow_app/utils/helpers/localization_helpers.dart'; +import 'my_app.dart'; +void main() { + //to observe the state of the blocs in the output console + Bloc.observer = MyBlocObserver(); + //to catch all the errors in the app and send them to firebase + runZonedGuarded(() async { + //to load the environment variables + const environment = String.fromEnvironment('FLAVOR', defaultValue: 'production'); + await dotenv.load(fileName: '.env.$environment'); + + // //this is to make the app work with the self-signed certificate + // HttpOverrides.global = MyHttpOverrides(); + WidgetsFlutterBinding.ensureInitialized(); + + //license for the font + // LicenseRegistry.addLicense(() async* { + // final license = + // await rootBundle.loadString('assets/fonts/roboto/LICENSE.txt'); + // yield LicenseEntryWithLineBreaks(['google_fonts'], license); + // }); + + //to initialize the locator + initialSetup(); + + await Firebase.initializeApp( + name: 'test2', + options: DefaultFirebaseOptions.currentPlatform, + ); + + //final SharedPreferences prefs = await SharedPreferences.getInstance(); + + //to save the locale in the shared preferences + await LocalizationService.saveLocale(const Locale("en", "AE")); + final savedLocale = await LocalizationService.savedLocale(); + + runApp(MyApp(savedLocale)); + }, (error, stackTrace) { + // FirebaseCrashlytics.instance.recordError(error, stackTrace, fatal: true); + }); +} diff --git a/lib/my_app.dart b/lib/my_app.dart new file mode 100644 index 0000000..e3317d9 --- /dev/null +++ b/lib/my_app.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; +import 'package:syncrow_app/utils/resource_manager/theme_manager.dart'; +import 'features/scene/bloc/scene_bloc/scene_bloc.dart'; +import 'navigation/router.dart' as router; +import 'navigation/routing_constants.dart'; + +class MyApp extends StatelessWidget { + final Locale locale; + + const MyApp(this.locale, {super.key}); + + @override + Widget build(BuildContext context) { + Constants.appBarHeight = + MediaQuery.sizeOf(context).height * Constants.appBarHeightPercentage; + Constants.bottomNavBarHeight = MediaQuery.sizeOf(context).height * + Constants.bottomNavBarHeightPercentage; + return MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => AuthCubit()), + BlocProvider( + create: (context) => EffectPeriodBloc(), + ), + BlocProvider(create: (context) => SmartSceneSelectBloc()), + BlocProvider(create: (context) => CreateSceneBloc()), + BlocProvider(create: (context) => SceneBloc()), + BlocProvider(create: (context) => ProfileBloc()), + ], + child: MaterialApp( + navigatorKey: NavigationService.navigatorKey, + scaffoldMessengerKey: NavigationService.snackbarKey, + debugShowCheckedModeBanner: false, + color: ColorsManager.primaryColor, + title: 'Syncrow App', + onGenerateRoute: router.Router.generateRoute, + initialRoute: Routes.splash, + themeMode: ThemeMode.system, + theme: ThemeManager.selectTheme(), + supportedLocales: const [ + Locale('en', ''), // English, no country code + Locale('ar', ''), // Arabic, no country code + ], + // locale: locale, + locale: const Locale('en', ''), + ), + ); + } +} diff --git a/lib/navigation/navigate_to_route.dart b/lib/navigation/navigate_to_route.dart new file mode 100644 index 0000000..e7c2715 --- /dev/null +++ b/lib/navigation/navigate_to_route.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +void navigateToRoute(BuildContext context, String targetRoute) { + bool routeFound = false; + + Navigator.popUntil(context, (route) { + if (route.settings.name == targetRoute) { + routeFound = true; + return true; + } + return route.isFirst; + }); + + if (!routeFound) { + Navigator.pushNamed(context, targetRoute); + } +} diff --git a/lib/navigation/navigation_service.dart b/lib/navigation/navigation_service.dart new file mode 100644 index 0000000..c0b1216 --- /dev/null +++ b/lib/navigation/navigation_service.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + static GlobalKey navigatorKey = GlobalKey(); + static GlobalKey? snackbarKey = + GlobalKey(); +} diff --git a/lib/navigation/router.dart b/lib/navigation/router.dart new file mode 100644 index 0000000..990b7fb --- /dev/null +++ b/lib/navigation/router.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/view/app_layout.dart'; +import 'package:syncrow_app/features/auth/view/otp_view.dart'; +import 'package:syncrow_app/features/auth/view/login_view.dart'; +import 'package:syncrow_app/features/auth/view/sign_up_view.dart'; +import 'package:syncrow_app/features/dashboard/view/dashboard_view.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_event.dart'; +import 'package:syncrow_app/features/layout/view/layout_view.dart'; +import 'package:syncrow_app/features/menu/view/menu_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/create_home/create_home_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_bloc.dart'; +import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_event.dart'; +import 'package:syncrow_app/features/scene/view/device_functions_view.dart'; +import 'package:syncrow_app/features/scene/view/scene_auto_settings.dart'; +import 'package:syncrow_app/features/scene/view/scene_tasks_view.dart'; +import 'package:syncrow_app/features/scene/view/scene_rooms_tabbar.dart'; +import 'package:syncrow_app/features/scene/view/scene_view.dart'; +import 'package:syncrow_app/features/scene/view/smart_automation_select_route.dart'; +import 'package:syncrow_app/features/splash/view/splash_view.dart'; +import 'routing_constants.dart'; + +class Router { + static Route generateRoute(RouteSettings settings) { + switch (settings.name) { + case Routes.splash: + return MaterialPageRoute( + builder: (_) => const SplashView(), settings: settings); + + // case Routes.devicesRoute: + // return MaterialPageRoute( + // builder: (_) => const DevicesView(), settings: settings); + + case Routes.profileRoute: + return MaterialPageRoute( + builder: (_) => const ProfileView(), settings: settings); + + case Routes.sceneRoute: + return MaterialPageRoute( + builder: (_) => const SceneView(), settings: settings); + + case Routes.layoutRoute: + return MaterialPageRoute( + builder: (_) => const LayoutPage(), settings: settings); + + case Routes.authLogin: + return MaterialPageRoute( + builder: (_) => const LoginView(), settings: settings); + + case Routes.otpRoute: + return MaterialPageRoute( + builder: (_) => const OtpView(), settings: settings); + + case Routes.authSignUp: + return MaterialPageRoute( + builder: (_) => const SignUpView(), settings: settings); + + case Routes.dashboardRoute: + return MaterialPageRoute( + builder: (_) => const DashboardView(), settings: settings); + + case Routes.homeRoute: + return MaterialPageRoute( + builder: (_) => const AppLayout(), settings: settings); + + case Routes.menuRoute: + return MaterialPageRoute( + builder: (_) => const MenuView(), settings: settings); + + case Routes.createUnit: + return MaterialPageRoute( + builder: (_) => const CreateUnitView(), settings: settings); + case Routes.sceneTasksRoute: + return MaterialPageRoute( + builder: (_) => const SceneTasksView(), settings: settings); + case Routes.sceneControlDevicesRoute: + return MaterialPageRoute( + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (BuildContext context) => + DeviceManagerBloc()..add(FetchAllDevices()), + ), + BlocProvider( + create: (BuildContext context) => TabBarBloc( + context.read()) + ..add(const TabChanged(selectedIndex: 0, roomId: '-1')), + ), + ], + child: const SceneRoomsTabBarDevicesView(), + ), + settings: settings); + case Routes.deviceFunctionsRoute: + return MaterialPageRoute( + builder: (_) => DeviceFunctionsView(), + settings: settings, + ); + case Routes.sceneAutoSettingsRoute: + return MaterialPageRoute( + builder: (_) => const SceneAutoSettings(), + settings: settings, + ); + + case Routes.smartAutomationSelectRoute: + return MaterialPageRoute( + builder: (_) => const SmartAutomationSelectView(), + settings: settings, + ); + default: + return MaterialPageRoute( + builder: (_) => Scaffold( + body: Center(child: Text('No route defined for ${settings.name}')), + ), + ); + } + } +} diff --git a/lib/navigation/routing_constants.dart b/lib/navigation/routing_constants.dart new file mode 100644 index 0000000..b64849f --- /dev/null +++ b/lib/navigation/routing_constants.dart @@ -0,0 +1,25 @@ +class Routes { + static const String splash = '/'; + static const String homeRoute = '/navigation'; + static const String devicesRoute = '/devices'; + static const String dashboardRoute = '/dashboard'; + static const String sceneRoute = '/scene'; + static const String layoutRoute = '/layout'; + static const String profileRoute = '/profile'; + static const String menuRoute = '/menu'; + static const String authRoute = '/auth'; + static const String authLogin = '$authRoute/login'; + static const String authSignUp = '$authRoute/signup'; + static const String authOneTimePassword = '$authRoute/one-time-password'; + static const String authForgotPassword = '$authRoute/forgot-password'; + static const String authDidNotGetCode = '$authRoute/did-not-get-code'; + static const String policyRoute = '/policy'; + static const String termsRoute = '/terms'; + static const String otpRoute = '/otp'; + static const String createUnit = '/create-unit'; + static const String sceneTasksRoute = '/scene-tasks'; + static const String sceneControlDevicesRoute = '/scene-control-devices'; + static const String deviceFunctionsRoute = '/device-functions'; + static const String sceneAutoSettingsRoute = '/scene-automation-settings'; + static const String smartAutomationSelectRoute = '/smart-automation-select'; +} diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart new file mode 100644 index 0000000..f8867fc --- /dev/null +++ b/lib/services/api/api_links_endpoints.dart @@ -0,0 +1,178 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +abstract class ApiEndpoints { + static String baseUrl = dotenv.env['BASE_URL'] ?? ''; + +////////////////////////////////////// Authentication /////////////////////////////// + + static const String signUp = '/authentication/user/signup'; + static const String login = '/authentication/user/login'; + static const String deleteUser = '/authentication/user/delete/{id}'; + static const String sendOtp = '/authentication/user/send-otp'; + static const String verifyOtp = '/authentication/user/verify-otp'; + static const String forgetPassword = '/authentication/user/forget-password'; + +////////////////////////////////////// Spaces /////////////////////////////////////// + + ///Community Module +//POST + static const String addCommunity = '/community'; + static const String addCommunityToUser = '/community/user'; +//GET + static const String communityByUuid = '/community/{communityUuid}'; + static const String communityChild = '/community/child/{communityUuid}'; + static const String communityUser = '/community/user/{userUuid}'; +//PUT + static const String renameCommunity = '/community/rename/{communityUuid}'; + + ///Building Module +//POST + static const String addBuilding = '/building'; + static const String addBuildingToUser = '/building/user'; +//GET + static const String buildingByUuid = '/building/{buildingUuid}'; + static const String buildingChild = '/building/child/{buildingUuid}'; + static const String buildingParent = '/building/parent/{buildingUuid}'; + static const String buildingUser = '/building/user/{userUuid}'; +//PUT + static const String renameBuilding = '/building/rename/{buildingUuid}'; + + ///Floor Module +//POST + static const String addFloor = '/floor'; + static const String addFloorToUser = '/floor/user'; +//GET + static const String floorByUuid = '/floor/{floorUuid}'; + static const String floorChild = '/floor/child/{floorUuid}'; + static const String floorParent = '/floor/parent/{floorUuid}'; + static const String floorUser = '/floor/user/{userUuid}'; +//PUT + static const String renameFloor = '/floor/rename/{floorUuid}'; + + ///Unit Module +//POST + static const String addUnit = '/unit'; + static const String addUnitToUser = '/unit/user'; +//GET + static const String unitByUuid = '/unit/'; + static const String unitChild = '/unit/child/'; + static const String unitParent = '/unit/parent/{unitUuid}'; + static const String unitUser = '/unit/user/'; + static const String invitationCode = '/unit/{unitUuid}/invitation-code'; + static const String verifyInvitationCode = '/unit/user/verify-code'; + +//PUT + static const String renameUnit = '/unit/rename/{unitUuid}'; + + ///Room Module +//POST + static const String addRoom = '/room'; + static const String addRoomToUser = '/room/user'; +//GET + static const String roomByUuid = '/room/{roomUuid}'; + static const String roomParent = '/room/parent/{roomUuid}'; + static const String roomUser = '/room/user/{userUuid}'; +//PUT + static const String renameRoom = '/room/rename/{roomUuid}'; + + ///Group Module +//POST + static const String addGroup = '/group'; + static const String controlGroup = '/group/control'; +//GET + static const String groupBySpace = '/group/{unitUuid}'; + static const String devicesByGroupName = '/group/{unitUuid}/devices/{groupName}'; + + static const String groupByUuid = '/group/{groupUuid}'; +//DELETE + static const String deleteGroup = '/group/{groupUuid}'; + +////////////////////////////////////// Devices /////////////////////////////////////// + ///Device Module +//POST + static const String addDeviceToRoom = '/device/room'; + static const String addDeviceToGroup = '/device/group'; + static const String controlDevice = '/device/{deviceUuid}/control'; + static const String firmwareDevice = '/device/{deviceUuid}/firmware/{firmwareVersion}'; + static const String getDevicesByUserId = '/device/user/{userId}'; + static const String getDevicesByUnitId = '/device/unit/{unitUuid}'; + static const String openDoorLock = '/door-lock/open/{doorLockUuid}'; + +//GET + static const String deviceByRoom = '/device/room'; + static const String deviceByUuid = '/device/{deviceUuid}'; + static const String deviceFunctions = '/device/{deviceUuid}/functions'; + static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; + static const String deviceFunctionsStatus = '/device/{deviceUuid}/functions/status'; + + ///Device Permission Module +//POST + static const String addDevicePermission = '/device-permission/add'; +//GET + static const String devicePermissionList = '/device-permission/list'; +//PUT + static const String editDevicePermission = '/device-permission/edit/{userId}'; + + static const String assignDeviceToRoom = '/device/room'; + + /// Scene & Automation API //////////////////// + /// POST + static const String createScene = '/scene/tap-to-run'; + static const String triggerScene = '/scene/tap-to-run/trigger/{sceneId}'; + static const String createAutomation = '/automation'; + + /// GET + static const String getUnitScenes = '/scene/tap-to-run/{unitUuid}'; + + static const String getScene = '/scene/tap-to-run/details/{sceneId}'; + + static const String getUnitAutomation = '/automation/{unitUuid}'; + + static const String getAutomationDetails = '/automation/details/{automationId}'; + + /// PUT + static const String updateScene = '/scene/tap-to-run/{sceneId}'; + + static const String updateAutomation = '/automation/{automationId}'; + + static const String updateAutomationStatus = '/automation/status/{automationId}'; + + /// DELETE + static const String deleteScene = '/scene/tap-to-run/{unitUuid}/{sceneId}'; + + static const String deleteAutomation = '/automation/{unitUuid}/{automationId}'; + + //////////////////////Door Lock ////////////////////// + //online + static const String addTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}'; + static const String getTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}'; + + //one-time offline + static const String addOneTimeTemporaryPassword = + '/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; + static const String getOneTimeTemporaryPassword = + '/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; + + //user + + static const String getUser = '/user/{userUuid}'; + static const String saveRegion = '/user/region/{userUuid}'; + static const String saveTimeZone = '/user/timezone/{userUuid}'; + static const String saveName = '/user/name/{userUuid}'; + static const String sendPicture = '/user/profile-picture/{userUuid}'; + static const String getRegion = '/region'; + static const String getTimezone = '/timezone'; + + //multiple-time offline + static const String addMultipleTimeTemporaryPassword = + '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + static const String getMultipleTimeTemporaryPassword = + '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + + static const String renamePassword = + '/door-lock/temporary-password/{doorLockUuid}/offline/{passwordId}'; + + //multiple-time offline + static const String deleteTemporaryPassword = + '/door-lock/temporary-password/online/{doorLockUuid}/{passwordId}'; +} diff --git a/lib/services/api/authentication_api.dart b/lib/services/api/authentication_api.dart new file mode 100644 index 0000000..76bb969 --- /dev/null +++ b/lib/services/api/authentication_api.dart @@ -0,0 +1,53 @@ +import 'package:syncrow_app/features/auth/model/login_with_email_model.dart'; +import 'package:syncrow_app/features/auth/model/signup_model.dart'; +import 'package:syncrow_app/features/auth/model/token.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class AuthenticationAPI { + static Future> verifyPassCode({required Map body}) async { + final response = await HTTPService().post( + path: ApiEndpoints.verifyOtp, + body: body, + showServerMessage: false, + expectedResponseModel: (json) => json); + return response; + } + + static Future loginWithEmail({required LoginWithEmailModel model}) async { + final response = await HTTPService().post( + path: ApiEndpoints.login, + body: model.toJson(), + showServerMessage: false, + expectedResponseModel: (json) => Token.fromJson(json['data'])); + return response; + } + + static Future signUp({required SignUpModel model}) async { + final response = await HTTPService().post( + path: ApiEndpoints.signUp, + body: model.toJson(), + showServerMessage: false, + expectedResponseModel: (json) => json['statusCode'] == 201); + return response; + } + + static Future> sendOtp({required Map body}) async { + final response = await HTTPService().post( + path: ApiEndpoints.sendOtp, + body: body, + showServerMessage: false, + expectedResponseModel: (json) => json['data']); + return response; + } + + static Future> forgetPassword({required String email,required String password ,}) async { + Map params = {"email": email, "password": password,}; + final response = await HTTPService().post( + path: ApiEndpoints.forgetPassword, + body: params, + showServerMessage: false, + expectedResponseModel: (json) => json['data']); + return response; + } +} diff --git a/lib/services/api/devices_api.dart b/lib/services/api/devices_api.dart new file mode 100644 index 0000000..f347cdd --- /dev/null +++ b/lib/services/api/devices_api.dart @@ -0,0 +1,273 @@ +import 'dart:async'; +import 'package:syncrow_app/features/devices/model/device_category_model.dart'; +import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/function_model.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +import '../../features/devices/model/create_temporary_password_model.dart'; + +class DevicesAPI { + static final HTTPService _httpService = HTTPService(); + + static Future> firmwareDevice( + {required String deviceId, required String firmwareVersion}) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.firmwareDevice + .replaceAll('{deviceUuid}', deviceId) + .replaceAll('{firmwareVersion}', firmwareVersion), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> controlDevice( + DeviceControlModel controlModel, String deviceId) async { + try { + + final response = await _httpService.post( + path: ApiEndpoints.controlDevice.replaceAll('{deviceUuid}', deviceId), + body: controlModel.toJson(), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future openDoorLock(String deviceId) async { + final response = await _httpService.post( + path: ApiEndpoints.openDoorLock.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } + + static Future> fetchGroups(String spaceId) async { + // Map params = {"homeId": spaceId, "pageSize": 100, "pageNo": 1}; + + final response = await _httpService.get( + path: ApiEndpoints.groupBySpace.replaceAll('{unitUuid}', spaceId), + // queryParameters: params, + showServerMessage: false, + expectedResponseModel: (json) => DevicesCategoryModel.fromJsonList(json), + ); + return response; + } + + static Future> getDeviceStatus(String deviceId) async { + final response = await _httpService.get( + path: ApiEndpoints.deviceFunctionsStatus.replaceAll('{deviceUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + static Future> renamePass( + {required String name, required String doorLockUuid, required String passwordId}) async { + final response = await _httpService.put( + path: ApiEndpoints.renamePassword + .replaceAll('{doorLockUuid}', doorLockUuid) + .replaceAll('{passwordId}', passwordId), + body: {"name": name}, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + /// Get Device Functions + static Future deviceFunctions(String deviceId) async { + final response = await _httpService.get( + path: ApiEndpoints.deviceFunctions.replaceAll('{deviceUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + final functions = FunctionModel.fromJson(json); + return functions; + }); + return response; + } + + static Future> getDeviceByGroupName(String unitId, String groupName) async { + final response = await _httpService.get( + path: ApiEndpoints.devicesByGroupName + .replaceAll('{unitUuid}', unitId) + .replaceAll('{groupName}', groupName), + showServerMessage: false, + expectedResponseModel: (json) { + if (json == null || json.isEmpty || json == []) { + return []; + } + List devices = []; + for (var device in json) { + devices.add(DeviceModel.fromJson(device)); + } + return devices; + }, + ); + return response; + } + + static Future> getDevicesByRoomId(String roomId) async { + final response = await _httpService.get( + path: ApiEndpoints.deviceByRoom, + queryParameters: {"roomUuid": roomId}, + showServerMessage: false, + expectedResponseModel: (json) { + if (json == null || json.isEmpty || json == []) { + return []; + } + List devices = []; + for (var device in json) { + devices.add(DeviceModel.fromJson(device)); + } + return devices; + }, + ); + return response; + } + + static Future> getDevicesByGatewayId(String gatewayId) async { + final response = await _httpService.get( + path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), + showServerMessage: false, + expectedResponseModel: (json) { + List devices = []; + if (json == null || json.isEmpty || json == []) { + return devices; + } + for (var device in json['devices']) { + devices.add(DeviceModel.fromJson(device)); + } + return devices; + }, + ); + return response; + } + + static Future getTemporaryPasswords( + String deviceId, + ) async { + final response = await _httpService.get( + path: ApiEndpoints.getTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + static Future getOneTimePasswords(String deviceId) async { + final response = await _httpService.get( + path: ApiEndpoints.getOneTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + static Future getTimeLimitPasswords(String deviceId) async { + final response = await _httpService.get( + path: ApiEndpoints.getMultipleTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + //create password + static Future createPassword({ + required String name, + required String password, + required String effectiveTime, + required String invalidTime, + required String deviceId, + List? scheduleList, + }) async { + Map body = { + "name": name, + "password": password, + "effectiveTime": effectiveTime, + "invalidTime": invalidTime, + }; + if (scheduleList != null) { + body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); + } + final response = await _httpService.post( + path: ApiEndpoints.addTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + body: body, + showServerMessage: false, + expectedResponseModel: (json) => json, + ); + return response; + } + + static Future generateOneTimePassword({deviceId}) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addOneTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future generateMultiTimePassword({deviceId, effectiveTime, invalidTime}) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addMultipleTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: true, + body: {"effectiveTime": effectiveTime, "invalidTime": invalidTime}, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> deletePassword( + {required String passwordId, required String deviceId}) async { + final response = await _httpService.delete( + path: ApiEndpoints.deleteTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId) + .replaceAll('{passwordId}', passwordId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } +} diff --git a/lib/services/api/home_creation_api.dart b/lib/services/api/home_creation_api.dart new file mode 100644 index 0000000..5ac6d5a --- /dev/null +++ b/lib/services/api/home_creation_api.dart @@ -0,0 +1,166 @@ +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class HomeCreation { + static final HTTPService _httpService = HTTPService(); + + static Future> createCommunity(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addCommunity, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToCommunity(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addCommunityToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createBuilding(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addBuilding, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToBuilding(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addBuildingToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createFloor(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addFloor, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToFloor(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addBuildingToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createUnit(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addUnit, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToUnit(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addUnitToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> createRoom(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addRoom, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> assignUserToRoom(Map body) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addRoomToUser, + body: body, + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/services/api/home_management_api.dart b/lib/services/api/home_management_api.dart new file mode 100644 index 0000000..9eb35b2 --- /dev/null +++ b/lib/services/api/home_management_api.dart @@ -0,0 +1,58 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class HomeManagementAPI { + static final HTTPService _httpService = HTTPService(); + + static Future> fetchDevicesByUserId() async { + var storage = const FlutterSecureStorage(); + var userId = await storage.read(key: UserModel.userUuidKey) ?? ''; + + List list = []; + + await _httpService.get( + path: ApiEndpoints.getDevicesByUserId.replaceAll("{userId}", userId), + showServerMessage: false, + expectedResponseModel: (json) { + json.forEach((value) { + list.add(DeviceModel.fromJson(value)); + }); + }); + + return list; + } + + static Future> fetchDevicesByUnitId() async { + List list = []; + await _httpService.get( + path: ApiEndpoints.getDevicesByUnitId.replaceAll( + "{unitUuid}", HomeCubit.getInstance().selectedSpace?.id ?? ''), + showServerMessage: false, + expectedResponseModel: (json) { + json.forEach((value) { + list.add(DeviceModel.fromJson(value)); + }); + }); + return list; + } + + static Future> assignDeviceToRoom( + Map body) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.assignDeviceToRoom, + body: body, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart new file mode 100644 index 0000000..f153686 --- /dev/null +++ b/lib/services/api/http_interceptor.dart @@ -0,0 +1,81 @@ +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/auth/model/token.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'dart:async'; +import 'package:syncrow_app/services/api/network_exception.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class HTTPInterceptor extends InterceptorsWrapper { + List headerExclusionList = []; + + List headerExclusionListOfAddedParameters = [ + ApiEndpoints.login, + ]; + @override + void onResponse(Response response, ResponseInterceptorHandler handler) async { + if (await validateResponse(response)) { + super.onResponse(response, handler); + } else { + handler.reject(DioException(requestOptions: response.requestOptions, response: response)); + } + } + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { + var storage = const FlutterSecureStorage(); + var token = await storage.read(key: Token.loginAccessTokenKey); + if (checkHeaderExclusionListOfAddedParameters(options.path)) { + options.headers.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token"); + } + // options.headers['Authorization'] = 'Bearer ${'${token!}123'}'; + super.onRequest(options, handler); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + ServerFailure failure = ServerFailure.fromDioError(err); + if (failure.toString().isNotEmpty) { + CustomSnackBar.displaySnackBar(failure.toString()); + } + var storage = const FlutterSecureStorage(); + var token = await storage.read(key: Token.loginAccessTokenKey); + if (err.response?.statusCode == 401 && token != null) { + await AuthCubit.get(NavigationService.navigatorKey.currentContext!).logout(); + } + super.onError(err, handler); + } + + /// Validates the response and returns true if it is successful (status code 2xx). + Future validateResponse(Response response) async { + if (response.statusCode != null) { + if (response.statusCode! >= 200 && response.statusCode! < 300) { + // If the response status code is within the successful range (2xx), + // return true indicating a successful response. + return true; + } else { + // If the response status code is not within the successful range (2xx), + // return false indicating an unsuccessful response. + return false; + } + } else { + // If the response status code is null, return false indicating an unsuccessful response. + return false; + } + } + + checkHeaderExclusionListOfAddedParameters(String path) { + bool shouldAddHeader = true; + + for (var urlConstant in headerExclusionListOfAddedParameters) { + if (path.contains(urlConstant)) { + shouldAddHeader = false; + } + } + return shouldAddHeader; + } +} diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart new file mode 100644 index 0000000..36e8e71 --- /dev/null +++ b/lib/services/api/http_service.dart @@ -0,0 +1,134 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_interceptor.dart'; +import 'package:syncrow_app/services/locator.dart'; + +class HTTPService { + final Dio client = serviceLocator.get(); + + // final navigatorKey = GlobalKey(); + + String certificateString = ""; + + static Dio setupDioClient() { + Dio client = Dio( + BaseOptions( + baseUrl: ApiEndpoints.baseUrl, + receiveDataWhenStatusError: true, + followRedirects: false, + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30), + ), + ); + + client.interceptors.add(serviceLocator.get()); + return client; + } + + Future get({ + required String path, + Map? queryParameters, + required T Function(dynamic) expectedResponseModel, + bool showServerMessage = true, + }) async { + try { + final response = await client.get( + path, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future post( + {required String path, + Map? queryParameters, + Options? options, + dynamic body, + bool showServerMessage = true, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.post( + path, + data: body, + queryParameters: queryParameters, + options: options, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future patch( + {required String path, + Map? queryParameters, + dynamic body, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.patch( + path, + data: body, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future put( + {required String path, + Map? queryParameters, + dynamic body, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.put( + path, + data: body, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future download( + {required String path, + required String savePath, + Map? queryParameters, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.download( + path, + savePath, + onReceiveProgress: (current, total) {}, + ); + + return expectedResponseModel(response.data); + // return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future delete({ + required String path, + Map? queryParameters, + required T Function(dynamic) expectedResponseModel, + bool showServerMessage = true, + }) async { + try { + final response = await client.delete( + path, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } +} diff --git a/lib/services/api/my_http_overrides.dart b/lib/services/api/my_http_overrides.dart new file mode 100644 index 0000000..3707be3 --- /dev/null +++ b/lib/services/api/my_http_overrides.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +// We use this class to skip the problem of SSL certification and solve the Image.network(url) issue +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } +} diff --git a/lib/services/api/network_exception.dart b/lib/services/api/network_exception.dart new file mode 100644 index 0000000..f6fb88c --- /dev/null +++ b/lib/services/api/network_exception.dart @@ -0,0 +1,74 @@ +import 'package:dio/dio.dart'; + +abstract class Failure { + final String errMessage; + + Failure(this.errMessage); +} + +class ServerFailure extends Failure { + ServerFailure(super.errMessage); + + @override + String toString() { + return errMessage; + } + + factory ServerFailure.fromDioError(DioException dioError) { + switch (dioError.type) { + case DioExceptionType.connectionTimeout: + return ServerFailure("Connection timeout with the Server."); + case DioExceptionType.sendTimeout: + return ServerFailure("Send timeout with the Server."); + + case DioExceptionType.receiveTimeout: + return ServerFailure("Receive timeout with the Server."); + + case DioExceptionType.badCertificate: + return ServerFailure("Bad certificate!"); + + case DioExceptionType.badResponse: + { + // var document = parser.parse(dioError.response!.data.toString()); + // var message = document.body!.text; + return ServerFailure.fromResponse( + dioError.response!.statusCode!, dioError.response?.data['message'] ?? "Error"); + } + case DioExceptionType.cancel: + return ServerFailure("The request to ApiServer was canceled"); + + case DioExceptionType.connectionError: + return ServerFailure("No Internet Connection"); + + case DioExceptionType.unknown: + return ServerFailure("Unexpected Error, Please try again!"); + + default: + return ServerFailure("Unexpected Error, Please try again!"); + } + } + + factory ServerFailure.fromResponse(int? statusCode, dynamic responseMessage) { + switch (statusCode) { + case 401: + case 403: + return ServerFailure(responseMessage); + case 400: + List errors = []; + if (responseMessage is List) { + for (var error in responseMessage) { + errors.add(error); + } + } else { + return ServerFailure(responseMessage); + } + return ServerFailure(errors.join('\n')); + case 404: + return ServerFailure(""); + case 500: + return ServerFailure(responseMessage); + default: + return ServerFailure("Opps there was an Error, Please try again!"); + } + } +} diff --git a/lib/services/api/profile_api.dart b/lib/services/api/profile_api.dart new file mode 100644 index 0000000..c942f68 --- /dev/null +++ b/lib/services/api/profile_api.dart @@ -0,0 +1,114 @@ +import 'dart:async'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; +import 'package:syncrow_app/features/menu/model/region_model.dart'; +import 'package:syncrow_app/features/menu/model/time_zone_model.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class ProfileApi { + static final HTTPService _httpService = HTTPService(); + + static Future> saveName({String? firstName, String? lastName,}) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.saveName.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "firstName": firstName, + "lastName": lastName + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future saveRegion({String? regionUuid,}) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.saveRegion.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "regionUuid": regionUuid, + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future saveTimeZone({String? regionUuid,}) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.saveTimeZone.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "timezoneUuid": regionUuid, + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> saveImage(String image) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.sendPicture.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "profilePicture": 'data:image/png;base64,$image' + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + Future fetchUserInfo(userId) async { + final response = await _httpService.get( + path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), + showServerMessage: true, + expectedResponseModel: (json) { + return UserModel.fromJson(json); + } + ); + return response; + } + + static Future> fetchRegion() async { + final response = await _httpService.get( + path: ApiEndpoints.getRegion, + showServerMessage: true, + expectedResponseModel: (json) { + return (json as List).map((zone) => RegionModel.fromJson(zone)).toList(); + } + ); + return response as List; + } + + static Future> fetchTimeZone() async { + final response = await _httpService.get( + path: ApiEndpoints.getTimezone, + showServerMessage: true, + expectedResponseModel: (json) { + return (json as List).map((zone) => TimeZone.fromJson(zone)).toList(); + } + ); + return response as List; + } + +} diff --git a/lib/services/api/scene_api.dart b/lib/services/api/scene_api.dart new file mode 100644 index 0000000..e26920c --- /dev/null +++ b/lib/services/api/scene_api.dart @@ -0,0 +1,220 @@ +import 'package:syncrow_app/features/scene/model/create_automation_model.dart'; +import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; +import 'package:syncrow_app/features/scene/model/scene_details_model.dart'; +import 'package:syncrow_app/features/scene/model/scenes_model.dart'; +import 'package:syncrow_app/features/scene/model/update_automation.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class SceneApi { + static final HTTPService _httpService = HTTPService(); + +//create scene + static Future> createScene( + CreateSceneModel createSceneModel) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.createScene, + body: createSceneModel.toMap(), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + +// create automation + static Future> createAutomation( + CreateAutomationModel createAutomationModel) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.createAutomation, + body: createAutomationModel.toMap(), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + //get scene by unit id + + static Future> getScenesByUnitId(String unitId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUnitScenes.replaceAll('{unitUuid}', unitId), + showServerMessage: false, + expectedResponseModel: (json) { + List scenes = []; + for (var scene in json) { + scenes.add(ScenesModel.fromJson(scene)); + } + return scenes; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + //getAutomation + + static Future> getAutomationByUnitId(String unitId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUnitAutomation.replaceAll('{unitUuid}', unitId), + showServerMessage: false, + expectedResponseModel: (json) { + List scenes = []; + for (var scene in json) { + scenes.add(ScenesModel.fromJson(scene)); + } + return scenes; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future triggerScene(String sceneId) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.triggerScene.replaceAll('{sceneId}', sceneId), + showServerMessage: false, + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } + +//automation details + static Future getAutomationDetails( + String automationId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getAutomationDetails + .replaceAll('{automationId}', automationId), + showServerMessage: false, + expectedResponseModel: (json) => SceneDetailsModel.fromJson(json), + ); + return response; + } catch (e) { + rethrow; + } + } + + //updateAutomationStatus + static Future updateAutomationStatus(String automationId, + AutomationStatusUpdate createAutomationEnable) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.updateAutomationStatus + .replaceAll('{automationId}', automationId), + body: createAutomationEnable.toMap(), + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } + + //getScene + + static Future getSceneDetails(String sceneId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId), + showServerMessage: false, + expectedResponseModel: (json) => SceneDetailsModel.fromJson(json), + ); + return response; + } catch (e) { + rethrow; + } + } + + //update Scene + static updateScene(CreateSceneModel createSceneModel, String sceneId) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId), + body: createSceneModel + .toJson(sceneId.isNotEmpty == true ? sceneId : null), + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + //update automation + static updateAutomation( + CreateAutomationModel createAutomationModel, String automationId) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.updateAutomation + .replaceAll('{automationId}', automationId), + body: createAutomationModel + .toJson(automationId.isNotEmpty == true ? automationId : null), + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + //delete Scene + + static Future deleteScene( + {required String unitUuid, required String sceneId}) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteScene + .replaceAll('{sceneId}', sceneId) + .replaceAll('{unitUuid}', unitUuid), + showServerMessage: false, + expectedResponseModel: (json) => json['statusCode'] == 200, + ); + return response; + } catch (e) { + rethrow; + } + } + + // delete automation + static Future deleteAutomation( + {required String unitUuid, required String automationId}) async { + try { + final response = await _httpService.delete( + path: ApiEndpoints.deleteAutomation + .replaceAll('{automationId}', automationId) + .replaceAll('{unitUuid}', unitUuid), + showServerMessage: false, + expectedResponseModel: (json) => json['statusCode'] == 200, + ); + return response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/services/api/spaces_api.dart b/lib/services/api/spaces_api.dart new file mode 100644 index 0000000..bd02075 --- /dev/null +++ b/lib/services/api/spaces_api.dart @@ -0,0 +1,59 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; +import 'package:syncrow_app/features/devices/model/room_model.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class SpacesAPI { + static final HTTPService _httpService = HTTPService(); + + static Future> getUnitsByUserId() async { + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + final response = await _httpService.get( + path: "${ApiEndpoints.unitUser}$uuid", + showServerMessage: false, + expectedResponseModel: (json) => SpaceModel.fromJsonList(json), + ); + return response; + } + + static Future> getRoomsBySpaceId(String unitId) async { + final response = await _httpService.get( + path: "${ApiEndpoints.unitChild}$unitId", + queryParameters: {"page": 1, "pageSize": 10}, + showServerMessage: false, + expectedResponseModel: (json) { + List rooms = []; + for (var room in json['children']) { + rooms.add(RoomModel.fromJson(room)); + } + return rooms; + }, + ); + return response; + } + + static Future generateInvitationCode( + String unitId, + ) async { + final response = await _httpService.get( + path: ApiEndpoints.invitationCode.replaceAll('{unitUuid}', unitId), + showServerMessage: false, + expectedResponseModel: (json) => json['invitationCode'], + ); + return response; + } + + static Future joinUnit( + Map body, + ) async { + final response = await _httpService.post( + path: ApiEndpoints.verifyInvitationCode, + showServerMessage: false, + body: body, + expectedResponseModel: (json) => json['success'], + ); + return response; + } +} diff --git a/lib/services/locator.dart b/lib/services/locator.dart new file mode 100644 index 0000000..da6399a --- /dev/null +++ b/lib/services/locator.dart @@ -0,0 +1,13 @@ +import 'package:dio/dio.dart'; +import 'package:get_it/get_it.dart'; +import 'package:syncrow_app/services/api/http_interceptor.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +final GetIt serviceLocator = GetIt.instance; +// setupLocator() // to search for dependency injection in flutter +initialSetup() { + serviceLocator.registerSingleton(HTTPInterceptor()); + //Base classes + + serviceLocator.registerSingleton(HTTPService.setupDioClient()); +} diff --git a/lib/utils/bloc_observer.dart b/lib/utils/bloc_observer.dart new file mode 100644 index 0000000..f93399a --- /dev/null +++ b/lib/utils/bloc_observer.dart @@ -0,0 +1,29 @@ +// ignore_for_file: avoid_print + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyBlocObserver extends BlocObserver { + @override + void onCreate(BlocBase bloc) { + super.onCreate(bloc); + print('onCreate -- ${bloc.runtimeType}'); + } + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + print('onChange -- ${bloc.runtimeType}, $change'); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + print('onError -- ${bloc.runtimeType}, $error'); + super.onError(bloc, error, stackTrace); + } + + @override + void onClose(BlocBase bloc) { + print('onClose -- ${bloc.runtimeType}'); + super.onClose(bloc); + } +} diff --git a/lib/utils/context_extension.dart b/lib/utils/context_extension.dart new file mode 100644 index 0000000..c9db183 --- /dev/null +++ b/lib/utils/context_extension.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/bottom_sheet/custom_bottom_sheet.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +extension ContextExtension on BuildContext { + Future goTo(String newRouteName) async { + // go(newRouteName); + await Navigator.pushNamed(this, newRouteName); + } + + double get width => MediaQuery.sizeOf(this).width; + + double get height => MediaQuery.sizeOf(this).height; + + InputDecorationTheme get inputDecoration => + Theme.of(this).inputDecorationTheme; + + TextStyle get displayLarge => Theme.of(this).textTheme.displayLarge!; + + TextStyle get displayMedium => Theme.of(this).textTheme.displayMedium!; + + TextStyle get displaySmall => Theme.of(this).textTheme.displaySmall!; + + TextStyle get titleLarge => Theme.of(this).textTheme.titleLarge!; + + TextStyle get titleMedium => Theme.of(this).textTheme.titleMedium!; + + TextStyle get titleSmall => Theme.of(this).textTheme.titleSmall!; + + TextStyle get bodyLarge => Theme.of(this).textTheme.bodyLarge!; + + TextStyle get bodyMedium => Theme.of(this).textTheme.bodyMedium!; + + TextStyle get bodySmall => Theme.of(this).textTheme.bodySmall!; + + void customBottomSheet({Widget? child}) { + showModalBottomSheet( + context: this, + isScrollControlled: true, + builder: (BuildContext context) { + return CustomBottomSheet( + child: child, + ); + }, + ); + } + + void showCustomSnackbar({required String message, Widget? icon}) { + ScaffoldMessenger.of(this).showSnackBar( + SnackBar( + content: Row( + children: [ + Expanded(child: Text(message)), + icon ?? const SizedBox.shrink(), + ], + ), + ), + ); + } + + void customAlertDialog({ + required Widget alertBody, + required String title, + required VoidCallback onConfirm, + VoidCallback? onDismiss, + bool? hideConfirmButton, + }) { + showDialog( + context: this, + builder: (BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: MediaQuery.sizeOf(context).width, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + /// header widget + BodyMedium( + text: title, + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontsManager.extraBold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 50, + ), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + + /// custom body content + Flexible(child: SingleChildScrollView(child: alertBody)), + + /// Footer buttons + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + hideConfirmButton != true + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: onDismiss ?? + () { + Navigator.pop(context); + }, + child: Center( + child: BodyMedium( + text: 'Cancel', + style: context.bodyMedium + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + Container( + height: 50, + width: 1, + color: ColorsManager.greyColor, + ), + GestureDetector( + onTap: onConfirm, + child: Center( + child: BodyMedium( + text: 'Confirm', + style: context.bodyMedium.copyWith( + color: + ColorsManager.primaryColorWithOpacity), + ), + ), + ), + ], + ) + : Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: GestureDetector( + onTap: onDismiss ?? + () { + Navigator.pop(context); + }, + child: Center( + child: BodyMedium( + text: 'Cancel', + style: context.bodyMedium + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/utils/helpers/custom_page_route.dart b/lib/utils/helpers/custom_page_route.dart new file mode 100644 index 0000000..c39585a --- /dev/null +++ b/lib/utils/helpers/custom_page_route.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class CustomPageRoute extends PageRouteBuilder { + final WidgetBuilder builder; + + CustomPageRoute({required this.builder}) + : super( + pageBuilder: (BuildContext context, Animation animation, + Animation secondaryAnimation) { + return builder(context); + }, + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + ); +} diff --git a/lib/utils/helpers/decode_base64.dart b/lib/utils/helpers/decode_base64.dart new file mode 100644 index 0000000..e047399 --- /dev/null +++ b/lib/utils/helpers/decode_base64.dart @@ -0,0 +1,21 @@ +import 'dart:convert'; + +String decodeBase64(String str) { + //'-', '+' 62nd char of encoding, '_', '/' 63rd char of encoding + String output = str.replaceAll('-', '+').replaceAll('_', '/'); + switch (output.length % 4) { + // Pad with trailing '=' + case 0: // No pad chars in this case + break; + case 2: // Two pad chars + output += '=='; + break; + case 3: // One pad char + output += '='; + break; + default: + throw Exception('Illegal base64url string!"'); + } + + return utf8.decode(base64Url.decode(output)); +} diff --git a/lib/utils/helpers/life_cycle_event_handler.dart b/lib/utils/helpers/life_cycle_event_handler.dart new file mode 100644 index 0000000..46daf9b --- /dev/null +++ b/lib/utils/helpers/life_cycle_event_handler.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class LifecycleEventHandler extends WidgetsBindingObserver { + final AsyncCallback resumeCallBack; + final AsyncCallback suspendingCallBack; + final AsyncCallback onPauseCallBack; + final AsyncCallback inactiveCallBack; + + LifecycleEventHandler( + {required this.resumeCallBack, + required this.suspendingCallBack, + required this.onPauseCallBack, + required this.inactiveCallBack}); + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { + switch (state) { + case AppLifecycleState.resumed: + await resumeCallBack(); + break; + case AppLifecycleState.inactive: + await inactiveCallBack(); + break; + case AppLifecycleState.paused: + await onPauseCallBack(); + break; + case AppLifecycleState.detached: + await suspendingCallBack(); + break; + case AppLifecycleState.hidden: + // TODO: Handle this case. + } + } +} diff --git a/lib/utils/helpers/localization_helpers.dart b/lib/utils/helpers/localization_helpers.dart new file mode 100644 index 0000000..8cadc78 --- /dev/null +++ b/lib/utils/helpers/localization_helpers.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncrow_app/utils/resource_manager/constants.dart'; + +class LocalizationService { + static saveLocale(Locale locale) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString("Locale Language Code", locale.languageCode); + if (locale.countryCode != null) { + await prefs.setString("Locale Country Code", locale.countryCode!); + } + } + + static Future savedLocale() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var savedLanguageCode = prefs.getString(Constants.languageCode); + var savedCountryCode = prefs.getString(Constants.countryCode); + var savedLocale = const Locale("ar", "JO"); + + if (savedCountryCode != null && savedLanguageCode != null) { + savedLocale = Locale(savedLanguageCode, savedCountryCode); + } + + return savedLocale; + } +} diff --git a/lib/utils/helpers/misc_string_helpers.dart b/lib/utils/helpers/misc_string_helpers.dart new file mode 100644 index 0000000..14a2a92 --- /dev/null +++ b/lib/utils/helpers/misc_string_helpers.dart @@ -0,0 +1,78 @@ +import 'dart:io'; + +class StringHelpers { + // TODO ( to Mohammad Salameh): convert this to extension method + static String enhanceFileName(File file) { + var fileName = " "; + final filePath = file.path; + final fileStringArray = filePath.split("/"); + fileName = fileStringArray.last; + if (fileName.length > 20) { + // crop the name and keep the extension + final firstPart = fileName.substring(0, 17); + fileName = "$firstPart..."; + } + return fileName; + } + + static String returnEnglishStringFromArabicNumber(String arabicString) { + var englishStringNumber = arabicString; + englishStringNumber = englishStringNumber.replaceAll("Ù ", "0"); + englishStringNumber = englishStringNumber.replaceAll("Ù¡", "1"); + englishStringNumber = englishStringNumber.replaceAll("Ù¢", "2"); + englishStringNumber = englishStringNumber.replaceAll("Ù£", "3"); + englishStringNumber = englishStringNumber.replaceAll("Ù¤", "4"); + englishStringNumber = englishStringNumber.replaceAll("Ù¥", "5"); + englishStringNumber = englishStringNumber.replaceAll("Ù¦", "6"); + englishStringNumber = englishStringNumber.replaceAll("Ù§", "7"); + englishStringNumber = englishStringNumber.replaceAll("Ù¨", "8"); + englishStringNumber = englishStringNumber.replaceAll("Ù©", "9"); + return englishStringNumber; + } + + static String returnTheFirstThreeWords(String completeString) { + // We use this method to return a nice readable word that is shorter than 26 characters + // split the string to substrings with the space as the pattern + final arrayOfSubStrings = completeString.split(" "); + String newString = ""; + // keep adding the substrings until 26 is reached + var index = 0; + while (newString.length < 20 && index < arrayOfSubStrings.length) { + newString = "$newString + ${arrayOfSubStrings[index]} "; + index++; + } + return newString; + } + + static int getIntFromString(String? numberString) { + if (numberString != null) { + final numberInt = int.tryParse(numberString); + if (numberInt != null) { + return numberInt; + } else { + final numberDouble = double.tryParse(numberString); + if (numberDouble != null) { + return numberDouble.toInt(); + } else { + return 0; + } + } + } else { + return 0; + } + } + + static String toTitleCase(String text) { + if (text.contains(' ')) { + //remove empty elements + String title = text.split(' ').where((element) => element.isNotEmpty).join(' '); + String result = title + .split(' ') + .map((element) => element[0].toUpperCase() + element.substring(1)) + .join(' '); + return result; + } else { + return text.toUpperCase(); + } + } +} diff --git a/lib/utils/helpers/parser_helper.dart b/lib/utils/helpers/parser_helper.dart new file mode 100644 index 0000000..11d7d3b --- /dev/null +++ b/lib/utils/helpers/parser_helper.dart @@ -0,0 +1,70 @@ +import 'package:intl/intl.dart'; + +class ParserHelper { + //todo : convert to extension + static int parseInt(String value) { + int result; + try { + result = int.parse(value); + } catch (err) { + result = 0; + } + return result; + } + + static double parseDouble(String value) { + double result; + try { + result = double.parse(value); + } catch (err) { + result = 0.0; + } + return result; + } + + static String roundNumber(dynamic value) { + String valueToString = value.toString(); + num number = num.tryParse(valueToString.isNotEmpty + ? valueToString.contains(',') + ? valueToString.replaceAll(',', '') + : valueToString + : '0') ?? + 0; + int roundedNum = number.round(); + + return NumberFormat("#,###,###", 'en_US').format(roundedNum); + } + + static String roundArea(dynamic value) { + if (value.accessTokenIsNotEmpty && value.contains(' ')) { + List split = value!.split(' '); + String formattedArea = ParserHelper.roundNumber(split[0]); + + return '$formattedArea ${split[1]}'; + } else { + String valueToString = value.toString(); + num number = num.tryParse(valueToString.isNotEmpty + ? valueToString.contains(',') + ? valueToString.replaceAll(',', '') + : valueToString + : '0') ?? + 0; + int roundedNum = number.round(); + + return NumberFormat("#,###,###", 'en_US').format(roundedNum); + } + } + + static String oneDecimal(num value) { + String numWithOneDecimal = value.toStringAsFixed(1); + if (numWithOneDecimal.contains('.')) { + List numList = []; + numList = numWithOneDecimal.split('.'); + // Example: if the number is 12.0 change it to 12 + if (numList[1] == '0') { + numWithOneDecimal = numList[0]; + } + } + return numWithOneDecimal; + } +} diff --git a/lib/utils/helpers/phone_number_formatter.dart b/lib/utils/helpers/phone_number_formatter.dart new file mode 100644 index 0000000..ded1c95 --- /dev/null +++ b/lib/utils/helpers/phone_number_formatter.dart @@ -0,0 +1,27 @@ +import 'package:flutter/services.dart'; + +class PhoneNumberTextInputFormatter extends TextInputFormatter { + final String mask; + final String separator; + PhoneNumberTextInputFormatter({ + required this.mask, + required this.separator, + }); + @override + TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { + if (newValue.text.isNotEmpty) { + if (newValue.text.length > oldValue.text.length) { + if (newValue.text.length > mask.length) return oldValue; + if (newValue.text.length < mask.length && mask[newValue.text.length - 1] == separator) { + return TextEditingValue( + text: '${oldValue.text}$separator${newValue.text.substring(newValue.text.length - 1)}', + selection: TextSelection.collapsed( + offset: newValue.selection.end + 1, + ), + ); + } + } + } + return newValue; + } +} diff --git a/lib/utils/helpers/shared_preferences_helper.dart b/lib/utils/helpers/shared_preferences_helper.dart new file mode 100644 index 0000000..b9c7e0f --- /dev/null +++ b/lib/utils/helpers/shared_preferences_helper.dart @@ -0,0 +1,58 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class SharedPreferencesHelper { + static saveStringToSP(String key, String value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(key, value); + } + + static saveBoolToSP(String key, bool value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(key, value); + } + + static saveIntToSP(String key, int value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(key, value); + } + + static saveDoubleToSP(String key, double value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setDouble(key, value); + } + + static saveStringListToSP(String key, List value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList(key, value); + } + + static Future readStringFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + String value = prefs.getString(key) ?? ''; + return value; + } + + static Future readBoolFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + bool? value = prefs.getBool(key); + return value; + } + + static Future readIntFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + int value = prefs.getInt(key) ?? 0; + return value; + } + + static Future> readStringListFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + List? value = prefs.getStringList(key) ?? []; + return value; + } + + static Future removeValueFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(key); + return true; + } +} diff --git a/lib/utils/helpers/snack_bar.dart b/lib/utils/helpers/snack_bar.dart new file mode 100644 index 0000000..991232c --- /dev/null +++ b/lib/utils/helpers/snack_bar.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/navigation/navigation_service.dart'; + +class CustomSnackBar { + static displaySnackBar(String message) { + final key = NavigationService.snackbarKey; + if (key != null) { + final snackBar = SnackBar(content: Text(message)); + key.currentState?.clearSnackBars(); + key.currentState?.showSnackBar(snackBar); + } + } + + static greenSnackBar(String message) { + final key = NavigationService.snackbarKey; + BuildContext? currentContext = key?.currentContext; + if (key != null && currentContext != null) { + final snackBar = SnackBar( + padding: const EdgeInsets.all(16), + backgroundColor: Colors.green, + content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Icon( + Icons.check_circle, + color: Colors.white, + size: 32, + ), + const SizedBox( + width: 8, + ), + Text( + message, + style: Theme.of(currentContext).textTheme.bodySmall!.copyWith( + fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), + ) + ]), + ); + key.currentState?.showSnackBar(snackBar); + } + } +} diff --git a/lib/utils/resource_manager/color_manager.dart b/lib/utils/resource_manager/color_manager.dart new file mode 100644 index 0000000..7cf3131 --- /dev/null +++ b/lib/utils/resource_manager/color_manager.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +abstract class ColorsManager { + static const Color textPrimaryColor = Color(0xFF5D5D5D); + static const Color switchOffColor = Color(0x7F8D99AE); + static const Color primaryColor = Color(0xFF0030CB); + static const Color secondaryTextColor = Color(0xFF848484); + static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static const Color onPrimaryColor = Colors.white; + static const Color secondaryColor = Color(0xFF023DFE); + static const Color onSecondaryColor = Color(0xFF023DFE); + + static const Color primaryTextColor = Colors.black; + + static const Color greyColor = Color(0xFFd5d5d5); + + static const Color backgroundColor = Color(0xFFececec); + static const Color dozeColor = Color(0xFFFEC258); + static const Color relaxColor = Color(0xFFFBD288); + static const Color readingColor = Color(0xFFF7D69C); + static const Color energizingColor = Color(0xFFEDEDED); + static const Color dividerColor = Color(0xFFEBEBEB); + static const Color slidingBlueColor = Color(0x99023DFE); + static const Color blackColor = Color(0xFF000000); + static const Color lightGreen = Color(0xFF00FF0A); + static const Color grayColor = Color(0xFF999999); + static const Color red = Colors.red; + static const Color graysColor = Color(0xffEBEBEB); + static const Color textGray = Color(0xffD5D5D5); +} diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart new file mode 100644 index 0000000..774f726 --- /dev/null +++ b/lib/utils/resource_manager/constants.dart @@ -0,0 +1,481 @@ +//ignore_for_file: constant_identifier_names +import 'dart:ui'; + +import 'package:syncrow_app/features/devices/model/function_model.dart'; +import 'package:syncrow_app/features/menu/view/widgets/create_home/create_home_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/join_home/join_home_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/manage_home/manage_home_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/privacy/privacy_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/securty_view.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +abstract class Constants { + static const String appName = "Syncrow App"; + + static const String languageCode = "en"; + + static const String countryCode = "US"; + + static const double appBarHeightPercentage = 0.1; + static const double bottomNavBarHeightPercentage = 0.1; + static late double appBarHeight; + static late double bottomNavBarHeight; + + static const double defaultPadding = 16; + + static const String token = ''; +} + +enum SpaceType { Unit, Building, Floor, Room, Community } + +Map spaceTypesMap = { + "unit": SpaceType.Unit, + "building": SpaceType.Building, + "floor": SpaceType.Floor, + "room": SpaceType.Room, + "community": SpaceType.Community, +}; + +enum DeviceType { + AC, + LightBulb, + DoorLock, + Curtain, + Blind, + ThreeGang, + Gateway, + CeilingSensor, + WallSensor, + Other, +} + +enum FunctionType { Boolean, Enum, Integer, Raw, String } + +enum ValueACRange { LOW, MIDDLE, HIGH, AUTO } + +Map functionTypesMap = { + "Boolean": FunctionType.Boolean, + "Enum": FunctionType.Enum, + "Integer": FunctionType.Integer, + "Raw": FunctionType.Raw, + "String": FunctionType.String, +}; +Map devicesTypesMap = { + "AC": DeviceType.AC, + "GW": DeviceType.Gateway, + "CPS": DeviceType.CeilingSensor, + "DL": DeviceType.DoorLock, + "WPS": DeviceType.WallSensor, + "3G": DeviceType.ThreeGang, + "CUR": DeviceType.Curtain, +}; +Map> devicesFunctionsMap = { + DeviceType.AC: [ + FunctionModel( + code: 'switch', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), + FunctionModel( + code: 'mode', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + // "range": ["cold", "hot", "wind"] + })), + FunctionModel( + code: 'temp_set', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + { + // "unit": {"min": 200, "max": 300, "scale": 1, "step": 5}, + }, + ), + ), + FunctionModel( + code: 'level', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + // "range": ["low", "middle", "high", "auto"] + })), + FunctionModel( + code: 'child_lock', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), + ], + DeviceType.Gateway: [ + FunctionModel( + code: 'switch_alarm_sound', + type: functionTypesMap['Boolean'], + values: ValueModel.fromJson({})), + FunctionModel( + code: 'master_state', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ["normal", "alarm"] + })), + FunctionModel( + code: 'factory_reset', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), + FunctionModel( + code: 'alarm_active', + type: functionTypesMap['String'], + values: ValueModel.fromJson({"maxlen": 255})), + ], + DeviceType.CeilingSensor: [ + FunctionModel( + code: 'sensitivity', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "", "min": 1, "max": 10, "scale": 0, "step": 1})), + ], + DeviceType.DoorLock: [ + FunctionModel( + code: 'remote_no_pd_setkey', + type: functionTypesMap['Raw'], + values: ValueModel.fromJson({})), + FunctionModel( + code: 'remote_no_dp_key', type: functionTypesMap['Raw'], values: ValueModel.fromJson({})), + FunctionModel( + code: 'normal_open_switch', + type: functionTypesMap['Boolean'], + values: ValueModel.fromJson({})), + ], + DeviceType.WallSensor: [ + FunctionModel( + code: 'far_detection', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "cm", "min": 75, "max": 600, "scale": 0, "step": 75})), + FunctionModel( + code: 'presence_time', + type: functionTypesMap['Integer'], + values: + ValueModel.fromJson({"unit": "Min", "min": 0, "max": 65535, "scale": 0, "step": 1})), + FunctionModel( + code: 'motion_sensitivity_value', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "", "min": 1, "max": 5, "scale": 0, "step": 1})), + FunctionModel( + code: 'motionless_sensitivity', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "", "min": 1, "max": 5, "scale": 0, "step": 1})), + FunctionModel( + code: 'indicator', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), + ], + DeviceType.ThreeGang: [ + FunctionModel( + code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), + FunctionModel( + code: 'switch_2', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), + FunctionModel( + code: 'switch_3', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})), + FunctionModel( + code: 'countdown_1', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})), + FunctionModel( + code: 'countdown_2', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})), + FunctionModel( + code: 'countdown_3', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})), + ], + DeviceType.Curtain: [ + FunctionModel( + code: 'control', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson( + {"range": ["open","stop","close"]} + ) + ), + FunctionModel( + code: 'percent_control', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + {"unit": "%", "min": 0, "max": 100, "scale": 0, "step": 1}) + ), + ], +}; + +enum TempModes { hot, cold, wind } + +enum FanSpeeds { auto, low, middle, high } + +final Map fanSpeedsIconMap = { + FanSpeeds.auto: Assets.assetsIconsFan0, + FanSpeeds.low: Assets.assetsIconsFan1, + FanSpeeds.middle: Assets.assetsIconsFan2, + FanSpeeds.high: Assets.assetsIconsFan3, +}; + +final Map tempModesIconMap = { + TempModes.hot: Assets.assetsIconsSunnyMode, + TempModes.cold: Assets.assetsIconsColdMode, + TempModes.wind: Assets.assetsIconsWindyMode, +}; + +final Map fanSpeedsMap = { + 'auto': FanSpeeds.auto, + 'low': FanSpeeds.low, + 'middle': FanSpeeds.middle, + 'high': FanSpeeds.high, +}; + +final Map tempModesMap = { + 'hot': TempModes.hot, + 'cold': TempModes.cold, + 'wind': TempModes.wind, +}; + +final Map reversedFanSpeedsMap = + fanSpeedsMap.map((key, value) => MapEntry(value, key)); + +final Map reversedTempModesMap = + tempModesMap.map((key, value) => MapEntry(value, key)); + +String getNextFanSpeedKey(FanSpeeds currentFanSpeed) { + const List speeds = FanSpeeds.values; + final int currentIndex = speeds.indexOf(currentFanSpeed); + + // Return null if currentFanSpeed is the last value + if (currentIndex == speeds.length - 1) { + return reversedFanSpeedsMap[FanSpeeds.auto]!; + } + + final FanSpeeds nextSpeed = speeds[currentIndex + 1]; + return reversedFanSpeedsMap[nextSpeed]!; +} + +K? getNextItem(Map map, V value) { + // Iterate over the map entries + for (var entry in map.entries) { + // If the current value matches the provided value + if (entry.value == value) { + // Get the index of the current entry + final index = map.keys.toList().indexOf(entry.key); + // If the index is not the last item, return the next item + if (index < map.length - 1) { + return map.keys.elementAt(index + 1); + } + // If it's the last item, return null + return map.keys.elementAt(0); + } + } + // If the value is not found, return null + return null; +} + +List> menuSections = [ + //Home Management + { + 'title': 'Home Management', + 'color': ColorsManager.primaryColor, + 'buttons': [ + { + 'title': 'Create a Unit', + 'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsCreateHome, + 'page': const CreateUnitView() + }, + { + 'title': 'Join a Unit', + 'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsJoinAHome, + 'page': const JoinHomeView() + }, + { + 'title': 'Manage Your Units', + 'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsManageYourHome, + 'page': const ManageHomeView() + }, + ], + }, + //General Settings + { + 'title': 'General Settings', + 'color': const Color(0xFF023DFE), + 'buttons': [ + { + 'title': 'Voice Assistant', + 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsVoiceAssistant, + 'page': null + }, + { + 'title': 'Temperature unit', + 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsTemperatureUnit, + 'page': null + }, + { + 'title': 'Touch tone on panel', + 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsTouchTone, + 'page': null + }, + { + 'title': 'Language', + 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsLanguage, + 'page': null + }, + { + 'title': 'Network Diagnosis', + 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsNetworkDiagnosis, + 'page': null + }, + { + 'title': 'Clear Cache', + 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsClearCach, + 'page': null + }, + ], + }, + //Messages Center + { + 'title': 'Messages Center', + 'color': const Color(0xFF0088FF), + 'buttons': [ + { + 'title': 'Alerts', + 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsAlerts, + 'page': null + }, + { + 'title': 'Messages', + 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsMessages, + 'page': null + }, + {'title': 'FAQs', 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsFAQs, 'page': null}, + { + 'title': 'Help & Feedback', + 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsHelpAndFeedback, + 'page': null + }, + ], + }, + //Security And Privacy + { + 'title': 'Security And Privacy', + 'color': const Color(0xFF8AB9FF), + 'buttons': [ + { + 'title': 'Security', + 'Icon': Assets.assetsIconsMenuIconsSecurityAndPrivacyIconsSecurty, + 'page': const SecurtyView() + }, + { + 'title': 'Privacy', + 'Icon': Assets.assetsIconsMenuIconsSecurityAndPrivacyIconsPrivacy, + 'page': const PrivacyView() + }, + ], + }, + //Legal Information + { + 'title': 'Legal Information', + 'color': const Color(0xFF001B72), + 'buttons': [ + {'title': 'About', 'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsAbout, 'page': null}, + { + 'title': 'Privacy Policy', + 'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy, + 'page': null + }, + { + 'title': 'User Agreement', + 'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsUserAgreement, + 'page': null + }, + ], + }, +]; + +enum MemberRole { + FamilyMember, + OtherMember, +} + +List> members = [ + { + 'name': 'member 1', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 2', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 3', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 4', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 5', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 6', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 7', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 8', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 9', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 10', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 11', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 12', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 13', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 14', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 15', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 16', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 17', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 18', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 19', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 20', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 21', + 'role': MemberRole.FamilyMember, + }, + { + 'name': 'member 22', + 'role': MemberRole.OtherMember, + }, + { + 'name': 'member 23', + 'role': MemberRole.FamilyMember, + }, +]; diff --git a/lib/utils/resource_manager/font_manager.dart b/lib/utils/resource_manager/font_manager.dart new file mode 100644 index 0000000..9eecf92 --- /dev/null +++ b/lib/utils/resource_manager/font_manager.dart @@ -0,0 +1,45 @@ +import 'dart:ui'; + +/// # To add custom fonts to your application, add a fonts section here, +/// # in this "flutter" section. Each entry in this list should have a +/// # "family" key with the font family name, and a "fonts" key with a +/// # list giving the asset and other descriptors for the font. For +/// # example: +/// +/// fonts: +/// - family:Montserrat +/// fonts: +/// - asset: assets/ fonts/Montserrat-Botd. ttf +/// weight: 700 +/// - asset: assets/ fonts/Montserrat-SemiB01d. ttf +/// weight: 600 + +abstract class FontsManager { + static const FontWeight light = FontWeight.w300; + static const FontWeight regular = FontWeight.w400; + static const FontWeight medium = FontWeight.w500; + static const FontWeight semiBold = FontWeight.w600; + static const FontWeight bold = FontWeight.w700; + static const FontWeight extraBold = FontWeight.w800; + static const FontWeight black = FontWeight.w900; + + static const String fontFamily = 'Aftika'; +} + +class FontSize { + static const double s12 = 12; + static const double s10 = 10; + static const double s14 = 14; + static const double s16 = 16; + static const double s18 = 18; + static const double s20 = 20; + static const double s21 = 21; + static const double s22 = 22; + static const double s24 = 24; + static const double s25 = 25; + static const double s26 = 26; + static const double s28 = 28; + static const double s30 = 30; + static const double s35 = 35; + static const double s48 = 48; +} diff --git a/lib/utils/resource_manager/strings_manager.dart b/lib/utils/resource_manager/strings_manager.dart new file mode 100644 index 0000000..421a0c3 --- /dev/null +++ b/lib/utils/resource_manager/strings_manager.dart @@ -0,0 +1,44 @@ +class StringsManager { + static const noRouteFound = 'No route found'; + static const noInternetConnection = 'No internet connection'; + + static const String dashboard = 'Dashboard'; + static const String devices = 'Devices'; + static const String routine = 'Routines'; + static const String tapToRunRoutine = 'Tap to run routine'; + static const String wizard = 'Wizard'; + static const String active = 'Active'; + static const String current = 'Current'; + static const String frequency = 'Frequency'; + static const String energyUsage = 'Energy Usage'; + static const String totalConsumption = 'Total Consumption'; + static const String ACConsumption = 'AC Consumption'; + static const String units = 'Units'; + static const String emissions = 'Emissions'; + static const String reductions = 'Reductions'; + static const String winter = 'Winter'; + static const String winterMode = 'Winter Mode'; + static const String summer = 'Summer'; + static const String summerMode = 'Summer Mode'; + static const String on = 'ON'; + static const String off = 'OFF'; + static const String timer = 'Timer'; + static const String dimmerAndColor = "Dimmer & color"; + static const String recentlyUsed = "Recently used colors"; + static const String lightingModes = "Lighting modes"; + static const String doze = "Doze"; + static const String relax = "Relax"; + static const String reading = "Reading"; + static const String energizing = "Energizing"; + static const String createScene = 'Create Scene'; + static const String tapToRun = 'Launch: Tap - To - Run'; + static const String turnOffAllLights = + 'Example: turn off all lights in the with one tap.'; + static const String whenDeviceStatusChanges = 'When device status changes'; + static const String whenUnusualActivityIsDetected = + 'Example: when an unusual activity is detected.'; + static const String functions = "Functions"; + static const String firstLaunch = "firstLaunch"; + static const String deleteScene = 'Delete Scene'; + static const String deleteAutomation = 'Delete Automation'; +} diff --git a/lib/utils/resource_manager/styles_manager.dart b/lib/utils/resource_manager/styles_manager.dart new file mode 100644 index 0000000..1db6c8e --- /dev/null +++ b/lib/utils/resource_manager/styles_manager.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +import 'font_manager.dart'; + +TextStyle _getTextStyle({ + required double fontSize, + required Color color, + required FontWeight fontWeight, +}) => + TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: fontSize, + color: color, + fontWeight: fontWeight, + ); + +/// regular style +TextStyle getTextStyleRegular({ + required Color color, + double fontSize = FontSize.s14, + fontWeight = FontsManager.regular, +}) => + _getTextStyle( + fontSize: fontSize, + color: color, + fontWeight: fontWeight, + ); + +/// light style +TextStyle getTextStyleLight({ + required Color color, + double fontSize = FontSize.s12, + fontWeight = FontsManager.light, +}) => + _getTextStyle( + fontSize: fontSize, + color: color, + fontWeight: fontWeight, + ); + +/// medium style +TextStyle getTextStyleMedium({ + required Color color, + double fontSize = FontSize.s16, + fontWeight = FontsManager.medium, +}) => + _getTextStyle( + fontSize: fontSize, + color: color, + fontWeight: fontWeight, + ); + +/// semi bold style +TextStyle getTextStyleSemiBold({ + required Color color, + double fontSize = FontSize.s18, + fontWeight = FontsManager.semiBold, +}) => + _getTextStyle( + fontSize: fontSize, + color: color, + fontWeight: fontWeight, + ); + +/// bold style +TextStyle getTextStyleBold({ + required Color color, + double fontSize = FontSize.s20, + fontWeight = FontsManager.bold, +}) => + _getTextStyle( + fontSize: fontSize, + color: color, + fontWeight: fontWeight, + ); + +/// extra bold style +TextStyle getTextStyleExtraBold({ + required Color color, + double fontSize = FontSize.s22, + fontWeight = FontsManager.extraBold, +}) => + _getTextStyle( + fontSize: fontSize, + color: color, + fontWeight: fontWeight, + ); + +///inputDecoration + +InputDecoration defaultInputDecoration(BuildContext context, {String? hint}) => + InputDecoration( + enabledBorder: context.inputDecoration.enabledBorder, + focusedBorder: context.inputDecoration.enabledBorder, + errorBorder: context.inputDecoration.errorBorder, + focusedErrorBorder: context.inputDecoration.enabledBorder, + hintText: hint, + hintStyle: context.inputDecoration.hintStyle, + filled: context.inputDecoration.filled, + fillColor: context.inputDecoration.fillColor, + contentPadding: context.inputDecoration.contentPadding, + ); diff --git a/lib/utils/resource_manager/theme_manager.dart b/lib/utils/resource_manager/theme_manager.dart new file mode 100644 index 0000000..fdde064 --- /dev/null +++ b/lib/utils/resource_manager/theme_manager.dart @@ -0,0 +1,382 @@ +import 'package:flutter/material.dart'; + +import 'color_manager.dart'; +import 'font_manager.dart'; + +abstract class ThemeManager { + static bool isDarkTheme = false; + + static ThemeData selectTheme() => isDarkTheme ? darkTheme : lightTheme; + static ThemeData lightTheme = ThemeData( + ///main colors + primaryColor: ColorsManager.primaryColor, + colorScheme: const ColorScheme( + background: ColorsManager.backgroundColor, + brightness: Brightness.light, + primary: ColorsManager.primaryColor, + onPrimary: ColorsManager.onPrimaryColor, + secondary: ColorsManager.secondaryColor, + onSecondary: ColorsManager.onSecondaryColor, + error: Colors.red, + onError: Colors.white, + onBackground: ColorsManager.textPrimaryColor, + surface: Colors.white, + onSurface: ColorsManager.textPrimaryColor, + ), + scaffoldBackgroundColor: Colors.white, + + ///text theme + textTheme: const TextTheme( + ///display + displayLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s35, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + displayMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s20, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + displaySmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + + ///title + titleLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s48, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + titleMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s30, + fontWeight: FontsManager.bold, + color: ColorsManager.textPrimaryColor, + ), + titleSmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s25, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + + ///body + bodyLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s18, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + bodyMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s14, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + bodySmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s12, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + + labelLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s18, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + labelMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + labelSmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s14, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + ), + + ///button theme + // buttonTheme: ButtonThemeData( + // buttonColor: ColorsManager.primaryLightColor, + // padding: const EdgeInsets.symmetric(horizontal: AppPadding.p8), + // textTheme: ButtonTextTheme.primary, + // hoverColor: ColorsManager.primaryDarkColor, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(AppRadius.r4), + // ),), + // + // textButtonTheme: TextButtonThemeData( + // style: ButtonStyle( + // backgroundColor: + // MaterialStateProperty.all(ColorsManager.primaryLightColor), + // padding: MaterialStateProperty.all( + // const EdgeInsets.all(AppPadding.p8),), + // textStyle: MaterialStateProperty.all( + // const TextStyle( + // fontFamily: FontsManager.fontFamily, + // fontSize: FontSize.s16, + // fontWeight: FontsManager.regular, + // color: ColorsManager.onPrimaryLightColor, + // ), + // ), + // // shape: MaterialStateProperty.all( + // // RoundedRectangleBorder( + // // borderRadius: BorderRadius.circular(AppRadius.r4), + // // side: const BorderSide(color: Colors.grey), + // // ), + // // ), + // ), + // ), + + dropdownMenuTheme: const DropdownMenuThemeData( + textStyle: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: ColorsManager.primaryColor), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + labelStyle: TextStyle( + fontFamily: FontsManager.fontFamily, + color: Colors.grey, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + ), + ), + menuStyle: MenuStyle( + backgroundColor: MaterialStatePropertyAll(Colors.white), + padding: MaterialStatePropertyAll(EdgeInsets.all(8)), + ), + ), + + ///input decoration theme + inputDecorationTheme: InputDecorationTheme( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(20), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(20), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(20), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: const BorderSide(color: Colors.red)), + hintStyle: const TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s14, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.all(10), + labelStyle: const TextStyle( + fontFamily: FontsManager.fontFamily, + color: Colors.grey, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + ), + ), + + ///card theme + cardTheme: const CardTheme( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + ); + + //TODO implement dark theme + static ThemeData darkTheme = ThemeData( + ///main colors + primaryColor: ColorsManager.primaryColor, + colorScheme: const ColorScheme( + background: ColorsManager.backgroundColor, + brightness: Brightness.light, + primary: ColorsManager.primaryColor, + onPrimary: ColorsManager.onPrimaryColor, + secondary: ColorsManager.secondaryColor, + onSecondary: ColorsManager.onSecondaryColor, + error: Colors.red, + onError: Colors.white, + onBackground: ColorsManager.textPrimaryColor, + surface: Colors.white, + onSurface: ColorsManager.textPrimaryColor, + ), + scaffoldBackgroundColor: Colors.white, + + ///text theme + textTheme: const TextTheme( + ///display + displayLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s35, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + displayMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s20, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + displaySmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + + ///title + titleLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s48, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + titleMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s30, + fontWeight: FontsManager.bold, + color: ColorsManager.textPrimaryColor, + ), + titleSmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s25, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + + ///body + bodyLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s18, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + bodyMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s14, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + bodySmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s12, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + + labelLarge: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s18, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + labelMedium: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + labelSmall: TextStyle( + fontFamily: FontsManager.fontFamily, + fontSize: FontSize.s14, + fontWeight: FontsManager.regular, + color: ColorsManager.textPrimaryColor, + ), + ), + + ///button theme + // buttonTheme: ButtonThemeData( + // buttonColor: ColorsManager.primaryLightColor, + // padding: const EdgeInsets.symmetric(horizontal: AppPadding.p8), + // textTheme: ButtonTextTheme.primary, + // hoverColor: ColorsManager.primaryDarkColor, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(AppRadius.r4), + // ),), + // + // textButtonTheme: TextButtonThemeData( + // style: ButtonStyle( + // backgroundColor: + // MaterialStateProperty.all(ColorsManager.primaryLightColor), + // padding: MaterialStateProperty.all( + // const EdgeInsets.all(AppPadding.p8),), + // textStyle: MaterialStateProperty.all( + // const TextStyle( + // fontFamily: FontsManager.fontFamily, + // fontSize: FontSize.s16, + // fontWeight: FontsManager.regular, + // color: ColorsManager.onPrimaryLightColor, + // ), + // ), + // // shape: MaterialStateProperty.all( + // // RoundedRectangleBorder( + // // borderRadius: BorderRadius.circular(AppRadius.r4), + // // side: const BorderSide(color: Colors.grey), + // // ), + // // ), + // ), + // ), + + ///input decoration theme + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: ColorsManager.primaryColor), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + labelStyle: TextStyle( + fontFamily: FontsManager.fontFamily, + color: Colors.grey, + fontSize: FontSize.s16, + fontWeight: FontsManager.regular, + ), + ), + + ///card theme + //TODO edit card theme + cardTheme: const CardTheme( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + ); +} diff --git a/lib/utils/resource_manager/values_manager.dart b/lib/utils/resource_manager/values_manager.dart new file mode 100644 index 0000000..4898fdb --- /dev/null +++ b/lib/utils/resource_manager/values_manager.dart @@ -0,0 +1,59 @@ +abstract class AppMargin { + static const m8 = 8.0; + static const m12 = 12.0; + static const m14 = 14.0; + static const m16 = 16.0; + static const m18 = 18.0; + static const m20 = 20.0; + static const m24 = 24.0; + static const m32 = 32.0; + static const m40 = 40.0; + static const m65 = 65.0; +} + +abstract class AppPadding { + static const p4 = 4.0; + static const p8 = 8.0; + static const p12 = 12.0; + static const p14 = 14.0; + static const p16 = 16.0; + static const paragraphPadding = 15.0; + static const p18 = 18.0; + static const p20 = 20.0; + static const titleSmallPadding = 20.0; + static const p24 = 24.0; + static const p32 = 32.0; + static const p40 = 40.0; + static const p65 = 65.0; + static const p80 = 80.0; + static const p100 = 100.0; + static const p150 = 150.0; + static const deskTopPadding = 160.0; + static const tabletPadding = 100.0; + static const mobilePadding = 40.0; +} + +abstract class AppSize { + static const s8 = 8.0; + static const s12 = 12.0; + static const s14 = 14.0; + static const s16 = 16.0; + static const s18 = 18.0; + static const s20 = 20.0; + static const s24 = 24.0; + static const s32 = 32.0; + static const s40 = 40.0; + + static const double logoHeight = 80; + static const double appBarHeight = 40; +} + +abstract class AppRadius { + static const r4 = 4.0; + static const r8 = 8.0; + static const r12 = 12.0; + static const r14 = 14.0; + static const r16 = 16.0; + static const r18 = 18.0; + static const r50 = 50.0; +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..453fb7b --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1159 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" + url: "https://pub.dev" + source: hosted + version: "1.3.35" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + url: "https://pub.dev" + source: hosted + version: "8.1.3" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + day_picker: + dependency: "direct main" + description: + name: day_picker + sha256: a0f73716d688b3643769db39e7626cae54026bec24c33a8ebf5a6f8c618a05f2 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91 + url: "https://pub.dev" + source: hosted + version: "10.1.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" + source: hosted + version: "7.0.0" + dio: + dependency: "direct main" + description: + name: dio + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" + url: "https://pub.dev" + source: hosted + version: "5.4.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + sha256: c6220b23397f9302a42617227ee8fb1c5d718097a5351fcce53561d73fc10339 + url: "https://pub.dev" + source: hosted + version: "10.8.7" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: "7f1c02cdd93a5e0a561af2f551465ffb6abdd541dbd0c8a9b8628d9ae0a5d024" + url: "https://pub.dev" + source: hosted + version: "3.9.7" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: ebb857c23f35fed52220b6c3271c12eeb6137de3930845223e3d0590b6fd0649 + url: "https://pub.dev" + source: hosted + version: "0.5.5+19" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" + url: "https://pub.dev" + source: hosted + version: "2.32.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + url: "https://pub.dev" + source: hosted + version: "5.0.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "22fcb352744908224fc7be3caae254836099786acfe5df6e9fe901e9c2575a41" + url: "https://pub.dev" + source: hosted + version: "2.17.1" + firebase_crashlytics: + dependency: "direct main" + description: + name: firebase_crashlytics + sha256: "0126fa101b74fb981796b3e6f47ccf7fc40237ec918327aaec7c0a06fd1bb4c1" + url: "https://pub.dev" + source: hosted + version: "3.4.16" + firebase_crashlytics_platform_interface: + dependency: transitive + description: + name: firebase_crashlytics_platform_interface + sha256: cdfa0a20d66e1b32de542883c0ddf651ee9b66b12cebf73067e4d2cdc0865d17 + url: "https://pub.dev" + source: hosted + version: "3.6.23" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + sha256: "3b9ca306d26ad243ccbc4c717ff6e8563a080ebe11ee77fa7349b419c894b42d" + url: "https://pub.dev" + source: hosted + version: "10.5.7" + firebase_database_platform_interface: + dependency: transitive + description: + name: firebase_database_platform_interface + sha256: "5864cc362275465e9bd682b243f19419c9d78b861c2db820241eea596ae3b320" + url: "https://pub.dev" + source: hosted + version: "0.2.5+35" + firebase_database_web: + dependency: transitive + description: + name: firebase_database_web + sha256: a6008395dd20e8b8dde0691b441c181a1216c3866f89f48dcb6889d34fd35905 + url: "https://pub.dev" + source: hosted + version: "0.2.5+7" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d" + url: "https://pub.dev" + source: hosted + version: "0.66.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_animated_dialog: + dependency: "direct main" + description: + name: flutter_animated_dialog + sha256: "16b88e56ea7014925a1ed4c3c3644f735edfe9194d5cc027a1a10a95d8f55fce" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + url: "https://pub.dev" + source: hosted + version: "3.0.1" + flutter_localization: + dependency: "direct main" + description: + name: flutter_localization + sha256: faaeb1eba307473032e2c2af737f36ced61fc98735608410d0a6d9c231b50912 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e + url: "https://pub.dev" + source: hosted + version: "2.0.20" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + url: "https://pub.dev" + source: hosted + version: "3.0.1" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" + html: + dependency: "direct main" + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "4161e1f843d8480d2e9025ee22411778c3c9eb7e40076dcf2da23d8242b7b51c" + url: "https://pub.dev" + source: hosted + version: "0.8.12+3" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" + url: "https://pub.dev" + source: hosted + version: "3.0.4" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.dev" + source: hosted + version: "0.8.12" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + onesignal_flutter: + dependency: "direct main" + description: + name: onesignal_flutter + sha256: f3940387d6c7033a9c341aa0548f24d98217fce9182f9ad80bf2554b9dd3dc1a + url: "https://pub.dev" + source: hosted + version: "5.2.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + url: "https://pub.dev" + source: hosted + version: "11.3.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "8bb852cd759488893805c3161d0b2b5db55db52f773dbb014420b304055ba2c5" + url: "https://pub.dev" + source: hosted + version: "12.0.6" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662 + url: "https://pub.dev" + source: hosted + version: "9.4.4" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d" + url: "https://pub.dev" + source: hosted + version: "0.1.1" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" + url: "https://pub.dev" + source: hosted + version: "4.2.1" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + pin_code_fields: + dependency: "direct main" + description: + name: pin_code_fields + sha256: "4c0db7fbc889e622e7c71ea54b9ee624bb70c7365b532abea0271b17ea75b729" + url: "https://pub.dev" + source: hosted + version: "8.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: transitive + description: + name: provider + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + url: "https://pub.dev" + source: hosted + version: "6.1.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + sleek_circular_slider: + dependency: "direct main" + description: + name: sleek_circular_slider + sha256: "8844d036269a13e60dda4e16534d30d27cacaff8d2f10d042d9d61d111468b13" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + smooth_page_indicator: + dependency: "direct main" + description: + name: smooth_page_indicator + sha256: "725bc638d5e79df0c84658e1291449996943f93bacbc2cec49963dbbab48d8ae" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + time_picker_spinner: + dependency: "direct main" + description: + name: time_picker_spinner + sha256: "53d824801d108890d22756501e7ade9db48b53dac1ec41580499dd4ebd128e3c" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" + url: "https://pub.dev" + source: hosted + version: "6.2.5" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 + url: "https://pub.dev" + source: hosted + version: "6.3.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + url: "https://pub.dev" + source: hosted + version: "6.2.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + url: "https://pub.dev" + source: hosted + version: "5.2.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" +sdks: + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..be84008 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,93 @@ +name: syncrow_app +description: This is the mobile application project, developed with Flutter for + Syncrow IOT Project. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +version: 1.0.3+21 + +environment: + sdk: ">=3.0.6 <4.0.0" + +dependencies: + cached_network_image: ^3.3.1 + cupertino_icons: ^1.0.6 + dio: ^5.4.1 + equatable: ^2.0.5 + firebase_analytics: ^10.8.7 + firebase_core: ^2.25.5 + firebase_crashlytics: ^3.4.16 + fl_chart: ^0.66.2 + flutter: + sdk: flutter + flutter_animated_dialog: ^2.0.1 + flutter_svg: ^2.0.10+1 + sleek_circular_slider: ^2.0.1 + day_picker: 2.2.0 + # Utility Packages + flutter_secure_storage: ^9.0.0 + flutter_dotenv: ^5.1.0 + # noinspection YAMLSchemaValidation + intl: ^0.19.0 + get_it: ^7.6.7 + url_launcher: ^6.2.5 + flutter_localization: ^0.2.0 + flutter_bloc: ^8.1.4 + html: ^0.15.4 + onesignal_flutter: ^5.2.0 + permission_handler: ^11.3.1 + pin_code_fields: ^8.0.1 + share_plus: ^9.0.0 + shared_preferences: ^2.2.2 + smooth_page_indicator: ^1.1.0 + uuid: ^4.4.0 + time_picker_spinner: ^1.0.0 + image_picker: ^1.1.2 + device_info_plus: ^10.1.0 + firebase_database: ^10.5.7 + +dev_dependencies: + flutter_lints: ^3.0.1 + flutter_test: + sdk: flutter + +flutter_assets: + assets_path: assets/ + output_path: lib/generated2/ + filename: Assets.assetsIconsDart + +flutter: + uses-material-design: true + + assets: + - assets/images/ + - assets/icons/ + - assets/fonts/ + - assets/icons/doorlock-assets/ + - assets/icons/presence-sensor-assets/ + - assets/icons/battery/dmOff/ + - assets/icons/battery/dmOn/ + - assets/icons/unlockingMethodsIcons/ + - assets/icons/linkageIcons/ + - assets/icons/MenuIcons/GeneralSettingsIcons/ + - assets/icons/MenuIcons/HomeManagementIcons/ + - assets/icons/MenuIcons/LeagalInfoIcons/ + - assets/icons/MenuIcons/MessagesCenterIcons/ + - assets/icons/MenuIcons/SecurityAndPrivacyIcons/ + - assets/icons/curtainsIcon/ + - assets/icons/functions_icons/ + - assets/icons/functions_icons/automation_functions/ + - .env.development + - .env.staging + - .env.production + + + fonts: + - family: Aftika + fonts: + - asset: assets/fonts/AftikaRegular.ttf + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..cf0d573 --- /dev/null +++ b/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + syncrow_app + + + + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..0dccf18 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "syncrow_app", + "short_name": "syncrow_app", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "This is the mobile application project, developed with Flutter for Syncrow IOT Project..", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}