diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49e2da1..39ec167 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,19 +13,19 @@ jobs: matrix: target: [Android, Windows, Linux] include: - - os: windows-2019 + - os: windows-latest target: Windows build_target: windows build_path: build\windows\runner\Release asset_extension: .zip asset_content_type: application/zip - - os: ubuntu-20.04 + - os: ubuntu-latest target: Linux build_target: linux build_path: build/linux/x64/release/bundle asset_extension: .tar.gz asset_content_type: application/gzip - - os: ubuntu-20.04 + - os: ubuntu-latest target: Android build_target: apk build_path: build/app/outputs/flutter-apk @@ -45,11 +45,14 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libx11-dev pkg-config cmake ninja-build libblkid-dev + - name: Install Android dependencies if: matrix.target == 'Android' - uses: actions/setup-java@v1 + uses: actions/setup-java@v5 with: - java-version: '12.x' + java-version: "17" + distribution: "microsoft" + - name: Enable desktop support if: matrix.target != 'Android' run: | @@ -60,7 +63,7 @@ jobs: # Checkout smarthome code, recreate missing files, and get packages. - name: Checkout smarthome code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - run: flutter create . --project-name smarthome - run: flutter pub get diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c41a17a..8057ac0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,12 +34,12 @@ jobs: draft-release: name: Draft Github release needs: generate-changelog - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: - name: Download changelog - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v5 with: name: changelog - name: Draft release with changelog @@ -62,19 +62,19 @@ jobs: matrix: target: [Android, Windows, Linux] include: - - os: windows-2019 + - os: windows-latest target: Windows build_target: windows build_path: build\windows\runner\Release asset_extension: .zip asset_content_type: application/zip - - os: ubuntu-20.04 + - os: ubuntu-latest target: Linux build_target: linux build_path: build/linux/x64/release/bundle asset_extension: .tar.gz asset_content_type: application/gzip - - os: ubuntu-20.04 + - os: ubuntu-latest target: Android build_target: apk build_path: build/app/outputs/flutter-apk @@ -95,11 +95,14 @@ jobs: run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libx11-dev pkg-config cmake ninja-build libblkid-dev + - name: Install Android dependencies if: matrix.target == 'Android' - uses: actions/setup-java@v1 + uses: actions/setup-java@v5 with: - java-version: '12.x' + java-version: "17" + distribution: "microsoft" + - name: Enable desktop support if: matrix.target != 'Android' run: | @@ -110,7 +113,7 @@ jobs: # Checkout smarthome code, recreate missing files, and get packages. - name: Checkout smarthome code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - run: flutter create . --project-name smarthome - run: flutter pub get diff --git a/.gitignore b/.gitignore index d43c435..45dc983 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ .pub-cache/ .pub/ build/ +android/app/.cxx/ # Android related **/android/**/gradle-wrapper.jar diff --git a/android/app/build.gradle b/android/app/build.gradle index 8b285c1..c7623c6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,25 +1,32 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') +def localPropertiesFile = rootProject.file("local.properties") if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> + localPropertiesFile.withReader("UTF-8") { reader -> localProperties.load(reader) } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" } -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = '1.1' + flutterVersionCode = "1" } - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.1' +def str = "" +for (element in flutterVersionName.split("\\.")) { + str += element.toString().padLeft(2, "0") } +flutterVersionCode = str + flutterVersionCode.padLeft(4, "0") //Use 4 left pads, to be compatible with the old mobile client versioning stuff def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') @@ -27,29 +34,45 @@ if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +def dartEnvironmentVariables = ["":""] + +if (project.hasProperty('dart-defines')) { + dartEnvironmentVariables = project.property('dart-defines') + .split(',') + .collectEntries { entry -> + def pair = new String(entry.decodeBase64(), 'UTF-8').split('=') + [(pair.first()): pair.last()] + } +} +if (!dartEnvironmentVariables.containsKey('APPLICATION_ID')) { + dartEnvironmentVariables['APPLICATION_ID'] = 'de.airsphere.paxcontrol.client.mobile' +} android { - compileSdkVersion 33 + namespace = "de.susch19.smarthome" + compileSdk = flutter.compileSdkVersion - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } + compileOptions { + coreLibraryDesugaringEnabled true - lintOptions { - disable 'InvalidPackage' + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "de.susch19.smarthome" + applicationId dartEnvironmentVariables.APPLICATION_ID resValue "string", "app_name", "Smarthome" - minSdkVersion flutter.minSdkVersion - targetSdkVersion 31 + minSdk flutter.minSdkVersion + targetSdk flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true + } signingConfigs { @@ -61,28 +84,28 @@ android { } } - buildTypes { + buildTypes { release { signingConfig signingConfigs.release } - profile { - applicationIdSuffix ".profile" - versionNameSuffix "-profile" - resValue "string", "app_name", "Smarthome Profile" + // profile { + // applicationIdSuffix ".profile" + // versionNameSuffix "-profile" + // resValue "string", "app_name", "Smarthome Profile" - } - debug { - applicationIdSuffix ".debug" - versionNameSuffix "-debug" - resValue "string", "app_name", "Smarthome Debug" - } + // } + // debug { + // applicationIdSuffix ".debug" + // versionNameSuffix "-debug" + // resValue "string", "app_name", "Smarthome Debug" + // } } } flutter { - source '../..' + source = "../.." } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' } diff --git a/android/build.gradle b/android/build.gradle index 3d4b77c..d2ffbff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,29 +1,16 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() - jcenter() + mavenCentral() } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } tasks.register("clean", Delete) { diff --git a/android/gradle.properties b/android/gradle.properties index a673820..1a3e971 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 6b66533..9162f10 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bc..b507b94 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +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 + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.9.1" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false +} + +include ":app" diff --git a/android/settings_aar.gradle b/android/settings_aar.gradle deleted file mode 100644 index d3db109..0000000 --- a/android/settings_aar.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/assets/certs/Smarthome.pem b/assets/certs/Smarthome.pem new file mode 100644 index 0000000..c65cfc8 --- /dev/null +++ b/assets/certs/Smarthome.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFVTCCAz2gAwIBAgIUXW1ShXyRiKCSivhA/2xKZAARk04wDQYJKoZIhvcNAQEL +BQAwOTEaMBgGA1UEAwwRU21hcnRob21lIFJvb3QgQ0ExCzAJBgNVBAYTAkRFMQ4w +DAYDVQQKDAVzdXNjaDAgFw0yNTAxMjYxNTUxMjBaGA8yMTI1MDEwMjE1NTEyMFow +OTEaMBgGA1UEAwwRU21hcnRob21lIFJvb3QgQ0ExCzAJBgNVBAYTAkRFMQ4wDAYD +VQQKDAVzdXNjaDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOSaqVaf +Itwv+hhfGzoTJ9zuqEt+Dcfy/cf49/D8ZPdh5IjFOAV4QuKFmZDT3vhSXePnlZEc +tNQip34DGyhFcE4RaIFL2g4ud3xjVg8H3G8A1Onm0huT42rssQysWfEXM71lV6fe +i4g3BoIH7GVJ89gfoo8CBU9euK+6TIoZa75AJaagaUnl/HIZ/0//VJkoAHA3EANG +M3yvUQkjUwBs+WAFMLXwJpWOXPZw0poLHuCixyghu8gNOj6nm7oSiB0/MiTgVHGe +kH3HgfjJzP5P53YxsMRi59gogwNTzBHic2/fFzBBiWHNyQPfG5Kh6SlEqtTJB7au +x5q53JpTEHbU3hpGXQj5c/myVhP3oVl6skftG5BK1g2q3y2hc38J3QMviRorLGjk +VEV09/zF7t+P0db8aDf9/jfItIBb0Ihfd0Zw6O69v9+GbiAgI/oMoMhpt8Ie6QQ3 +t3Wk7wMjdMdfij7LEGsD817VflxDL2Qf2NLjDj1W+IUzcQGtbiGBbd3uiwm+edUU +fq5uITmEcKr/W2clmwwc5Bs5Bl/BXCWDtj7m5rtsWnrBOzg5+wfOlmfnhv5JADat +cczCNvYJWds6aMghOxZ4VtrN/jcSpLN4ZuHfZaKbBVaOq11dJZVn7O3oP72KXwFq +g/P2gta7uZ1sWgSmzxeCE4xyURmZTioE2BoLAgMBAAGjUzBRMB0GA1UdDgQWBBQd +X8C9H848iuddGOEsl02uj94XhTAfBgNVHSMEGDAWgBQdX8C9H848iuddGOEsl02u +j94XhTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAtksIZ8SRe +NvDJGXRukxSzVgOR+5WPRoLBHQNCq368KW0B34jn/yrrA1K2nb1Gr4g4eoQe7CRf +0H4pG4QPpJIK+tKNSAnv1cDeQZeExyqsETqMalzWVabYIDfqNfTps7CZycdYQiVP +3g6c5W7VyY1dDgcUkSB6NFIjhU6PyL7acu7LsxNh+PQy0qifGAN/gK8D15GTIqEk +M3ytQTzzDA0fNpSaqzC5ErEx9ZSVOeHJLbf9k05aQXfL3UbkpXTtk26KrPnAwJs4 +CBBUK+knCkiUxv1sHjpsmshyiGOhMCLXFrl/r89/mA1UNWkDB/AnTfTUGWz0xnWq +/2fwJ3gW9fSQWj6J2++g1m2UE/kGcDSH3K1L53jvRDlQrAlQFoPZwe+GN0cahEKu +a9xXnWGlUPZZZ7n2dEtSpgrnRwkblUzAiTXQ+booZOFXZxgQ+ARwYoc3v3TlePO8 +zf596Wvf4LepoAK5GOoCEDI5wC5NwQBT6cizHNvVYDK1Lp4XjqEVN4IaVuKzhbig +VCzRdbNDAjM58Pciw/g+wvNPzJC1pXc8DYSzGtZNg27QVpH4IiXfC0uxPZjprFDL +sVQcryAtu+8J4qRBEe46udReoIt5yABNFhZ16swrbK+DC9d7lx4t5Md8FL6Mxuj2 +mh0Uf1t9eY7ECK2aqFuYBZK9E3IybGZGEA== +-----END CERTIFICATE----- diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..353a18a --- /dev/null +++ b/build.yaml @@ -0,0 +1,18 @@ +targets: + $default: + builders: + swagger_dart_code_generator: + options: + input_folder: 'lib/swagger_jsons/' + output_folder: 'lib/restapi/' + overriden_models: + - file_name: "swagger" + import_url: "../devices/device_exporter.dart" + overriden_models: + - "Device" + - "DetailPropertyInfo" + - "DashboardPropertyInfo" + - "LayoutBasePropertyInfo" + - "SvgIcon" + - "AppNotification" + - "Command" diff --git a/ios/Flutter/ephemeral/flutter_lldb_helper.py b/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/ios/Flutter/ephemeral/flutter_lldbinit b/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh index be18f5f..d17dbbb 100644 --- a/ios/Flutter/flutter_export_environment.sh +++ b/ios/Flutter/flutter_export_environment.sh @@ -5,7 +5,7 @@ export "FLUTTER_APPLICATION_PATH=C:\Users\susch\source\flutter\smarthome" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_TARGET=lib\main.dart" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=1.2.0" +export "FLUTTER_BUILD_NAME=1.3.0" export "FLUTTER_BUILD_NUMBER=0" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" diff --git a/lib/controls/blurry_card.dart b/lib/controls/blurry_card.dart index 0fc67b5..65284c4 100644 --- a/lib/controls/blurry_card.dart +++ b/lib/controls/blurry_card.dart @@ -19,15 +19,14 @@ class BlurryCard extends StatelessWidget { final Color darkShadowColor; final EdgeInsets margin; const BlurryCard( - {final Key? key, + {super.key, this.child, this.borderRadius = defaultBorderRadius, this.lightColor = defaultLightColor, this.darkColor = defaultBlackColor, this.lightShadowColor = defaultLightShadowColor, this.darkShadowColor = defaultDarkShadowColor, - this.margin = defaultMargin}) - : super(key: key); + this.margin = defaultMargin}); @override Widget build(final BuildContext context) { diff --git a/lib/controls/blurry_container.dart b/lib/controls/blurry_container.dart index ac68e9c..998ff4d 100644 --- a/lib/controls/blurry_container.dart +++ b/lib/controls/blurry_container.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:smarthome/helper/preference_manager.dart'; +import 'package:flutter_riverpod/legacy.dart'; const EdgeInsetsGeometry kDefaultPadding = EdgeInsets.all(0); const EdgeInsetsGeometry kDefaultMargin = EdgeInsets.all(0); @@ -29,8 +30,7 @@ class BlurryContainer extends ConsumerWidget { this.borderRadius = kBorderRadius, this.margin = kDefaultMargin, this.blendMode = kblendMode, - final Key? key}) - : super(key: key); + super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { diff --git a/lib/controls/checkbox_dialog_option.dart b/lib/controls/checkbox_dialog_option.dart index 07e1b73..a77e631 100644 --- a/lib/controls/checkbox_dialog_option.dart +++ b/lib/controls/checkbox_dialog_option.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; class CheckboxDialogOption extends SimpleDialogOption { - - const CheckboxDialogOption({ - final Key? key, + const CheckboxDialogOption({ + super.key, final onPressed, final padding, final child, - }) : super(key: key, onPressed: onPressed, padding: padding, child:child); -} \ No newline at end of file + }) : super(onPressed: onPressed, padding: padding, child: child); +} diff --git a/lib/controls/circle_blur_painter.dart b/lib/controls/circle_blur_painter.dart index ce1fba8..696e679 100644 --- a/lib/controls/circle_blur_painter.dart +++ b/lib/controls/circle_blur_painter.dart @@ -1,15 +1,14 @@ import 'package:flutter/material.dart'; class CircleBlurPainter extends CustomPainter { - - CircleBlurPainter(this.color, this.offset, {required this.circleWidth, required this.blurSigma}); + CircleBlurPainter(this.color, this.offset, + {required this.circleWidth, required this.blurSigma}); double circleWidth; double blurSigma; Color color; Offset offset; - @override void paint(final Canvas canvas, final Size size) { final Paint line = Paint() @@ -23,4 +22,4 @@ class CircleBlurPainter extends CustomPainter { bool shouldRepaint(final CustomPainter oldDelegate) { return true; } -} \ No newline at end of file +} diff --git a/lib/controls/controls_exporter.dart b/lib/controls/controls_exporter.dart index 5d310d3..4aeca73 100644 --- a/lib/controls/controls_exporter.dart +++ b/lib/controls/controls_exporter.dart @@ -4,4 +4,4 @@ export 'dashboard_card.dart'; export 'double_wheel.dart'; export 'gradient_rounded_rect_slider_track_shape.dart'; export 'circle_blur_painter.dart'; -export 'blurry_card.dart'; \ No newline at end of file +export 'blurry_card.dart'; diff --git a/lib/controls/dashboard_card.dart b/lib/controls/dashboard_card.dart index 4b960de..d07468e 100644 --- a/lib/controls/dashboard_card.dart +++ b/lib/controls/dashboard_card.dart @@ -6,39 +6,37 @@ import 'package:smarthome/devices/device_exporter.dart'; import 'package:smarthome/controls/controls_exporter.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:smarthome/devices/generic/stores/store_service.dart'; -import 'package:tuple/tuple.dart'; -class StatelessDashboardCard extends StatelessWidget { +class DashboardCard extends ConsumerWidget { final Device device; final VoidCallback onLongPress; - final Object tag; - const StatelessDashboardCard({ - final Key? key, + const DashboardCard({ + super.key, required this.device, required this.onLongPress, - required this.tag, - }) : super(key: key); + }); @override - Widget build(final BuildContext context) { - // final friendlyName = ref.watch(BaseModel.friendlyNameProvider(device.id)); - // final typeNames = ref.watch(BaseModel.typeNamesProvider(device.id)); - // final isConnected = ref.watch(BaseModel.isConnectedProvider(device.id)); - - // final deviceIcon = ref.watch(iconWidgetProvider(Tuple3(typeNames ?? [], device, AdaptiveTheme.of(context)))); - // print("Redraw $friendlyName"); + Widget build(final BuildContext context, final WidgetRef ref) { + final typeNames = ref.watch(BaseModel.typeNamesProvider(device.id)); + final deviceIcon = ref.watch(iconWidgetProvider( + typeNames ?? [], + device, + AdaptiveTheme.of(context), + false, + )); return ClipRRect( borderRadius: BorderRadius.circular(24), child: Card( color: Colors.transparent, child: BlurryContainer( - color: AdaptiveTheme.of(context).brightness == Brightness.light ? Colors.white54 : Colors.black38, + color: AdaptiveTheme.of(context).brightness == Brightness.light + ? Colors.white54 + : Colors.black38, child: MaterialButton( splashColor: Colors.transparent, disabledColor: Colors.transparent, - - // ), onPressed: () => device.navigateToDevice(context), onLongPress: onLongPress, child: Column( @@ -47,16 +45,18 @@ class StatelessDashboardCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.only(left: 4, top: 8.0, bottom: 8.0), - child: Consumer( - builder: (final _, final ref, final __) { - final typeNames = ref.watch(BaseModel.typeNamesProvider(device.id)); - final deviceIcon = ref.watch( - iconWidgetProvider(Tuple4(typeNames ?? [], device, AdaptiveTheme.of(context), false))); - return SizedBox( - width: 32, height: 40, child: deviceIcon, //icon, - ); - }, + margin: + const EdgeInsets.only(left: 4, top: 8.0, bottom: 8.0), + child: SizedBox( + width: 32, + height: 40, + child: IconButton( + padding: EdgeInsets.all(2), + onPressed: () { + device.iconPressed(context, ref); + }, + icon: deviceIcon, + ), //icon, ), ), Expanded( @@ -66,9 +66,11 @@ class StatelessDashboardCard extends StatelessWidget { ), ), Container( - // width: 16, - // height: 16, - margin: const EdgeInsets.only(right: 8.0, top: 8.0, bottom: 8.0), + margin: const EdgeInsets.only( + right: 8.0, + top: 8.0, + bottom: 8.0, + ), child: device.getRightWidgets(), ), ], @@ -86,13 +88,16 @@ class StatelessDashboardCard extends StatelessWidget { final bool? isConnected; if (device is GenericDevice) { isConnected = ref - .watch(valueStoreChangedProvider(Tuple2("isConnected", device.id))) + .watch(valueStoreChangedProvider( + "isConnected", device.id)) ?.currentValue; } else { - isConnected = ref.watch(ConnectionBaseModel.isConnectedProvider(device.id)); + isConnected = ref.watch( + ConnectionBaseModel.isConnectedProvider( + device.id)); } - if (isConnected == null) return Container(); + if (isConnected == null) return const SizedBox(); return CustomPaint( painter: CircleBlurPainter( isConnected ? Colors.greenAccent : Colors.red, @@ -108,7 +113,8 @@ class StatelessDashboardCard extends StatelessWidget { margin: const EdgeInsets.only(top: 8.0), child: Consumer( builder: (final _, final ref, final __) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(device.id)); + final friendlyName = ref.watch( + BaseModel.friendlyNameProvider(device.id)); return Text( friendlyName, style: const TextStyle(), @@ -118,10 +124,6 @@ class StatelessDashboardCard extends StatelessWidget { }, )), ), - SizedBox( - height: 16, - width: 12, - ), ], ), ), diff --git a/lib/controls/double_wheel.dart b/lib/controls/double_wheel.dart index fc32077..d83da4d 100644 --- a/lib/controls/double_wheel.dart +++ b/lib/controls/double_wheel.dart @@ -1,4 +1,4 @@ -library wheel_chooser; +library; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart'; @@ -34,10 +34,9 @@ class WheelChooser extends StatefulWidget { this.listHeight, this.horizontal = false, this.diameter = 10000, - final Key? key}) + super.key}) : assert(perspective <= 0.01), - children = null, - super(key: key); + children = null; WheelChooser.custom( {required this.onValueChanged, @@ -52,12 +51,11 @@ class WheelChooser extends StatefulWidget { this.listWidth, this.listHeight, this.horizontal = false, - final Key? key}) + super.key}) : assert(perspective <= 0.01), assert(datas == null || datas.length == children!.length), selectTextStyle = null, - unSelectTextStyle = null, - super(key: key); + unSelectTextStyle = null; WheelChooser.integer( {required this.onValueChanged, @@ -76,7 +74,7 @@ class WheelChooser extends StatefulWidget { this.listHeight, this.horizontal = false, final bool reverse = false, - final Key? key}) + super.key}) : assert(perspective <= 0.01), assert(minValue < maxValue), assert(initValue == null || initValue >= minValue), @@ -88,8 +86,7 @@ class WheelChooser extends StatefulWidget { ? 0 : reverse ? (maxValue - initValue) ~/ step - : (initValue - minValue) ~/ step, - super(key: key); + : (initValue - minValue) ~/ step; WheelChooser.double( {required this.onValueChanged, @@ -108,7 +105,7 @@ class WheelChooser extends StatefulWidget { this.listHeight, this.horizontal = false, final bool reverse = false, - final Key? key}) + super.key}) : assert(perspective <= 0.01), assert(minValue < maxValue), assert(initValue == null || initValue >= minValue), @@ -120,11 +117,14 @@ class WheelChooser extends StatefulWidget { ? 0 : reverse ? (maxValue - initValue) ~/ step - : (initValue - minValue) ~/ step, - super(key: key); + : (initValue - minValue) ~/ step; static List _createDoubleList( - final double minValue, final double maxValue, final double step, final bool reverse) { + final double minValue, + final double maxValue, + final double step, + final bool reverse, + ) { final List result = []; if (reverse) { for (double i = maxValue; i >= minValue; i -= step) { @@ -138,7 +138,12 @@ class WheelChooser extends StatefulWidget { return result; } - static List _createIntegerList(final int minValue, final int maxValue, final int step, final bool reverse) { + static List _createIntegerList( + final int minValue, + final int maxValue, + final int step, + final bool reverse, + ) { final List result = []; if (reverse) { for (int i = maxValue; i >= minValue; i -= step) { @@ -165,7 +170,8 @@ class WheelChooserState extends State { void initState() { super.initState(); currentPosition = widget.startPosition; - fixedExtentScrollController = FixedExtentScrollController(initialItem: currentPosition!); + fixedExtentScrollController = + FixedExtentScrollController(initialItem: currentPosition!); } void _listener(final int position) { @@ -207,10 +213,14 @@ class WheelChooserState extends State { RotatedBox( quarterTurns: widget.horizontal ? 1 : 0, child: Text( - widget.datas![i] is double ? widget.datas![i].toStringAsFixed(1) : widget.datas![i].toString(), + widget.datas![i] is double + ? widget.datas![i].toStringAsFixed(1) + : widget.datas![i].toString(), textAlign: TextAlign.center, textScaler: const TextScaler.linear(1.5), - style: i == currentPosition ? widget.selectTextStyle : widget.unSelectTextStyle, + style: i == currentPosition + ? widget.selectTextStyle + : widget.unSelectTextStyle, ), ), ); @@ -245,14 +255,14 @@ class ListWheelScrollViewX extends StatelessWidget { final double diameterRatio; final void Function(int)? onSelectedItemChanged; const ListWheelScrollViewX({ - final Key? key, + super.key, required this.builder, required this.itemExtent, this.controller, this.onSelectedItemChanged, this.scrollDirection = Axis.vertical, this.diameterRatio = 100000, - }) : super(key: key); + }); @override Widget build(final BuildContext context) { diff --git a/lib/controls/expandable_fab.dart b/lib/controls/expandable_fab.dart index 40d32ab..54913a0 100644 --- a/lib/controls/expandable_fab.dart +++ b/lib/controls/expandable_fab.dart @@ -4,11 +4,11 @@ import 'dart:math' as math; @immutable class ExpandableFab extends StatefulWidget { const ExpandableFab({ - final Key? key, + super.key, this.initialOpen, required this.distance, required this.children, - }) : super(key: key); + }); final bool? initialOpen; final double distance; @@ -18,7 +18,8 @@ class ExpandableFab extends StatefulWidget { ExpandableFabState createState() => ExpandableFabState(); } -class ExpandableFabState extends State with SingleTickerProviderStateMixin { +class ExpandableFabState extends State + with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _expandAnimation; bool _open = false; @@ -98,7 +99,9 @@ class ExpandableFabState extends State with SingleTickerProviderS final children = []; final count = widget.children.length; const step = 56.0; //90.0 / (count - 1); - for (var i = 0, angleInDegrees = step + 8.0; i < count; i++, angleInDegrees += step) { + for (var i = 0, angleInDegrees = step + 8.0; + i < count; + i++, angleInDegrees += step) { children.add( _ExpandingActionButton( positionOffsetInPx: angleInDegrees, @@ -143,12 +146,11 @@ class ExpandableFabState extends State with SingleTickerProviderS @immutable class _ExpandingActionButton extends StatelessWidget { const _ExpandingActionButton({ - final Key? key, required this.positionOffsetInPx, required this.maxDistance, required this.progress, required this.child, - }) : super(key: key); + }); final double positionOffsetInPx; final double maxDistance; @@ -185,10 +187,10 @@ class _ExpandingActionButton extends StatelessWidget { @immutable class ActionButton extends StatelessWidget { const ActionButton({ - final Key? key, + super.key, this.onPressed, required this.icon, - }) : super(key: key); + }); final VoidCallback? onPressed; final Widget icon; diff --git a/lib/controls/gradient_rounded_rect_slider_track_shape.dart b/lib/controls/gradient_rounded_rect_slider_track_shape.dart index a0f930d..e3ca989 100644 --- a/lib/controls/gradient_rounded_rect_slider_track_shape.dart +++ b/lib/controls/gradient_rounded_rect_slider_track_shape.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -class GradientRoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackShape { +class GradientRoundedRectSliderTrackShape extends SliderTrackShape + with BaseSliderTrackShape { final LinearGradient gradient; /// Create a slider track that draws two rectangles with rounded outer edges. @@ -42,10 +43,12 @@ class GradientRoundedRectSliderTrackShape extends SliderTrackShape with BaseSlid // Assign the track segment paints, which are leading: active and // trailing: inactive. - final ColorTween activeTrackColorTween = - ColorTween(begin: sliderTheme.disabledActiveTrackColor, end: sliderTheme.activeTrackColor); - final ColorTween inactiveTrackColorTween = - ColorTween(begin: sliderTheme.disabledInactiveTrackColor, end: sliderTheme.inactiveTrackColor); + final ColorTween activeTrackColorTween = ColorTween( + begin: sliderTheme.disabledActiveTrackColor, + end: sliderTheme.activeTrackColor); + final ColorTween inactiveTrackColorTween = ColorTween( + begin: sliderTheme.disabledInactiveTrackColor, + end: sliderTheme.inactiveTrackColor); final Paint activePaint = Paint() ..shader = gradient.createShader(trackRect) ..color = activeTrackColorTween.evaluate(enableAnimation)!; @@ -74,8 +77,12 @@ class GradientRoundedRectSliderTrackShape extends SliderTrackShape with BaseSlid trackRect.top, thumbCenter.dx, trackRect.bottom, - topLeft: (textDirection == TextDirection.ltr) ? activeTrackRadius : trackRadius, - bottomLeft: (textDirection == TextDirection.ltr) ? activeTrackRadius : trackRadius, + topLeft: (textDirection == TextDirection.ltr) + ? activeTrackRadius + : trackRadius, + bottomLeft: (textDirection == TextDirection.ltr) + ? activeTrackRadius + : trackRadius, ), leftTrackPaint, ); @@ -85,8 +92,12 @@ class GradientRoundedRectSliderTrackShape extends SliderTrackShape with BaseSlid trackRect.top, trackRect.right, trackRect.bottom, - topRight: (textDirection == TextDirection.rtl) ? activeTrackRadius : trackRadius, - bottomRight: (textDirection == TextDirection.rtl) ? activeTrackRadius : trackRadius, + topRight: (textDirection == TextDirection.rtl) + ? activeTrackRadius + : trackRadius, + bottomRight: (textDirection == TextDirection.rtl) + ? activeTrackRadius + : trackRadius, ), rightTrackPaint, ); diff --git a/lib/dashboard/group_devices.dart b/lib/dashboard/group_devices.dart index 22cb4fc..5ed39e7 100644 --- a/lib/dashboard/group_devices.dart +++ b/lib/dashboard/group_devices.dart @@ -3,12 +3,14 @@ import 'package:smarthome/devices/device_exporter.dart'; import 'package:smarthome/devices/device_manager.dart'; import 'package:smarthome/helper/theme_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; @immutable class GroupDevices extends ConsumerWidget { - GroupDevices(this.groupName, this.isNewGroup, {final Key? key}) : super(key: key); + GroupDevices(this.groupName, this.isNewGroup, {super.key}); - final tempGroupProvider = StateProvider.family, int>((final ref, final id) { + final tempGroupProvider = + StateProvider.family, int>((final ref, final id) { return ref.read(Device.groupsByIdProvider(id)); }); @@ -25,7 +27,7 @@ class GroupDevices extends ConsumerWidget { body: Container( decoration: ThemeManager.getBackgroundDecoration(context), child: Form( - onWillPop: () => _onWillPop(context), + onPopInvoked: (final didPop) => _onWillPop(context, didPop), autovalidateMode: AutovalidateMode.onUserInteraction, child: Consumer( builder: (final context, final ref, final child) { @@ -39,9 +41,10 @@ class GroupDevices extends ConsumerWidget { child: const Icon(Icons.save), onPressed: () { for (final device in modifiedDevices) { - ref.read(Device.groupsByIdProvider(device.id).notifier).state = ref.read(tempGroupProvider(device.id)); + ref.read(Device.groupsByIdProvider(device.id).notifier).state = + ref.read(tempGroupProvider(device.id)); } - ref.read(deviceProvider.notifier).saveDeviceGroups(); + ref.read(deviceManagerProvider.notifier).saveDeviceGroups(); Navigator.of(context).pop(true); }, ), @@ -50,16 +53,20 @@ class GroupDevices extends ConsumerWidget { List mapDevices(final WidgetRef ref) { final widgets = []; - final devices = ref.watch(deviceProvider); + final devicesFuture = ref.watch(deviceManagerProvider); + if (!devicesFuture.hasValue) return []; + final devices = devicesFuture.requireValue; for (final device in devices) { final groups = ref.watch(tempGroupProvider(device.id)); final contains = groups.contains(groupName); widgets.add(ListTile( - leading: Checkbox(value: contains, onChanged: (final v) => changed(v, device, ref)), + leading: Checkbox( + value: contains, onChanged: (final v) => changed(v, device, ref)), title: Consumer( builder: (final context, final ref, final child) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(device.id)); + final friendlyName = + ref.watch(BaseModel.friendlyNameProvider(device.id)); final typeName = ref.watch(BaseModel.typeNameProvider(device.id)); return Wrap( children: [ @@ -76,7 +83,8 @@ class GroupDevices extends ConsumerWidget { return widgets; } - void changed(final bool? value, final Device device, final WidgetRef ref) { + void changed( + final bool? value, final Device device, final WidgetRef ref) { if (value == null) return; if (modifiedDevices.contains(device)) { @@ -94,17 +102,19 @@ class GroupDevices extends ConsumerWidget { groupsState.state = groups; } - Future _onWillPop(final BuildContext context) async { - if (!isNewGroup && modifiedDevices.isEmpty) return true; + Future _onWillPop(final BuildContext context, final bool didPop) async { + if (!didPop || (!isNewGroup && modifiedDevices.isEmpty)) return true; final ThemeData theme = Theme.of(context); - final TextStyle dialogTextStyle = theme.textTheme.titleMedium!.copyWith(color: theme.textTheme.bodySmall!.color); + final TextStyle dialogTextStyle = theme.textTheme.titleMedium! + .copyWith(color: theme.textTheme.bodySmall!.color); return await (showDialog( context: context, builder: (final BuildContext context) => AlertDialog( content: isNewGroup - ? Text("Es wurden der neuen Gruppe keine Geräte zugeordnet.\r\nSoll die neue Gruppe verworfen werden?", + ? Text( + "Es wurden der neuen Gruppe keine Geräte zugeordnet.\r\nSoll die neue Gruppe verworfen werden?", style: dialogTextStyle) : Text( "Es wurden Änderungen an den Temperatur-Einstellungen vorgenommen.\r\nSollen diese verworfen werden?", diff --git a/lib/devices/base_model.dart b/lib/devices/base_model.dart index 3dfb19b..97fff8c 100644 --- a/lib/devices/base_model.dart +++ b/lib/devices/base_model.dart @@ -1,18 +1,40 @@ import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; part 'base_model.g.dart'; -final baseModelProvider = StateProvider>((final ref) => []); +@Riverpod(keepAlive: true) +class BaseModels extends _$BaseModels { + @override + List build() { + return []; + } + + void storeModels(final List baseModels) { + final oldState = state.toList(); + for (final element in baseModels) { + if (oldState.indexWhere((final x) => x.id == element.id) + case final int index when index > -1) { + oldState[index] = element; + } else { + oldState.add(element); + } + } + state = oldState; + } +} -final baseModelASMapProvider = Provider>( - (final ref) => ref.watch(baseModelProvider).toMap((final bm) => bm.id, (final bm) => bm)); +final baseModelASMapProvider = Provider>((final ref) => + ref.watch(baseModelsProvider).toMap((final bm) => bm.id, (final bm) => bm)); -final baseModelFriendlyNamesMapProvider = Provider>((final ref) { - return ref.watch(baseModelProvider).toMap((final bm) => bm.id, (final bm) => bm.friendlyName); +final baseModelFriendlyNamesMapProvider = + Provider>((final ref) { + return ref + .watch(baseModelsProvider) + .toMap((final bm) => bm.id, (final bm) => bm.friendlyName); }); const defaultList = []; @@ -20,22 +42,26 @@ const defaultList = []; @JsonSerializable() @immutable class BaseModel { - static final byIdProvider = Provider.family((final ref, final id) { - final baseModels = ref.watch(baseModelProvider); - return baseModels.firstOrNull((final bm) => bm.id == id); + static final byIdProvider = + Provider.family((final ref, final id) { + final baseModels = ref.watch(baseModelsProvider); + return baseModels.firstOrDefault((final bm) => bm.id == id); }); - static final friendlyNameProvider = Provider.family((final ref, final id) { + static final friendlyNameProvider = + Provider.family((final ref, final id) { final baseModel = ref.watch(BaseModel.byIdProvider(id)); return baseModel?.friendlyName ?? ""; }); - static final typeNamesProvider = Provider.family?, int>((final ref, final id) { + static final typeNamesProvider = + Provider.family?, int>((final ref, final id) { final baseModel = ref.watch(BaseModel.byIdProvider(id)); return baseModel?.typeNames; }); - static final typeNameProvider = Provider.family((final ref, final id) { + static final typeNameProvider = + Provider.family((final ref, final id) { final baseModel = ref.watch(BaseModel.typeNamesProvider(id)); if (baseModel?.isEmpty ?? true) { return ""; @@ -49,15 +75,14 @@ class BaseModel { // @JsonKey(includeFromJson: false, includeToJson: false) final List typeNames; + final Map? dynamicStateData; @protected BaseModel getModelFromJson(final Map json) { return BaseModel.fromJson(json, defaultList); } - BaseModel updateFromJson(final Map json) { - final updatedModel = getModelFromJson(json); - + BaseModel mergeWith(final BaseModel updatedModel) { bool updated = false; if (updatedModel != this) { updated = true; @@ -66,16 +91,14 @@ class BaseModel { return updated ? updatedModel : this; } - @protected - bool updateModelFromJson(final Map json) { - return false; - } - - const BaseModel(this.id, this.friendlyName, this.typeName, [this.typeNames = defaultList]); + const BaseModel(this.id, this.friendlyName, this.typeName, + {this.typeNames = defaultList, this.dynamicStateData = const {}}); - factory BaseModel.fromJson(final Map json, final List typeNames) { - final bm = BaseModel(json['id'] as int, json['friendlyName'] as String, json['typeName'], typeNames); - StoreService.updateAndGetStores(bm.id, json); + factory BaseModel.fromJson( + final Map json, final List typeNames) { + final bm = BaseModel(json['id'] as int, + json['friendlyName'] as String? ?? "", json['typeName'], + typeNames: typeNames, dynamicStateData: json["dynamicStateData"]); return bm; } @@ -83,7 +106,10 @@ class BaseModel { @override bool operator ==(final Object other) => - other is BaseModel && other.id == id && other.friendlyName == friendlyName && other.typeName == typeName; + other is BaseModel && + other.id == id && + other.friendlyName == friendlyName && + other.typeName == typeName; @override int get hashCode => Object.hash(id, friendlyName); diff --git a/lib/devices/connection_base_model.dart b/lib/devices/connection_base_model.dart index bf2073b..e55e9a3 100644 --- a/lib/devices/connection_base_model.dart +++ b/lib/devices/connection_base_model.dart @@ -9,15 +9,18 @@ part 'connection_base_model.g.dart'; @immutable class ConnectionBaseModel extends BaseModel { final bool isConnected; - static final isConnectedProvider = Provider.family((final ref, final id) { + static final isConnectedProvider = + Provider.family((final ref, final id) { final baseModel = ref.watch(BaseModel.byIdProvider(id)); if (baseModel is ConnectionBaseModel) return baseModel.isConnected; return null; }); - const ConnectionBaseModel(super.id, super.friendlyName, super.typeName, this.isConnected); + const ConnectionBaseModel( + super.id, super.friendlyName, super.typeName, this.isConnected); - factory ConnectionBaseModel.fromJson(final Map json) => _$ConnectionBaseModelFromJson(json); + factory ConnectionBaseModel.fromJson(final Map json) => + _$ConnectionBaseModelFromJson(json); @override Map toJson() => _$ConnectionBaseModelToJson(this); diff --git a/lib/devices/device.dart b/lib/devices/device.dart index 594b42b..39cc3c7 100644 --- a/lib/devices/device.dart +++ b/lib/devices/device.dart @@ -3,113 +3,148 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/legacy.dart'; import 'package:flutter_svg/svg.dart'; import 'package:quiver/core.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; // import 'package:signalr_client/signalr_client.dart'; // import 'package:signalr_core/signalr_core.dart'; import 'package:signalr_netcore/signalr_client.dart'; -import 'package:smarthome/controls/dashboard_card.dart'; import 'package:smarthome/devices/base_model.dart'; import 'package:smarthome/devices/device_manager.dart'; import 'package:smarthome/devices/generic/icons/icon_manager.dart'; -import 'package:smarthome/devices/zigbee/iobroker_history_model.dart'; import 'package:smarthome/helper/connection_manager.dart'; import 'package:smarthome/main.dart'; -import 'package:smarthome/models/message.dart' as sm; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:tuple/tuple.dart'; +import 'package:smarthome/models/command.dart'; +import 'package:smarthome/restapi/swagger.swagger.dart'; -final historyPropertyProvider = - FutureProvider.family, Tuple2>((final ref, final id) async { - final hubConnection = ref.watch(hubConnectionConnectedProvider); +part 'device.g.dart'; - if (hubConnection != null) { - final result = await hubConnection.invoke("GetIoBrokerHistories", args: [id.item1, id.item2]); - final resList = result as List; - return resList.map((final e) => HistoryModel.fromJson(e)).toList(); - } - return []; -}); +@riverpod +FutureOr historyPropertyName( + final Ref ref, + final int id, + final DateTime fromTime, + final DateTime toTime, + final String propertyName, +) async { + final api = ref.watch(apiProvider); + final res = await api.appHistoryRangeGet( + id: id, + from: fromTime, + to: toTime, + propertyName: propertyName, + ); + return res.bodyOrThrow; +} -final historyPropertyNameProvider = - FutureProvider.family>((final ref, final id) async { - final hubConnection = ref.watch(hubConnectionConnectedProvider); +@riverpod +Widget iconWidget( + final Ref ref, + final List typeNames, + final Device device, + final AdaptiveThemeManager themeManager, + final bool withMargin, +) { + final brightness = ref.watch(brightnessProvider(themeManager)); + final icon = ref.watch(iconByTypeNamesProvider(typeNames)); - if (hubConnection != null) { - final result = await hubConnection.invoke("GetIoBrokerHistory", args: [id.item1, id.item2, id.item3]); - final e = result as Map; - return HistoryModel.fromJson(e); - } - return HistoryModel(); -}); - -final _iconWidgetProvider = Provider.autoDispose - .family, Device, AdaptiveThemeManager, bool>>((final ref, final id) { - final brightness = ref.watch(brightnessProvider(id.item3)); - final iconByName = ref.watch(iconByTypeNamesProvider(id.item1)); - - return id.item2._createIcon(brightness, iconByName, id.item4); -}); - -final iconWidgetProvider = Provider.autoDispose - .family, Device, AdaptiveThemeManager, bool>>((final ref, final deviceTypeName) { - final iconByName = ref.watch(_iconWidgetProvider(deviceTypeName)); - return iconByName; -}); - -final iconWidgetSingleProvider = Provider.autoDispose - .family>((final ref, final deviveTypeName) { - final iconByName = ref.watch(_iconWidgetProvider( - Tuple4([deviveTypeName.item1], deviveTypeName.item2, deviveTypeName.item3, deviveTypeName.item4))); - return iconByName; -}); + return device._createIcon(brightness, icon, withMargin); +} + +@riverpod +Widget iconWidgetSingle( + final Ref ref, + final String typeName, + final Device device, + final AdaptiveThemeManager themeManager, + final bool withMargin, +) { + return ref.watch( + iconWidgetProvider([typeName], device, themeManager, withMargin), + ); +} abstract class Device { + static const fromJsonFactory = _$DeviceRequestFromJson; + + static Device _$DeviceRequestFromJson(final Map json) { + final types = (json["typeNames"] as List) + .map((final x) => x.toString()) + .toList(); + String type = "Device"; + for (final item in types) { + if (!stringNameJsonFactory.containsKey(item)) { + continue; + } + type = item; + break; + } + final id = json["id"] as int; + + final model = stringNameJsonFactory[type]!(json, types); + final dev = deviceCtorFactory[type]!(id, types.first); + dev.lastModel = model; + return dev; + } + final baseModelTProvider = Provider.family((final ref, final id) { final baseModel = ref.watch(BaseModel.byIdProvider(id)); if (baseModel is T) return baseModel; return null; }); - static final groupsByIdProvider = StateProvider.family, int>((final ref, final id) { - final friendlyNameSplit = ref.watch(BaseModel.friendlyNameProvider(id)).split(" "); + static final groupsByIdProvider = StateProvider.family, int>(( + final ref, + final id, + ) { + final friendlyNameSplit = ref + .watch(BaseModel.friendlyNameProvider(id)) + .split(" "); return [friendlyNameSplit.first]; }); - IconData? iconData; + final IconData? iconData; final String typeName; final int id; + late BaseModel? lastModel; final Random r = Random(); - Device(this.id, this.typeName, {final IconData? iconData, final Uint8List? iconBytes}) { - if (iconData != null) { - this.iconData = iconData; - } - } + Device(this.id, this.typeName, {this.iconData, this.lastModel}); - Widget _createIcon(final Brightness themeMode, final Uint8List? iconBytes, final bool withMargin) { + Widget _createIcon( + final Brightness themeMode, + final Uint8List? iconBytes, + final bool withMargin, + ) { if (iconData != null) { - return Icon( - iconData, - ); + return Icon(iconData); } if (iconBytes != null) { return createIconFromSvgByteList(iconBytes, themeMode, withMargin); } - return Container(); + return const SizedBox(); } - Widget createIconFromSvgByteList(final Uint8List list, final Brightness brightness, final bool withMargin) { + Widget createIconFromSvgByteList( + final Uint8List list, + final Brightness brightness, + final bool withMargin, + ) { if (withMargin) { return Container( margin: const EdgeInsets.all(8), child: Center( child: SvgPicture.memory( list, - colorFilter: - ColorFilter.mode(brightness == Brightness.light ? Colors.black : Colors.white, BlendMode.srcIn), + colorFilter: ColorFilter.mode( + brightness == Brightness.light ? Colors.black : Colors.white, + BlendMode.srcIn, + ), ), ), ); @@ -117,34 +152,33 @@ abstract class Device { return Center( child: SvgPicture.memory( list, - colorFilter: ColorFilter.mode(brightness == Brightness.light ? Colors.black : Colors.white, BlendMode.srcIn), + colorFilter: ColorFilter.mode( + brightness == Brightness.light ? Colors.black : Colors.white, + BlendMode.srcIn, + ), ), ); } } Widget getRightWidgets() { - return Container(); + return const SizedBox(); } Widget dashboardCardBody() => const Text(""); - Widget dashboardView(final void Function() onLongPress) { - return StatelessDashboardCard( - device: this, - onLongPress: onLongPress, - tag: id, - ); - } - // @mustCallSuper // BaseModel updateFromServer(final Map message) { // return baseModel.updateFromJson(message); // } Future getFromServer( - final String methodName, final List? args, final HubConnectionContainer container) async { - if (container.connectionState != HubConnectionState.Connected || container.connection == null) { + final String methodName, + final List? args, + final HubConnectionContainer container, + ) async { + if (container.connectionState != HubConnectionState.Connected || + container.connection == null) { return; } @@ -152,30 +186,76 @@ abstract class Device { } @override - bool operator ==(final Object other) => other is Device && other.id == id && other.typeName == typeName; + bool operator ==(final Object other) => + other is Device && other.id == id && other.typeName == typeName; @override int get hashCode => hash2(id, typeName); @mustCallSuper - Future sendToServer(final sm.MessageType messageType, final sm.Command command, final List? parameters, - final HubConnectionContainer container) async { - if (container.connectionState != HubConnectionState.Connected || container.connection == null) { - return; + Future sendToServer( + final MessageType messageType, + final Command command, + final List parameters, + final Swagger container, + ) async { + final message = JsonApiSmarthomeMessage( + parameters: parameters, + messageType: messageType, + command: command.value!, + id: id, + ); + await container.appSmarthomePost(body: message); + } + + static Future postMessage( + final int id, + final PropertyEditInformation editInfo, + final Swagger api, + final dynamic value, [ + final List preParameters = const [], + final List postParamters = const [], + ]) async { + final editParameter = + editInfo.editParameter.firstWhereOrNull((final x) => x.$value == value) ?? + editInfo.editParameter.firstOrNull; + if (editParameter == null) return; + dynamic valueParam = value; + if (editParameter.extensionData?.containsKey("Digits") ?? false) { + if (value is double) { + valueParam = value.toStringAsFixed( + editParameter.extensionData!["Digits"], + ); + } } - final message = sm.Message(id, messageType, command.index, parameters); - final jsonMsg = message.toJson(); - await container.connection!.invoke("Update", args: [jsonMsg]); + await api.appSmarthomePost( + body: JsonApiSmarthomeMessage( + parameters: [ + ...preParameters, + ...(editParameter.parameters ?? []), + valueParam, + ...postParamters, + ], + id: id, + messageType: editParameter.messageType ?? editInfo.messageType, + command: editParameter.command, + ), + ); } - Future updateDeviceOnServer(final int id, final String friendlyName, final HubConnectionContainer container) async { - if (container.connectionState != HubConnectionState.Connected || container.connection == null) { - return; - } - return await container.connection!.invoke("UpdateDevice", args: [id, friendlyName]); + Future updateDeviceOnServer( + final int id, + final String friendlyName, + final Swagger api, + ) async { + await api.appDevicePatch( + body: DeviceRenameRequest(id: id, newName: friendlyName), + ); } DeviceTypes getDeviceType(); void navigateToDevice(final BuildContext context); + + void iconPressed(final BuildContext context, final WidgetRef ref) {} } diff --git a/lib/devices/device_exporter.dart b/lib/devices/device_exporter.dart index 7cb7d4d..71c767c 100644 --- a/lib/devices/device_exporter.dart +++ b/lib/devices/device_exporter.dart @@ -3,19 +3,10 @@ export 'heater/heater.dart'; export 'heater/heater_model.dart'; export 'lamp.dart'; export 'painless_led_strip/led_strip.dart'; -export 'xiaomi/temp_sensor.dart'; export 'base_model.dart'; -export 'xiaomi/temp_sensor_model.dart'; -export 'floaltpanel/floalt_panel.dart'; -export 'zigbee/zigbee_model.dart'; -export 'osramb40rw/osram_b40_rw.dart'; -export 'osramplug/osram_plug.dart'; -export 'tradfriledbulb/tradfri_led_bulb.dart'; -export 'tradfriledbulb/tradfri_led_bulb_model.dart'; -export 'tradfricontroloutlet/tradfri_control_outlet.dart'; -export 'tradfrimotionsensor/tradfri_motion_sensor.dart'; -export 'tradfrimotionsensor/tradfri_motion_sensor_model.dart'; -export 'zigbee/zigbeelamp/zigbee_lamp.dart'; -export 'zigbee/zigbeelamp/zigbee_lamp_model.dart'; export 'generic_device.dart'; export 'connection_base_model.dart'; +export 'property_info.dart'; +export 'generic/icons/svg_icon.dart'; +export '../notifications/app_notification.dart'; +export '../models/command.dart'; diff --git a/lib/devices/device_manager.dart b/lib/devices/device_manager.dart index ed30ef8..38c44fb 100644 --- a/lib/devices/device_manager.dart +++ b/lib/devices/device_manager.dart @@ -2,9 +2,12 @@ import 'dart:collection'; import 'dart:convert'; +import 'dart:io'; import 'dart:math'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; // import 'package:signalr_client/signalr_client.dart'; // import 'package:signalr_core/signalr_core.dart'; @@ -13,16 +16,18 @@ import 'package:smarthome/devices/generic/device_layout_service.dart'; import 'package:smarthome/devices/generic/generic_device_exporter.dart'; import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/devices/painless_led_strip/led_strip_model.dart'; -import 'package:smarthome/devices/zigbee/zigbee_switch_model.dart'; import 'package:smarthome/helper/connection_manager.dart'; +import 'package:smarthome/helper/firebase_manager.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; import 'package:smarthome/helper/preference_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:tuple/tuple.dart'; +import 'package:flutter_riverpod/legacy.dart'; import '../icons/smarthome_icons.dart'; import 'device_exporter.dart'; +part 'device_manager.g.dart'; + // final deviceIdProvider = StateProvider>((final _) => []); // final deviceIdProvider = StateNotifierProvider>((final _) => DeviceIdManager([])); @@ -38,29 +43,41 @@ import 'device_exporter.dart'; // } // } -final deviceProvider = StateNotifierProvider>((final ref) { - final connection = ref.watch(hubConnectionConnectedProvider); +@riverpod +Device? deviceById(final Ref ref, final int id) { + final dm = ref.watch(deviceManagerProvider); - return DeviceManager(ref, connection); -}); + return switch (dm) { + AsyncData(:final value) => value.firstOrDefault((final e) => e.id == id), + _ => null, + }; +} -final deviceByIdProvider = Provider.family((final ref, final id) { - final dm = ref.watch(deviceProvider); - return dm.firstOrNull((final e) => e.id == id); -}); +@riverpod +Device? deviceByIdValueStoreKey( + final Ref ref, + final String valueKey, + final int id, +) { + final dm = ref.watch(deviceByIdProvider(id)); + final valueStores = ref.watch(valueStoreChangedProvider(valueKey, id)); + if (valueStores != null) { + return dm; + } -final deviceByIdValueStoreKeyProvider = Provider.family>((final ref, final key) { - final dm = ref.watch(deviceByIdProvider(key.item2)); - final valueStores = ref.watch(valueStoreChangedProvider(key)); - if (valueStores != null) return dm; return null; -}); +} + +final devicesByValueStoreKeyProvider = Provider.family, String>(( + final ref, + final key, +) { + final dm = ref.watch(deviceManagerProvider); + if (!dm.hasValue) return []; -final devicesByValueStoreKeyProvider = Provider.family, String>((final ref, final key) { - final dm = ref.watch(deviceProvider); final List devices = []; - for (final device in dm) { - final valueStores = ref.watch(valueStoreChangedProvider(Tuple2(key, device.id))); + for (final device in dm.value!) { + final valueStores = ref.watch(valueStoreChangedProvider(key, device.id)); if (valueStores != null) devices.add(device); } return devices; @@ -68,21 +85,35 @@ final devicesByValueStoreKeyProvider = Provider.family, String>((fi final sortedDeviceProvider = Provider>((final ref) { final sort = ref.watch(deviceSortProvider); - final devices = ref.watch(deviceProvider); + final devicesManager = ref.watch(deviceManagerProvider); final baseModels = ref.watch(baseModelFriendlyNamesMapProvider); + if (!devicesManager.hasValue) return []; + final devices = devicesManager.requireValue; switch (sort) { case SortTypes.NameAsc: - devices.sort((final x, final b) => baseModels[x.id]!.compareTo(baseModels[b.id]!)); + devices.sort( + (final x, final b) => baseModels[x.id]!.compareTo(baseModels[b.id]!), + ); break; case SortTypes.NameDesc: - devices.sort((final x, final b) => baseModels[b.id]!.compareTo(baseModels[x.id]!)); + devices.sort( + (final x, final b) => baseModels[b.id]!.compareTo(baseModels[x.id]!), + ); break; case SortTypes.TypeAsc: - devices.sort((final x, final b) => x.typeName.runtimeType.toString().compareTo(b.runtimeType.toString())); + devices.sort( + (final x, final b) => x.typeName.runtimeType.toString().compareTo( + b.runtimeType.toString(), + ), + ); break; case SortTypes.TypeDesc: - devices.sort((final x, final b) => b.typeName.runtimeType.toString().compareTo(x.runtimeType.toString())); + devices.sort( + (final x, final b) => b.typeName.runtimeType.toString().compareTo( + x.runtimeType.toString(), + ), + ); break; case SortTypes.IdAsd: devices.sort((final x, final b) => x.id.compareTo(b.id)); @@ -95,7 +126,9 @@ final sortedDeviceProvider = Provider>((final ref) { return devices.toList(); }); -final deviceSortProvider = StateProvider((final _) => SortTypes.NameAsc); +final deviceSortProvider = StateProvider( + (final _) => SortTypes.NameAsc, +); enum Action { removed, added } @@ -106,43 +139,58 @@ class DiffIdModel { DiffIdModel(this.id, this.action); } -class DeviceManager extends StateNotifier> { +final deviceCtorFactory = { + 'Heater': (final i, final n) => Heater(i, n, Icons.whatshot), + 'LedStrip': (final i, final n) => LedStrip(i, n, Icons.lightbulb_outline), + 'Device': (final i, final n) => GenericDevice(i, n), +}; +final stringNameJsonFactory = + , List)>{ + 'Heater': (final m, final _) => HeaterModel.fromJson(m), + 'LedStrip': (final m, final _) => LedStripModel.fromJson(m), + 'Device': (final m, final t) => BaseModel.fromJson(m, t), + }; + +@Riverpod(keepAlive: true) +class DeviceManager extends _$DeviceManager { HashSet _deviceIds = HashSet(); - static DeviceManager? instance; + // late List _diffIds = []; - late List _diffIds = []; - - late Ref? _ref; static bool showDebugInformation = false; - final HubConnection? _connection; - - DeviceManager(final Ref providerRef, this._connection) : super([]) { - _ref = providerRef; - instance = this; - _fetchIds(); - } + late HubConnection? _connection; - static final customGroupNameProvider = StateProvider.family((final ref, final name) { + static final Map _groupNames = {}; + static final customGroupNameProvider = StateProvider.family(( + final ref, + final name, + ) { return _groupNames[name] ?? name; }); - static final Map _groupNames = {}; + @override + FutureOr>> build() async { + final manager = ref.watch(connectionManagerProvider); + if (!manager.hasValue) return []; + final connection = manager.requireValue.connection; + if (connection == null) return []; + + _connection = connection; - void _fetchIds() { - final ref = _ref; - final connection = _connection; - if (ref == null || connection == null) return; final ids = HashSet(); - for (final key in PreferencesManager.instance.getKeys().where((final x) => x.startsWith("SHD"))) { + for (final key in PreferencesManager.instance.getKeys().where( + (final x) => x.startsWith("SHD"), + )) { final id = PreferencesManager.instance.getInt(key); if (id == null) continue; ids.add(id); } _deviceIds = ids; - _syncDevices(); - // _clearDevicesCacheOnConnectionLost(); + return await _syncDevices( + [], + ids.map((final x) => DiffIdModel(x, Action.added)).toList(), + ); } static void init() { @@ -157,26 +205,35 @@ class DeviceManager extends StateNotifier> { } } - void subscribeToDevice(final int device) { - _deviceIds.add(device); - _syncDevices(); + Future subscribeToDevice(final int id) async { + if (!_deviceIds.add(id)) return; + await _syncDevices(state.value ?? [], [DiffIdModel(id, Action.added)]); } - void subscribeToDevices(final List deviceIds) { - _deviceIds.addAll(deviceIds); - _syncDevices(); + Future subscribeToDevices(final List deviceIds) async { + final diffs = []; + for (final id in deviceIds) { + if (_deviceIds.add(id)) diffs.add(DiffIdModel(id, Action.added)); + } + if (diffs.isEmpty) return; + + await _syncDevices(state.value ?? [], diffs); } - removeDevice(final int id) { - _deviceIds.remove(id); - _syncDevices(); + Future removeDevice(final int id) async { + if (!_deviceIds.remove(id)) return; + await _syncDevices(state.value ?? [], [DiffIdModel(id, Action.removed)]); } void saveDeviceGroups() { - if (_ref == null) return; - - final deviceGroups = state - .map((final e) => "${e.id}\u0002${_ref!.read(Device.groupsByIdProvider(e.id)).join("\u0003")}") + final current = state.value; + if (current == null) return; + + final deviceGroups = current + .map( + (final e) => + "${e.id}\u0002${ref.read(Device.groupsByIdProvider(e.id)).join("\u0003")}", + ) .join("\u0001"); PreferencesManager.instance.setString("deviceGroups", deviceGroups); } @@ -187,72 +244,27 @@ class DeviceManager extends StateNotifier> { // } void changeGroupName(final String key, final String newName) { - _ref!.read(customGroupNameProvider(key).notifier).state = newName; + ref.read(customGroupNameProvider(key).notifier).state = newName; _groupNames[key] = newName; - final strs = _groupNames.select((final key, final value) => "$key\u0001$value"); + final strs = _groupNames.select( + (final key, final value) => "$key\u0001$value", + ); PreferencesManager.instance.setStringList("customGroupNames", strs); } - static List getDevicesOfType() { - return instance!.state.whereType().toList(); // where((x) => x.getDeviceType() == type).toList(); - } - - static Device getDeviceWithId(final int? id) { - return instance!.state.firstWhere((final x) => x.id == id); - } - - static final ctorFactory = { - //'LedStripMesh': (i, s, h, sp) => LedStrip(i, s, h, Icon(Icons.lightbulb_outline), sp), - 'Heater': (final i, final n) => Heater(i, n, Icons.whatshot), - // 'XiaomiTempSensor': (i, s) => XiaomiTempSensor(i, n, "Temperatursensor", - // s as TempSensorModel, ConnectionManager.hubConnection, - // icon: SmarthomeIcons.xiaomiTempSensor), - 'LedStrip': (final i, final n) => LedStrip(i, n, Icons.lightbulb_outline), - 'FloaltPanel': (final i, final n) => FloaltPanel(i, n, Icons.crop_square), - 'OsramB40RW': (final i, final n) => OsramB40RW(i, n, Icons.lightbulb_outline), - 'ZigbeeLamp': (final i, final n) => ZigbeeLamp(i, n, Icons.lightbulb_outline), - 'OsramPlug': (final i, final n) => OsramPlug(i, n, Icons.radio_button_checked), - 'TradfriLedBulb': (final i, final n) => TradfriLedBulb(i, n, Icons.lightbulb_outline), - 'TradfriControlOutlet': (final i, final n) => TradfriControlOutlet(i, n, Icons.radio_button_checked), - 'TradfriMotionSensor': (final i, final n) => TradfriMotionSensor(i, n, Icons.sensors), - 'Device': (final i, final n) => GenericDevice(i, n), - }; - static final stringNameJsonFactory = , List)>{ - // 'LedStripMesh': (m) => LedStripModel.fromJson(m), - 'Heater': (final m, final _) => HeaterModel.fromJson(m), - // 'XiaomiTempSensor': (m) => TempSensorModel.fromJson(m), - 'LedStrip': (final m, final _) => LedStripModel.fromJson(m), - 'ZigbeeLamp': (final m, final _) => ZigbeeLampModel.fromJson(m), - 'FloaltPanel': (final m, final _) => ZigbeeLampModel.fromJson(m), - 'OsramB40RW': (final m, final _) => ZigbeeLampModel.fromJson(m), - 'OsramPlug': (final m, final _) => ZigbeeSwitchModel.fromJson(m), - 'TradfriLedBulb': (final m, final _) => TradfriLedBulbModel.fromJson(m), - 'TradfriControlOutlet': (final m, final _) => ZigbeeSwitchModel.fromJson(m), - 'TradfriMotionSensor': (final m, final _) => TradfriMotionSensorModel.fromJson(m), - 'Device': (final m, final t) => BaseModel.fromJson(m, t) - }; - - void _loadDevices(final subs, final List ids) { - final ref = _ref; - if (ref == null) return; - final devices = state.toList(); - final StateController> baseModelState; - try { - baseModelState = ref.read(baseModelProvider.notifier); - } catch (e) { - print(e); - return; - } + List> _loadDevices(final List subs, final List ids) { + final List baseModels = ref.read(baseModelsProvider).toList(); - final baseModels = baseModelState.state.toList(); + final devices = []; // final futures = []; for (final id in ids) { - final sub = subs.firstWhere((final x) => x["id"] == id, orElse: () => null); + final sub = subs.firstOrDefault((final x) => x["id"] == id); if (sub == null) { continue; } - final types = PreferencesManager.instance.getStringList("Types$id") ?? []; + final types = + PreferencesManager.instance.getStringList("Types$id") ?? []; BaseModel? model; String? type; if (types.isEmpty) { @@ -276,12 +288,20 @@ class DeviceManager extends StateNotifier> { break; } } - try { model = stringNameJsonFactory[type]!(sub, types); - final dev = ctorFactory[type]!(id, types.first); - devices.add(dev as Device); - final toRemove = baseModels.firstOrNull((final element) => element.id == id); + final dev = deviceCtorFactory[type]!(id, types.first); + devices.add(dev); + ref.read(stateServiceProvider.notifier).updateAndGetStores(id, sub); + if (sub["dynamicStateData"] case final Map? extData + when extData != null) { + ref + .read(stateServiceProvider.notifier) + .updateAndGetStores(id, extData); + } + final toRemove = baseModels.firstOrDefault( + (final element) => element.id == id, + ); if (toRemove != null) { baseModels.remove(toRemove); } @@ -301,15 +321,18 @@ class DeviceManager extends StateNotifier> { final deviceGroup = item.split("\u0002"); final deviceId = deviceGroup.first; final groups = deviceGroup.last.split("\u0003"); - final dev = devices.firstOrNull((final element) => element.id.toString() == deviceId); + final dev = devices.firstOrDefault( + (final element) => element.id.toString() == deviceId, + ); if (dev != null) { try { - final groupings = ref.read(Device.groupsByIdProvider(dev.id).notifier); + final groupings = ref.read( + Device.groupsByIdProvider(dev.id).notifier, + ); groupings.state = groups; } catch (ex) { print(ex); - return; } } } @@ -322,94 +345,91 @@ class DeviceManager extends StateNotifier> { // return; // } - baseModelState.state = baseModels; - - try { - state = devices; - } catch (ex) { - print(ex); - return; - } + ref.read(baseModelsProvider.notifier).storeModels(baseModels); + return devices; } Future reloadCurrentDevices() async { - final ref = _ref; - final connection = _connection; - if (ref == null || connection == null) return; - - final s = connection.invoke("GetAllDevices", args: []); - - final serverDevices = await s; - if (serverDevices is! List) return; - - final baseModelState = ref.read(baseModelProvider.notifier); - final baseModels = baseModelState.state.toList(); - for (int i = baseModels.length - 1; i >= 0; i--) { - final baseModel = baseModels[i]; - final existingDevice = serverDevices.firstOrNull((final element) => element["id"] == baseModel.id); - if (existingDevice == null) continue; - final newBaseModel = baseModel.updateFromJson(existingDevice); - if (newBaseModel == baseModel) continue; - baseModels.remove(baseModel); - baseModels.add(newBaseModel); + final baseModels = ref.read(baseModelsProvider).toList(); + if (baseModels.isNotEmpty) { + final api = ref.read(apiProvider); + + final res = await api.appDeviceGet(); + final devices = res.bodyOrThrow; + for (int i = baseModels.length - 1; i >= 0; i--) { + final baseModel = baseModels[i]; + final existingDevice = devices.firstOrDefault( + (final element) => element.id == baseModel.id, + ); + if (existingDevice == null) continue; + + baseModels.remove(baseModel); + baseModels.add(existingDevice.lastModel!); + } + ref.read(baseModelsProvider.notifier).storeModels(baseModels); } - baseModelState.state = baseModels; } void removeAllDevices() { - for (int i = state.length - 1; i >= 0; i--) { - final d = state.elementAt(i); - PreferencesManager.instance.remove("SHD${d.id}"); - PreferencesManager.instance.remove("Json${d.id}"); - PreferencesManager.instance.remove("Type${d.id}"); + final devices = state.value?.map((final x) => x.id).toList() ?? _deviceIds; + if (devices.isEmpty) { + _deviceIds.clear(); + return; + } + for (int i = devices.length - 1; i >= 0; i--) { + final d = devices.elementAt(i); + PreferencesManager.instance.remove("SHD$d"); + PreferencesManager.instance.remove("Json$d"); + PreferencesManager.instance.remove("Type$d"); } _deviceIds.clear(); - _syncDevices(); + _syncDevices( + [], + devices.map((final x) => DiffIdModel(x, Action.removed)).toList(), + ); saveDeviceGroups(); } - void _syncDevices() { - final ref = _ref; + Future>> _syncDevices( + final List> current, + final List diffIds, + ) async { final connection = _connection; - if (ref == null || connection == null || connection.state != HubConnectionState.Connected) return; - final deviceIds = _deviceIds; - - final deviceIdsSet = deviceIds.toSet(); - final existingDeviceIdsSet = state.map((final x) => x.id).toSet(); - - final newDevices = deviceIdsSet.difference(existingDeviceIdsSet); - final removedDevices = existingDeviceIdsSet.difference(deviceIdsSet); - - _diffIds = [ - for (final id in newDevices) DiffIdModel(id, Action.added), - for (final id in removedDevices) DiffIdModel(id, Action.removed), - ]; - - ref.watch(valueStoreProvider); - if (_diffIds.isEmpty) { - return; + if (connection == null || + connection.state != HubConnectionState.Connected) { + return current; } - if (_diffIds.any((final element) => element.action == Action.added)) { - final deviceIds = _diffIds.where((final x) => x.action == Action.added).map((final x) => x.id).toList(); - connection.invoke("Subscribe", args: [deviceIds]).then((final subs) { - _loadDevices(subs, deviceIds); - for (final id in deviceIds) { - if (PreferencesManager.instance.containsKey("SHD$id")) continue; - PreferencesManager.instance.setInt("SHD$id", id); + if (diffIds.any((final element) => element.action == Action.added)) { + final deviceIds = diffIds + .where((final x) => x.action == Action.added) + .map((final x) => x.id) + .toList(); + final subs = await connection.invoke("Subscribe", args: [deviceIds]); + current.addAll(_loadDevices(subs as List, deviceIds)); + for (final id in deviceIds) { + if (PreferencesManager.instance.containsKey("SHD$id")) continue; + PreferencesManager.instance.setInt("SHD$id", id); + if (Platform.isAndroid || Platform.isIOS) { + FirebaseMessaging.instance.subscribeToTopic("device_$id"); } - }); + } } - if (_diffIds.any((final element) => element.action == Action.removed)) { - final deviceIds = _diffIds.where((final x) => x.action == Action.removed).map((final x) => x.id).toList(); - connection.invoke("Unsubscribe", args: [deviceIds]).then((final value) { - final devices = state.toList(); - for (final diffId in _diffIds) { - devices.removeWhere((final d) => d.id == diffId.id); + if (diffIds.any((final element) => element.action == Action.removed)) { + final deviceIds = diffIds + .where((final x) => x.action == Action.removed) + .map((final x) => x.id) + .toList(); + await connection.invoke("Unsubscribe", args: [deviceIds]); + + for (final diffId in diffIds) { + current.removeWhere((final d) => d.id == diffId.id); + if (Platform.isAndroid || Platform.isIOS) { + FirebaseMessaging.instance.unsubscribeFromTopic("device_$diffId"); } - state = devices; - }); + } + for (final id in deviceIds) { PreferencesManager.instance.remove("SHD$id"); PreferencesManager.instance.remove("Json$id"); @@ -417,6 +437,7 @@ class DeviceManager extends StateNotifier> { PreferencesManager.instance.remove("Types$id"); } } + return current; } } @@ -431,7 +452,7 @@ enum DeviceTypes { TradfriControlOutlet, TradfriMotionSensor, ZigbeeLamp, - Generic + Generic, } enum SortTypes { NameAsc, NameDesc, TypeAsc, TypeDesc, IdAsd, IdDesc } diff --git a/lib/devices/device_overview_model.dart b/lib/devices/device_overview_model.dart index f5ddd6b..08bcd3c 100644 --- a/lib/devices/device_overview_model.dart +++ b/lib/devices/device_overview_model.dart @@ -16,7 +16,8 @@ class DeviceOverviewModel { this.friendlyName, ); - factory DeviceOverviewModel.fromJson(final Map json) => _$DeviceOverviewModelFromJson(json); + factory DeviceOverviewModel.fromJson(final Map json) => + _$DeviceOverviewModelFromJson(json); Map toJson() => _$DeviceOverviewModelToJson(this); } diff --git a/lib/devices/floaltpanel/floalt_panel.dart b/lib/devices/floaltpanel/floalt_panel.dart deleted file mode 100644 index 48a8c5a..0000000 --- a/lib/devices/floaltpanel/floalt_panel.dart +++ /dev/null @@ -1,15 +0,0 @@ -// ignore_for_file: unnecessary_null_comparison - -import 'package:flutter/material.dart'; -// import 'package:signalr_client/signalr_client.dart'; -import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/zigbee/zigbeelamp/zigbee_lamp.dart'; - -class FloaltPanel extends ZigbeeLamp { - FloaltPanel(final int id, final String typeName, final IconData icon) : super(id, typeName, icon); - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.FloaltPanel; - } -} diff --git a/lib/devices/generic/dashboard_device_layout.dart b/lib/devices/generic/dashboard_device_layout.dart deleted file mode 100644 index 6ac35b1..0000000 --- a/lib/devices/generic/dashboard_device_layout.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:quiver/core.dart'; -import 'package:smarthome/devices/generic/generic_device_exporter.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; - -part 'dashboard_device_layout.g.dart'; - -@JsonSerializable() -class DashboardDeviceLayout { - List dashboardProperties; - - @override - bool operator ==(final Object other) => - other is DashboardDeviceLayout && other.dashboardProperties.sequenceEquals(dashboardProperties); - - @override - int get hashCode => hashObjects(dashboardProperties); - - DashboardDeviceLayout(this.dashboardProperties); - - factory DashboardDeviceLayout.fromJson(final Map json) => _$DashboardDeviceLayoutFromJson(json); - - Map toJson() => _$DashboardDeviceLayoutToJson(this); -} diff --git a/lib/devices/generic/dashboard_property_info.dart b/lib/devices/generic/dashboard_property_info.dart deleted file mode 100644 index 902c514..0000000 --- a/lib/devices/generic/dashboard_property_info.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/generic/generic_device_exporter.dart'; -import 'package:smarthome/devices/generic/layout_base_property_info.dart'; - -part 'dashboard_property_info.g.dart'; - -@JsonSerializable() -class DashboardPropertyInfo extends LayoutBasePropertyInfo { - SpecialType specialType = SpecialType.none; - - DashboardPropertyInfo(final String name, final int order) : super(name, order); - - factory DashboardPropertyInfo.fromJson(final Map json) { - return _$DashboardPropertyInfoFromJson(json); - } - - @override - bool operator ==(final Object other) => - other is DashboardPropertyInfo && - other.name == name && - other.order == order && - textStyle == other.textStyle && - editInfo == other.editInfo && - rowNr == other.rowNr && - unitOfMeasurement == other.unitOfMeasurement && - format == other.format && - showOnlyInDeveloperMode == other.showOnlyInDeveloperMode && - specialType == other.specialType; - - @override - int get hashCode => Object.hash( - name, order, textStyle, editInfo, rowNr, unitOfMeasurement, format, showOnlyInDeveloperMode, specialType); - - @override - Map toJson() => _$DashboardPropertyInfoToJson(this); -} diff --git a/lib/devices/generic/detail_device_layout.dart b/lib/devices/generic/detail_device_layout.dart deleted file mode 100644 index 96b2634..0000000 --- a/lib/devices/generic/detail_device_layout.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:quiver/core.dart'; -import 'package:smarthome/devices/generic/detail_property_info.dart'; -import 'package:smarthome/devices/generic/detail_tab_info.dart'; -import 'package:smarthome/devices/generic/history_property_info.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; - -part 'detail_device_layout.g.dart'; - -@JsonSerializable() -class DetailDeviceLayout { - List propertyInfos; - List? tabInfos; - List? historyProperties; - - @override - bool operator ==(final Object other) => - other is DetailDeviceLayout && - other.propertyInfos.sequenceEquals(propertyInfos) && - ((other.tabInfos == tabInfos || - (other.tabInfos != null && tabInfos != null && other.tabInfos!.sequenceEquals(tabInfos!)))) && - (other.historyProperties == historyProperties || - other.historyProperties != null && - historyProperties != null && - other.historyProperties!.sequenceEquals(historyProperties!)); - - @override - int get hashCode => hashObjects(propertyInfos) ^ hashObjects(tabInfos ?? []) ^ hashObjects(historyProperties ?? []); - - DetailDeviceLayout(this.propertyInfos, this.tabInfos, this.historyProperties); - - factory DetailDeviceLayout.fromJson(final Map json) => _$DetailDeviceLayoutFromJson(json); - - Map toJson() => _$DetailDeviceLayoutToJson(this); -} diff --git a/lib/devices/generic/detail_property_info.dart b/lib/devices/generic/detail_property_info.dart deleted file mode 100644 index 02177a4..0000000 --- a/lib/devices/generic/detail_property_info.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -// ignore: unnecessary_import -import 'package:smarthome/devices/generic/generic_device_exporter.dart'; -import 'package:smarthome/devices/generic/layout_base_property_info.dart'; - -part 'detail_property_info.g.dart'; - -@JsonSerializable() -class DetailPropertyInfo extends LayoutBasePropertyInfo { - String? displayName; - bool? blurryCard; - int? tabInfoId; - - DetailPropertyInfo(final String name, final int order) : super(name, order); - - @override - bool operator ==(final Object other) => - other is DetailPropertyInfo && - other.name == name && - other.order == order && - textStyle == other.textStyle && - editInfo == other.editInfo && - rowNr == other.rowNr && - unitOfMeasurement == other.unitOfMeasurement && - format == other.format && - showOnlyInDeveloperMode == other.showOnlyInDeveloperMode && - displayName == other.displayName && - blurryCard == other.blurryCard && - tabInfoId == tabInfoId; - - @override - int get hashCode => Object.hash(name, order, textStyle, editInfo, rowNr, unitOfMeasurement, format, - showOnlyInDeveloperMode, displayName, blurryCard, tabInfoId); - - factory DetailPropertyInfo.fromJson(final Map json) => _$DetailPropertyInfoFromJson(json); - @override - Map toJson() => _$DetailPropertyInfoToJson(this); -} diff --git a/lib/devices/generic/detail_tab_info.dart b/lib/devices/generic/detail_tab_info.dart deleted file mode 100644 index d484234..0000000 --- a/lib/devices/generic/detail_tab_info.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/generic/linked_device_tab.dart'; - -part 'detail_tab_info.g.dart'; - -@JsonSerializable() -class DetailTabInfo { - int id; - String iconName; - int order; - LinkedDeviceTab? linkedDevice; - bool showOnlyInDeveloperMode; - - @override - bool operator ==(final Object other) => - other is DetailTabInfo && - other.id == id && - other.showOnlyInDeveloperMode == other.showOnlyInDeveloperMode && - other.iconName == iconName && - other.order == order && - other.linkedDevice == linkedDevice; - - @override - int get hashCode => Object.hash(id, iconName, order, linkedDevice, showOnlyInDeveloperMode); - - DetailTabInfo(this.id, this.iconName, this.order, this.linkedDevice, [this.showOnlyInDeveloperMode = false]); - - factory DetailTabInfo.fromJson(final Map json) => _$DetailTabInfoFromJson(json); - - Map toJson() => _$DetailTabInfoToJson(this); -} diff --git a/lib/devices/generic/device_layout.dart b/lib/devices/generic/device_layout.dart deleted file mode 100644 index b4e18b4..0000000 --- a/lib/devices/generic/device_layout.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:quiver/core.dart'; -import 'package:smarthome/devices/generic/generic_device_exporter.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; - -part 'device_layout.g.dart'; - -@JsonSerializable() -class DeviceLayout { - String uniqueName; - String? typeName; - List? typeNames; - List? ids; - DashboardDeviceLayout? dashboardDeviceLayout; - DetailDeviceLayout? detailDeviceLayout; - int version; - bool showOnlyInDeveloperMode; - - DeviceLayout(this.uniqueName, - {this.typeName, - this.typeNames, - this.ids, - this.dashboardDeviceLayout, - this.detailDeviceLayout, - this.version = 1, - this.showOnlyInDeveloperMode = false}); - - @override - bool operator ==(final Object other) => - other is DeviceLayout && - other.uniqueName == uniqueName && - other.version == version && - other.showOnlyInDeveloperMode == showOnlyInDeveloperMode && - other.typeName == typeName && - dashboardDeviceLayout == other.dashboardDeviceLayout && - detailDeviceLayout == other.detailDeviceLayout && - (other.ids ?? []).sequenceEquals(ids ?? []) && - (other.typeNames ?? []).sequenceEquals(typeNames ?? []); - - @override - int get hashCode => - Object.hash(uniqueName, typeName, version, dashboardDeviceLayout, detailDeviceLayout, showOnlyInDeveloperMode) ^ - hashObjects(ids ?? []) ^ - hashObjects(typeNames ?? []); - - factory DeviceLayout.fromJson(final Map json) => _$DeviceLayoutFromJson(json); - - Map toJson() => _$DeviceLayoutToJson(this); -} diff --git a/lib/devices/generic/device_layout_service.dart b/lib/devices/generic/device_layout_service.dart index f18ea96..062a47b 100644 --- a/lib/devices/generic/device_layout_service.dart +++ b/lib/devices/generic/device_layout_service.dart @@ -1,160 +1,167 @@ -import 'dart:convert'; import 'dart:io'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:smarthome/devices/device_exporter.dart'; import 'package:smarthome/helper/cache_file_manager.dart'; import 'package:smarthome/helper/connection_manager.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; import 'package:smarthome/helper/settings_manager.dart'; -import 'package:tuple/tuple.dart'; +import 'package:smarthome/restapi/swagger.swagger.dart'; + import 'package:path/path.dart' as path; -import 'package:synchronized/synchronized.dart'; import 'generic_device_exporter.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:signalr_netcore/hub_connection.dart'; -final _layoutProvider = StateNotifierProvider>((final ref) { - return DeviceLayoutService(); -}); +part 'device_layout_service.g.dart'; + +@Riverpod(keepAlive: true) +class LayoutIcons extends _$LayoutIcons { + @override + FutureOr> build() async { + final api = ref.watch(apiProvider); + + final allLayouts = await api.appLayoutAllGet(); + return allLayouts.bodyOrThrow; + } + + void updateFromServer(final List? arguments) { + final updateMap = arguments![0] as Map; + final hash = arguments[1] as String; + _updateFromServer(updateMap, hash, updateStorage: true); + } -final _idLayoutProvider = Provider.family((final ref, final id) { - final layouts = ref.watch(_layoutProvider); + Future _updateFromServer( + final Map deviceLayoutJson, final String hash, + {final bool updateStorage = false}) async { + final deviceLayout = DeviceLayout.fromJson(deviceLayoutJson); - return layouts.firstOrNull((final element) => element.ids?.contains(id) ?? false); -}); + final curState = state.value; + if (curState == null) return; -final _typeNameLayoutProvider = Provider.family((final ref, final typeName) { - final layouts = ref.watch(_layoutProvider); + final api = ref.read(apiProvider); + final res = await api.appLayoutSingleGet( + typeName: deviceLayout.typeName, + iconName: deviceLayout.iconName); + if (res.body case final LayoutResponse layoutRes) { + final existingLayout = curState.firstOrDefault( + (final e) => e.layout?.uniqueName == deviceLayout.uniqueName); - return layouts - .firstOrNull((final element) => element.typeName == typeName || (element.typeNames?.contains(typeName) ?? false)); -}); + final copy = curState.toList(); + copy.remove(existingLayout); + copy.add(layoutRes); -final deviceLayoutProvider = Provider.family>((final ref, final device) { - final deviceLayoutTypeName = ref.watch(_typeNameLayoutProvider(device.item2)); - final deviceLayoutId = ref.watch(_idLayoutProvider(device.item1)); - final retLayout = deviceLayoutId ?? deviceLayoutTypeName; - final connection = ref.watch(hubConnectionConnectedProvider); - if (retLayout == null) { - DeviceLayoutService.loadFromServer(device.item1, device.item2, connection); + state = AsyncData(copy); + } } - return retLayout; -}); - -final dashboardDeviceLayoutProvider = - Provider.family>((final ref, final device) { - final layoutProvider = ref.watch(deviceLayoutProvider(device)); - return layoutProvider?.dashboardDeviceLayout; -}); - -final dashboardSpecialTypeLayoutProvider = - Provider.family?, Tuple2>((final ref, final device) { - final layoutProvider = ref.watch(dashboardDeviceLayoutProvider(device)); - return layoutProvider?.dashboardProperties.where((final element) => element.specialType != SpecialType.none).toList(); -}); - -final dashboardNoSpecialTypeLayoutProvider = - Provider.family?, Tuple2>((final ref, final device) { - final layoutProvider = ref.watch(dashboardDeviceLayoutProvider(device)); - return layoutProvider?.dashboardProperties.where((final element) => element.specialType == SpecialType.none).toList(); -}); - -final detailDeviceLayoutProvider = Provider.family>((final ref, final device) { - final layoutProvider = ref.watch(deviceLayoutProvider(device)); - return layoutProvider?.detailDeviceLayout; -}); - -final detailPropertyInfoLayoutProvider = - Provider.family?, Tuple2>((final ref, final device) { - final layoutProvider = ref.watch(detailDeviceLayoutProvider(device)); +} + +@Riverpod(keepAlive: true) +class DeviceLayouts extends _$DeviceLayouts { + static final CacheFileManager _cacheFileManager = CacheFileManager( + path.join(Directory.systemTemp.path, "smarthome_layout_cache"), "json"); + @override + List build() { + final api = ref.watch(layoutIconsProvider); + + return switch (api) { + AsyncData(:final value) => + value.map((final x) => x.layout).whereType().toList(), + _ => [], + }; + } + + DeviceLayout? getLayout(final int id, final String typeName) { + final curState = state; + + final byId = curState + .firstOrDefault((final x) => x.ids != null && x.ids!.contains(id)); + if (byId != null) return byId; + final byName = curState.firstOrDefault((final x) => + x.typeName == typeName || + (x.typeNames != null && x.typeNames!.contains(typeName))); + + if (byName != null) return byName; + return null; + } +} + +@riverpod +DashboardDeviceLayout? dashboardDeviceLayout( + final Ref ref, final int id, final String typeName) { + ref.watch(deviceLayoutsProvider); + + final ret = ref + .read(deviceLayoutsProvider.notifier) + .getLayout(id, typeName) + ?.dashboardDeviceLayout; + return ret; +} + +@riverpod +List? dashboardSpecialTypeLayout( + final Ref ref, final int id, final String typeName) { + final layoutProvider = ref.watch(dashboardDeviceLayoutProvider(id, typeName)); + return layoutProvider?.dashboardProperties + .where((final element) => element.specialType != DasboardSpecialType.none) + .toList(); +} + +@riverpod +List? dashboardNoSpecialTypeLayout( + final Ref ref, final int id, final String typeName) { + final layoutProvider = ref.watch(dashboardDeviceLayoutProvider(id, typeName)); + return layoutProvider?.dashboardProperties + .where((final element) => element.specialType == DasboardSpecialType.none) + .toList(); +} + +@riverpod +DetailDeviceLayout? detailDeviceLayout( + final Ref ref, final int id, final String typeName) { + ref.watch(deviceLayoutsProvider); + return ref + .read(deviceLayoutsProvider.notifier) + .getLayout(id, typeName) + ?.detailDeviceLayout; +} + +@riverpod +List? detailPropertyInfoLayout( + final Ref ref, final int id, final String typeName) { + final layoutProvider = ref.watch(detailDeviceLayoutProvider(id, typeName)); final props = layoutProvider?.propertyInfos; return props; -}); +} -final fabLayoutProvider = Provider.family>((final ref, final device) { - final layoutProvider = ref.watch(detailDeviceLayoutProvider(device)); +@riverpod +DetailPropertyInfo? fabLayout( + final Ref ref, final int id, final String typeName) { + final layoutProvider = ref.watch(detailDeviceLayoutProvider(id, typeName)); final props = layoutProvider?.propertyInfos; - return props?.firstOrNull((final element) => element.editInfo?.editType == EditType.floatingActionButton); -}); + return props?.firstOrDefault( + (final element) => + element.editInfo?.editType.toLowerCase() == "floatingactionbutton", + ); +} -final detailTabInfoLayoutProvider = - Provider.family?, Tuple2>((final ref, final device) { - final layoutProvider = ref.watch(detailDeviceLayoutProvider(device)); +@riverpod +List? detailTabInfoLayout( + final Ref ref, final int id, final String typeName) { + final layoutProvider = ref.watch(detailDeviceLayoutProvider(id, typeName)); final showDebugInformation = ref.watch(debugInformationEnabledProvider); final tabInfos = layoutProvider?.tabInfos; if (tabInfos == null) return null; return tabInfos .where((final element) => - !element.showOnlyInDeveloperMode || element.showOnlyInDeveloperMode == showDebugInformation) + !element.showOnlyInDeveloperMode || + element.showOnlyInDeveloperMode == showDebugInformation) .toList(); -}); +} -final detailHistoryLayoutProvider = - Provider.family?, Tuple2>((final ref, final device) { - final layoutProvider = ref.watch(detailDeviceLayoutProvider(device)); +@riverpod +List? detailHistoryLayout( + final Ref ref, final int id, final String typeName) { + final layoutProvider = ref.watch(detailDeviceLayoutProvider(id, typeName)); return layoutProvider?.historyProperties; -}); - -class DeviceLayoutService extends StateNotifier> { - DeviceLayoutService() : super([]) { - _instance = this; - } - - static DeviceLayoutService? _instance; - static final CacheFileManager _cacheFileManager = - CacheFileManager(path.join(Directory.systemTemp.path, "smarthome_layout_cache"), "json"); - static final lock = Lock(); - - static void updateFromServer(final List? arguments) { - final updateMap = arguments![0] as Map; - final hash = arguments[1] as String; - // print(updateMap); - _updateFromServer(updateMap, hash, updateStorage: true); - } - - static Future _updateFromServer(final Map deviceLayoutJson, final String hash, - {final bool updateStorage = false}) async { - final instance = _instance; - if (instance == null) return; - final deviceLayout = DeviceLayout.fromJson(deviceLayoutJson); - - final currentList = instance.state.toList(); - final existingLayout = currentList.firstOrNull((final e) => e.uniqueName == deviceLayout.uniqueName); - if (existingLayout != null && existingLayout.version == deviceLayout.version && existingLayout == deviceLayout) { - return; - } - if (existingLayout != null) currentList.remove(existingLayout); - - currentList.add(deviceLayout); - if (updateStorage) { - await _cacheFileManager.ensureDirectoryExists(); - await _cacheFileManager.writeHashCode(deviceLayout.uniqueName, hash); - await _cacheFileManager.writeContentAsString(deviceLayout.uniqueName, jsonEncode(deviceLayoutJson)); - } - instance.state = currentList; - } - - static Future loadFromServer(final int id, final String typeName, final HubConnection? connection) async { - if (_instance == null || connection == null || connection.state != HubConnectionState.Connected) return; - - await lock.synchronized(() async { - final bestFit = await connection.invoke("GetDeviceLayoutHashByDeviceId", args: [id]) as Map?; - if (bestFit == null) return; - final hash = bestFit["hash"] as String; - final localHash = await _cacheFileManager.readHashCode(bestFit["name"]); - if (localHash == hash) { - final content = await _cacheFileManager.readContentAsString(bestFit["name"]); - if (content != null) { - _updateFromServer(jsonDecode(content) as Map, hash); - return; - } - } - - final fromServer = await connection.invoke("GetDeviceLayoutByDeviceId", args: [id]); - if (fromServer == null) return; - - _updateFromServer(fromServer as Map, hash, updateStorage: true); - }); - } } diff --git a/lib/devices/generic/edit_parameter.dart b/lib/devices/generic/edit_parameter.dart deleted file mode 100644 index a214473..0000000 --- a/lib/devices/generic/edit_parameter.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/models/message.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; - -part 'edit_parameter.g.dart'; - -@JsonSerializable() -class EditParameter { - int command; - Object value; - String? displayName; - List? parameters; - int? id; - MessageType? messageType; - @JsonKey(includeFromJson: false, includeToJson: false) - late Map raw; - - EditParameter(this.command, this.value, this.displayName, this.parameters, this.id, this.messageType); - - EditParameter clone() { - return EditParameter(command, value, displayName, parameters, id, messageType); - } - - @override - bool operator ==(final Object other) => - other is EditParameter && - other.command == command && - other.value == value && - other.displayName == displayName && - (other.parameters ?? []).sequenceEquals(parameters ?? []) && - other.id == id && - other.messageType == messageType; - - @override - int get hashCode => Object.hash(command, value, displayName, parameters, id, messageType, raw); - - factory EditParameter.fromJson(final Map json) => _$EditParameterFromJson(json)..raw = json; - - Map toJson() => _$EditParameterToJson(this); -} diff --git a/lib/devices/generic/enums.dart b/lib/devices/generic/enums.dart index b10ba6f..2c774a1 100644 --- a/lib/devices/generic/enums.dart +++ b/lib/devices/generic/enums.dart @@ -1,15 +1,15 @@ -enum SpecialType { none, right } +// enum SpecialType { none, right } -enum EditType { - button, - raisedButton, - floatingActionButton, - iconButton, - toggle, - dropdown, - slider, - input, - icon /*colorpicker*/, - text, - radial -} +// enum EditType { +// button, +// raisedButton, +// floatingActionButton, +// iconButton, +// toggle, +// dropdown, +// slider, +// input, +// icon /*colorpicker*/, +// text, +// radial +// } diff --git a/lib/devices/generic/generic_device_exporter.dart b/lib/devices/generic/generic_device_exporter.dart index a826748..1716913 100644 --- a/lib/devices/generic/generic_device_exporter.dart +++ b/lib/devices/generic/generic_device_exporter.dart @@ -1,17 +1,8 @@ -export 'dashboard_property_info.dart'; -export 'history_property_info.dart'; -export 'server_text_style.dart'; -export 'dashboard_device_layout.dart'; -export 'detail_device_layout.dart'; -export 'detail_property_info.dart'; -export 'detail_tab_info.dart'; export 'enums.dart'; -export 'icons/icon_manager.dart'; -export 'linked_device_tab.dart'; -export 'property_edit_information.dart'; -export 'device_layout.dart'; export 'widgets/dashboard_layout_widget.dart'; export 'widgets/dashboard_right_value_store_widget.dart'; export 'widgets/dashboard_value_store_widget.dart'; export 'widgets/detail_value_store_widget.dart'; export 'widgets/history_series_annotation_chart_widget.dart'; +export 'package:smarthome/restapi/swagger.enums.swagger.dart'; +export 'package:smarthome/restapi/swagger.swagger.dart'; diff --git a/lib/devices/generic/history_property_info.dart b/lib/devices/generic/history_property_info.dart deleted file mode 100644 index 7874c61..0000000 --- a/lib/devices/generic/history_property_info.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'history_property_info.g.dart'; - -@JsonSerializable() -class HistoryPropertyInfo { - String propertyName; - String xAxisName; - String iconName; - String unitOfMeasurement; - int brightThemeColor; - int darkThemeColor; - String chartType = "line"; - - HistoryPropertyInfo(this.propertyName, this.xAxisName, this.unitOfMeasurement, this.iconName, this.brightThemeColor, - this.darkThemeColor, - {this.chartType = "line"}); - - @override - bool operator ==(final Object other) => - other is HistoryPropertyInfo && - propertyName == other.propertyName && - xAxisName == other.xAxisName && - unitOfMeasurement == other.unitOfMeasurement && - iconName == other.iconName && - brightThemeColor == other.brightThemeColor && - darkThemeColor == other.darkThemeColor && - chartType == other.chartType; - - @override - int get hashCode => - Object.hash(propertyName, xAxisName, unitOfMeasurement, iconName, brightThemeColor, darkThemeColor, chartType); - - factory HistoryPropertyInfo.fromJson(final Map json) => _$HistoryPropertyInfoFromJson(json); - - Map toJson() => _$HistoryPropertyInfoToJson(this); -} diff --git a/lib/devices/generic/icons/icon_manager.dart b/lib/devices/generic/icons/icon_manager.dart index 44bd0e3..ae98b4b 100644 --- a/lib/devices/generic/icons/icon_manager.dart +++ b/lib/devices/generic/icons/icon_manager.dart @@ -1,120 +1,92 @@ -import 'dart:io'; -import 'package:path/path.dart' as path; - import 'package:flutter/foundation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; // import 'package:signalr_core/signalr_core.dart'; -import 'package:signalr_netcore/signalr_client.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:smarthome/devices/generic/device_layout_service.dart'; import 'package:smarthome/devices/generic/icons/svg_icon.dart'; -import 'package:smarthome/helper/cache_file_manager.dart'; -import 'package:smarthome/helper/connection_manager.dart'; - -final _iconProvider = StateNotifierProvider>((final ref) { - return IconManager(); -}); +import 'package:smarthome/helper/iterable_extensions.dart'; -final iconByTypeNameProvider = Provider.autoDispose.family((final ref, final name) { - final iconCache = ref.watch(_iconProvider); - final result = iconCache[name]; - if (result == null) { - final connection = ref.watch(hubConnectionConnectedProvider); - if (connection != null) { - ref.read(_iconProvider.notifier)._getIconForTypeName(name, connection); - } - } - return result; -}); +part 'icon_manager.g.dart'; -final iconByNameProvider = Provider.autoDispose.family((final ref, final name) { - final iconCache = ref.watch(_iconProvider); - final result = iconCache[name]; - if (result == null) { - final connection = ref.watch(hubConnectionConnectedProvider); - if (connection != null) { - ref.read(_iconProvider.notifier)._getIconByName(name, connection); - } - } - return result; -}); - -final iconByTypeNamesProvider = Provider.autoDispose.family>((final ref, final names) { - final iconCache = ref.watch(_iconProvider); +@riverpod +Uint8List? iconByTypeNames(final Ref ref, final List names) { + final icons = ref.watch(_iconTypeNamesProvider); for (final name in names) { - if (iconCache.containsKey(name)) { - return iconCache[name]; - } + if (icons.containsKey(name)) return icons[name]?.data; } - final connection = ref.watch(hubConnectionConnectedProvider); - if (connection != null && names.isNotEmpty) { - ref.read(_iconProvider.notifier)._getIconByName(names.first, connection); + for (final name in names) { + final data = ref.read(iconByNameProvider(name)); + if (data != null) return data; } return null; -}); - -class IconManager extends StateNotifier> { - static final CacheFileManager _cacheFileManager = - CacheFileManager(path.join(Directory.systemTemp.path, "smarthome_icon_cache"), "svg"); - - IconManager() : super({}); - Future _getIconForTypeName(final String typeName, final HubConnection connection) async { - return _getIcon(typeName, "GetIconByTypeName", connection, true); - } - - Future _getIconByName(final String iconName, final HubConnection connection) async { - return _getIcon(iconName, "GetIconByName", connection, false); - } +} - Future _getIconHashForTypeName(final String typeName, final HubConnection connection) async { - return await connection.invoke("GetHashCodeByTypeName", args: [typeName]) as String; - } +@riverpod +Uint8List? iconByDeviceId(final Ref ref, final int id) { + ref.watch(deviceLayoutsProvider); + return null; +} - Future _getIconHashByName(final String iconName, final HubConnection connection) async { - return await connection.invoke("GetHashCodeByName", args: [iconName]) as String; +@riverpod +Uint8List? iconByName(final Ref ref, final String iconName) { + final iconCache = ref.watch(_iconNameProvider); + if (iconCache.containsKey(iconName)) { + return iconCache[iconName]?.data; } + return null; +} - Future _getIcon( - final String name, final String endpointName, final HubConnection? connection, final bool byTypeName) async { - if (connection == null) return null; - final cache = state; - - if (cache.containsKey(name)) return cache[name]; - - String hash; - if (byTypeName) { - hash = await _getIconHashForTypeName(name, connection); - } else { - hash = await _getIconHashByName(name, connection); - } - if (!kIsWeb) { - await _cacheFileManager.ensureDirectoryExists(); - - final hashLocal = await _cacheFileManager.readHashCode(name); - if (hashLocal == hash) { - final bytes = await _cacheFileManager.readContentAsBytes(name); - if (bytes != null) return _putIntoCache(name, bytes); - } - } - - final res = await connection.invoke(endpointName, args: [name]); - final svg = SvgIcon.fromJson(res as Map); - - if (!kIsWeb) { - await _cacheFileManager.ensureDirectoryExists(); - await _cacheFileManager.writeHashCode(name, hash); - await _cacheFileManager.writeContentAsBytes(name, svg.data!); - - return _putIntoCache(name, svg.data!); - } +@Riverpod(keepAlive: true) +Map _iconTypeNames(final Ref ref) { + final layoutsRes = ref.watch(layoutIconsProvider); + + // return layoutsRes.maybeWhen( + // orElse: () => {}, + // data: (data) { + // final filtered = data.where((x) => + // x.icon != null && + // x.layout?.typeNames != null && + // x.layout!.typeNames!.isNotEmpty); + // final grouped = filtered.groupManyBy((x) => x.layout!.typeNames!); + // final ret = + // grouped.entries.toMap((x) => x.key, (x) => x.value.first.icon!); + // return ret; + // }); + return switch (layoutsRes) { + AsyncData(:final value) => value + .where((final x) => + x.icon != null && + x.layout?.typeNames != null && + x.layout!.typeNames!.isNotEmpty) + .groupManyBy((final x) => x.layout!.typeNames!) + .entries + .toMap((final x) => x.key, (final x) => x.value.first.icon!.icon), + _ => {}, + }; +} - return _putIntoCache(name, svg.data!); - } +@Riverpod(keepAlive: true) +Map _iconTypeName(final Ref ref) { + final layoutsRes = ref.watch(layoutIconsProvider); + return switch (layoutsRes) { + AsyncData(:final value) => value + .where((final x) => x.icon != null && x.layout?.typeName != null) + .groupBy((final x) => (x.layout!.typeName!, x.icon!)) + .keys + .toMap((final x) => x.$1, (final x) => x.$2.icon), + _ => {}, + }; +} - Uint8List _putIntoCache(final String name, final Uint8List uint8list) { - state = {...state, name: uint8list}; - // cache.state[name] = uint8list; - // cache.state = cache.state.map((final key, final value) => MapEntry(key, value)); - // return uint8list; - return uint8list; - } +@Riverpod(keepAlive: true) +Map _iconName(final Ref ref) { + final layoutsRes = ref.watch(layoutIconsProvider); + return switch (layoutsRes) { + AsyncData(:final value) => value + .where((final x) => x.icon != null) + .mapMany((final x) => [x.icon!, ...x.additionalIcons]) + .distinct() + .toMap((final x) => x.name, (final x) => x.icon), + _ => {}, + }; } diff --git a/lib/devices/generic/icons/svg_icon.dart b/lib/devices/generic/icons/svg_icon.dart index 061543b..5d9db17 100644 --- a/lib/devices/generic/icons/svg_icon.dart +++ b/lib/devices/generic/icons/svg_icon.dart @@ -17,12 +17,17 @@ class SvgIcon { SvgIcon(this.name, this.hash, {this.data}); @override - bool operator ==(final Object other) => other is SvgIcon && name == other.name && hash == other.hash; + bool operator ==(final Object other) => + other is SvgIcon && name == other.name && hash == other.hash; @override int get hashCode => Object.hash(super.hashCode, name, hash); - factory SvgIcon.fromJson(final Map json) => _$SvgIconFromJson(json); + factory SvgIcon.fromJson(final Map json) => + _$SvgIconFromJson(json); + + factory SvgIcon.fromJsonFactory(final Map json) => + _$SvgIconFromJson(json); Map toJson() => _$SvgIconToJson(this); diff --git a/lib/devices/generic/layout_base_property_info.dart b/lib/devices/generic/layout_base_property_info.dart deleted file mode 100644 index b0454c1..0000000 --- a/lib/devices/generic/layout_base_property_info.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/generic/generic_device_exporter.dart'; - -part 'layout_base_property_info.g.dart'; - -@JsonSerializable() -class LayoutBasePropertyInfo { - String name; - int order; - ServerTextStyle? textStyle; - PropertyEditInformation? editInfo; - int? rowNr; - String? unitOfMeasurement; - String? format; - bool? showOnlyInDeveloperMode; - int? deviceId; - bool? expanded; - int? precision; - @JsonKey(includeFromJson: false, includeToJson: false) - late Map raw; - - LayoutBasePropertyInfo(this.name, this.order); - - @override - bool operator ==(final Object other) => - other is LayoutBasePropertyInfo && - other.name == name && - other.order == order && - textStyle == other.textStyle && - editInfo == other.editInfo && - rowNr == other.rowNr && - unitOfMeasurement == other.unitOfMeasurement && - format == other.format && - showOnlyInDeveloperMode == other.showOnlyInDeveloperMode && - deviceId == other.deviceId && - expanded == other.expanded && - precision == other.precision && - hashCode == other.hashCode; - - @override - int get hashCode => Object.hash(name, order, textStyle, editInfo, rowNr, unitOfMeasurement, format, - showOnlyInDeveloperMode, deviceId, expanded, precision, raw); - - factory LayoutBasePropertyInfo.fromJson(final Map json) => - _$LayoutBasePropertyInfoFromJson(json)..raw = json; - - Map toJson() => _$LayoutBasePropertyInfoToJson(this); -} diff --git a/lib/devices/generic/linked_device_tab.dart b/lib/devices/generic/linked_device_tab.dart deleted file mode 100644 index 1347b1a..0000000 --- a/lib/devices/generic/linked_device_tab.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:quiver/core.dart'; - -part 'linked_device_tab.g.dart'; - -@JsonSerializable() -class LinkedDeviceTab { - String deviceIdPropertyName; - String deviceType; - - LinkedDeviceTab(this.deviceIdPropertyName, this.deviceType); - - @override - bool operator ==(final Object other) => - other is LinkedDeviceTab && other.deviceIdPropertyName == deviceIdPropertyName && other.deviceType == deviceType; - - @override - int get hashCode => hash2(deviceIdPropertyName, deviceType); - - factory LinkedDeviceTab.fromJson(final Map json) => _$LinkedDeviceTabFromJson(json); - - Map toJson() => _$LinkedDeviceTabToJson(this); -} diff --git a/lib/devices/generic/property_edit_information.dart b/lib/devices/generic/property_edit_information.dart deleted file mode 100644 index 22ba8e4..0000000 --- a/lib/devices/generic/property_edit_information.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:quiver/core.dart'; -import 'package:smarthome/devices/generic/edit_parameter.dart'; -import 'package:smarthome/devices/generic/enums.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; -import 'package:smarthome/models/message.dart'; - -part 'property_edit_information.g.dart'; - -@JsonSerializable() -class PropertyEditInformation { - MessageType editCommand; - @JsonKey(defaultValue: []) - List editParameter; - String? display; - EditType editType; - String? hubMethod; - Object? activeValue; - String? dialog; - @JsonKey(includeFromJson: false, includeToJson: false) - late Map raw; - - PropertyEditInformation( - this.editCommand, this.editParameter, this.display, this.hubMethod, this.activeValue, this.editType, this.dialog); - - @override - bool operator ==(final Object other) => - other is PropertyEditInformation && - other.editType == editType && - other.editCommand == editCommand && - other.display == display && - other.hubMethod == hubMethod && - other.activeValue == activeValue && - other.dialog == dialog && - hashCode == other.hashCode && - editParameter.sequenceEquals(other.editParameter); - - @override - int get hashCode => - Object.hash(editType, editCommand, display, activeValue, dialog, raw) ^ hashObjects(editParameter); - - List getEditParametersFor(final int id) { - final ret = editParameter.map((final e) => e.clone()).toList(); - for (final element in ret) { - if (element.id == 0) { - element.id = id; - } - } - return ret; - } - - factory PropertyEditInformation.fromJson(final Map json) => - _$PropertyEditInformationFromJson(json)..raw = json; - Map toJson() => _$PropertyEditInformationToJson(this); -} diff --git a/lib/devices/generic/server_text_style.dart b/lib/devices/generic/server_text_style.dart deleted file mode 100644 index 09f6c64..0000000 --- a/lib/devices/generic/server_text_style.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'server_text_style.g.dart'; - -@JsonSerializable() -class ServerTextStyle { - FontStyle fontStyle; - @JsonKey(fromJson: _fontWeightFromJson, toJson: _fontWeightToJson) - FontWeight fontWeight; - String? fontFamily; - double? fontSize; - - ServerTextStyle(this.fontFamily, this.fontWeight, this.fontStyle); - - @override - bool operator ==(final Object other) => - other is ServerTextStyle && - other.fontStyle == fontStyle && - other.fontWeight == fontWeight && - fontFamily == other.fontFamily && - fontSize == other.fontSize; - - @override - int get hashCode => Object.hash(fontStyle, fontWeight, fontFamily, fontSize); - - TextStyle toTextStyle() { - var ts = const TextStyle(); - if (fontSize != null) ts = ts.copyWith(fontSize: fontSize); - if (fontFamily != null && fontFamily != "") ts = ts.copyWith(fontFamily: fontFamily); - ts = ts.copyWith(fontStyle: fontStyle); - ts = ts.copyWith(fontWeight: fontWeight); - return ts; - } - - factory ServerTextStyle.fromJson(final Map json) => _$ServerTextStyleFromJson(json); - - Map toJson() => _$ServerTextStyleToJson(this); - - static FontWeight _fontWeightFromJson(final dynamic abc) { - return $enumDecode(_$FontWeightEnumMap, abc) == FontWeightEnum.bold - ? FontWeight.bold - : FontWeight.normal; - } - - static String _fontWeightToJson(final FontWeight abc) { - return _$FontWeightEnumMap[abc]!; - } -} - -enum FontWeightEnum { normal, bold } - -const _$FontWeightEnumMap = { - FontWeightEnum.normal: 'normal', - FontWeightEnum.bold: 'bold', -}; diff --git a/lib/devices/generic/stores/store_service.dart b/lib/devices/generic/stores/store_service.dart index ca2234f..a8fcb8a 100644 --- a/lib/devices/generic/stores/store_service.dart +++ b/lib/devices/generic/stores/store_service.dart @@ -1,41 +1,44 @@ // ignore_for_file: unused_import +import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:smarthome/devices/generic/device_layout_service.dart'; import 'package:smarthome/devices/generic/generic_device_exporter.dart'; import 'package:smarthome/devices/generic/stores/value_store.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; +import 'package:smarthome/models/command.dart'; import 'package:smarthome/models/message.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:synchronized/synchronized.dart'; -import 'package:tuple/tuple.dart'; +part 'store_service.g.dart'; // final valueStoreProvider = StateNotifierProvider>((final ref) => StoreService()); // final _valueStoreProvider = StateProvider>>((final ref) { // StoreService(ref); // return StoreService._stores; // }); -final valueStoreProvider = StateNotifierProvider>>((final ref) { - return StoreService(); -}); -final valueStoresPerIdProvider = Provider.family?, int>((final ref, final id) { - final stores = ref.watch(valueStoreProvider); +@riverpod +List? valueStoresPerId(final Ref ref, final int id) { + final stores = ref.watch(stateServiceProvider); return stores[id]; -}); +} -final valueStoreChangedProvider = - ChangeNotifierProvider.family>((final ref, final key) { - final stores = ref.watch(valueStoresPerIdProvider(key.item2)); - return stores?.firstOrNull((final e) => e.key == key.item1); -}); +@riverpod +ValueStore? valueStoreChanged(final Ref ref, final String key, final int id) { + final stores = ref.watch(valueStoresPerIdProvider(id)); + return stores?.firstOrDefault((final e) => e.key == key); +} -class StoreService extends StateNotifier>> { - StoreService() : super({}) { - _instance = this; - } +@Riverpod(keepAlive: true) +class StateService extends _$StateService { + // static late Ref>> _ref; + // static late StateService _instance; - static late StoreService _instance; + @override + Map> build() { + // _instance = this; + // _ref = ref; + return {}; + } // static final Map getWidgetFor = { // TemperatureStore: (vp) { @@ -61,11 +64,11 @@ class StoreService extends StateNotifier>> { // ), // }; - static final lock = Lock(); - static bool updateAndGetStores(final int deviceId, final Map json) { - final Map stores = (_instance.state[deviceId] ?? []).toMap((final e) => e.key, (final e) => e); + bool updateAndGetStores(final int deviceId, final Map json) { + final Map stores = + (state[deviceId] ?? []).toMap((final e) => e.key, (final e) => e); bool changed = false; - bool rebuild = false; + bool rebuild = false; for (final item in json.keys) { if (!rebuild && !stores.containsKey(item)) { rebuild = true; @@ -82,25 +85,38 @@ class StoreService extends StateNotifier>> { for (final item in json.keys) { if (rebuild) { - stores[item] = ValueStore(deviceId, json[item], item, Command.None); + final val = getValueFromJson(json[item]); + if (val.runtimeType == DateTime) { + stores[item] = + ValueStore( + deviceId, + val, + item, + Command.none, + ); + } else { + stores[item] = ValueStore(deviceId, val, item, Command.none); + } changed = true; } else { final store = stores[item]!; - store.setValue(json[item]); + store.value = getValueFromJson(json[item]); } } if (changed) { - lock.synchronized(() { - _instance.state[deviceId] = stores.values.toList(); - print("$deviceId: ${_instance.state.length}"); + state[deviceId] = stores.values.toList(); - _instance.state = {..._instance.state}; - }); + state = {...state}; } return changed; } + static dynamic getValueFromJson(final dynamic val) { + if (val.runtimeType == String) return DateTime.tryParse(val) ?? val; + return val; + } + // static Map? getStoresFor(final int id) { // return _deviceStores[id]; // } diff --git a/lib/devices/generic/stores/value_store.dart b/lib/devices/generic/stores/value_store.dart index 85e073f..b543e34 100644 --- a/lib/devices/generic/stores/value_store.dart +++ b/lib/devices/generic/stores/value_store.dart @@ -1,7 +1,9 @@ import 'package:flutter/foundation.dart'; +import 'package:json_path/json_path.dart'; +import 'package:smarthome/devices/property_info.dart'; import 'package:smarthome/helper/datetime_helper.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; -import 'package:smarthome/models/message.dart'; +import 'package:smarthome/helper/extension_export.dart'; +import 'package:smarthome/models/command.dart'; class ValueStore extends ChangeNotifier { T currentValue; @@ -21,15 +23,22 @@ class ValueStore extends ChangeNotifier { super.dispose(); } - T getValue() { + T get value { + if (currentValue.runtimeType == (DateTime)) { + return ((currentValue as DateTime).toLocal() as T); + } + return currentValue; } - setValue(final T newValue) { + set value(T newValue) { switch (T) { - case List: + case const (List): (currentValue as List).sequenceEquals(newValue as List); return; + case const (DateTime): + newValue = DateTime.tryParse(newValue.toString()) as T; + break; default: break; } @@ -40,9 +49,9 @@ class ValueStore extends ChangeNotifier { notifyListeners(); } - updateValue(final T newValue) { + set updateValue(final T newValue) { switch (T) { - case List: + case const (List): (currentValue as List).sequenceEquals(newValue as List); return; default: @@ -54,24 +63,43 @@ class ValueStore extends ChangeNotifier { notifyListeners(); } + dynamic getFromJson(final LayoutBasePropertyInfo info) { + if (value is Map && + (info.extensionData?.containsKey("JsonPath") ?? false)) { + final path = info.extensionData!["JsonPath"]; + final prices = JsonPath(path); + + return prices.read(value).firstOrDefault((final x) => true)?.value; + // .map((match) => '${match.path}:\t${match.value}') + // .forEach(print) + } + return getValueAsString( + precision: info.precision ?? 1, format: info.format); + } + String getMeasuremtUnit() { return ""; } - String getValueAsString({final int precision = 1, final String? format}) { - if (currentValue.runtimeType == (double)) { - return (currentValue as double).toStringAsFixed(precision); + String getValueAsString( + {final int precision = 1, + final String? format, + final bool asHex = false}) { + final val = value; + if (val is num) { + if (asHex) return val.toHex(); + return val.toStringAsFixed(precision); } if (format != null) { - if (currentValue.runtimeType == (DateTime)) { - return (currentValue as DateTime).toDate(format: format); + if (val is DateTime) { + return (val as DateTime).toDate(format: format); } } - if (currentValue is List) { - return (currentValue as List).join('\r\n'); + if (val is List) { + return (val as List).join('\r\n'); } - return currentValue.toString(); + return val.toString(); } } diff --git a/lib/devices/generic/widgets/dashboard_layout_widget.dart b/lib/devices/generic/widgets/dashboard_layout_widget.dart index ef754ef..739b501 100644 --- a/lib/devices/generic/widgets/dashboard_layout_widget.dart +++ b/lib/devices/generic/widgets/dashboard_layout_widget.dart @@ -8,11 +8,11 @@ class DashboardLayoutWidget extends ConsumerWidget { final List layout; final GenericDevice device; - const DashboardLayoutWidget(this.device, this.layout, {final Key? key}) : super(key: key); + const DashboardLayoutWidget(this.device, this.layout, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { - if (layout.isEmpty) return Container(); + if (layout.isEmpty) return const SizedBox(); return Column( children: [ diff --git a/lib/devices/generic/widgets/dashboard_right_value_store_widget.dart b/lib/devices/generic/widgets/dashboard_right_value_store_widget.dart index fb81a85..f13c6db 100644 --- a/lib/devices/generic/widgets/dashboard_right_value_store_widget.dart +++ b/lib/devices/generic/widgets/dashboard_right_value_store_widget.dart @@ -1,40 +1,44 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:smarthome/devices/device_exporter.dart'; import 'package:smarthome/devices/generic/generic_device_exporter.dart'; import 'package:smarthome/devices/generic/stores/store_service.dart'; -import 'package:smarthome/devices/generic_device.dart'; import 'package:smarthome/helper/settings_manager.dart'; -import 'package:tuple/tuple.dart'; -class DashboardRightValueStoreWidget extends ConsumerWidget { - final DashboardPropertyInfo e; +class DashboardRightValueStoreWidget extends HookConsumerWidget { + final DashboardPropertyInfo info; final GenericDevice device; - const DashboardRightValueStoreWidget(this.e, this.device, {final Key? key}) : super(key: key); + const DashboardRightValueStoreWidget(this.info, this.device, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { - final valueModel = ref.watch(valueStoreChangedProvider(Tuple2(e.name, e.deviceId ?? device.id))); - if (valueModel == null) return Container(); + final valueModel = ref.watch( + valueStoreChangedProvider(info.name, info.deviceId ?? device.id)); + if (valueModel == null) return const SizedBox(); + useListenable(valueModel); final showDebugInformation = ref.watch(debugInformationEnabledProvider); - if ((e.showOnlyInDeveloperMode ?? false) && !showDebugInformation) return Container(); - if (e.specialType == SpecialType.right) { - return device.getEditWidget(context, e, valueModel, ref); + if ((info.showOnlyInDeveloperMode ?? false) && !showDebugInformation) { + return const SizedBox(); + } + if (info.specialType == DasboardSpecialType.right) { + return device.getEditWidget(info, valueModel); } // else if (e.specialType == SpecialType.disabled) { - // if (valueModel.currentValue.runtimeType != (bool)) return Container(); + // if (valueModel.currentValue.runtimeType != (bool)) return const SizedBox(); // final currentValue = valueModel.currentValue as bool; // return Icon( // currentValue ? Icons.power_off_outlined : Icons.power_outlined, // size: 20, // ); // } - return Container(); + return const SizedBox(); } // StatelessWidget buildBatteryIcon(final ValueStore valueModel) { - // if (valueModel.currentValue.runtimeType != (int)) return Container(); + // if (valueModel.currentValue.runtimeType != (int)) return const SizedBox(); // final currentValue = valueModel.currentValue as int; // return Icon( // (currentValue > 80 diff --git a/lib/devices/generic/widgets/dashboard_value_store_widget.dart b/lib/devices/generic/widgets/dashboard_value_store_widget.dart index 3c10196..d04b8ff 100644 --- a/lib/devices/generic/widgets/dashboard_value_store_widget.dart +++ b/lib/devices/generic/widgets/dashboard_value_store_widget.dart @@ -1,29 +1,33 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:smarthome/devices/device_exporter.dart'; import 'package:smarthome/devices/generic/generic_device_exporter.dart'; import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/helper/settings_manager.dart'; -import 'package:tuple/tuple.dart'; -class DashboardValueStoreWidget extends ConsumerWidget { - final DashboardPropertyInfo e; +class DashboardValueStoreWidget extends HookConsumerWidget { + final DashboardPropertyInfo info; final GenericDevice device; - const DashboardValueStoreWidget(this.e, this.device, {final Key? key}) : super(key: key); + const DashboardValueStoreWidget(this.info, this.device, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { - final valueModel = ref.watch(valueStoreChangedProvider(Tuple2(e.name, e.deviceId ?? device.id))); + final valueModel = ref.watch( + valueStoreChangedProvider(info.name, info.deviceId ?? device.id)); final showDebugInformation = ref.watch(debugInformationEnabledProvider); + if (valueModel != null) { + useListenable(valueModel); + } if (valueModel == null || valueModel.currentValue == null || - (e.specialType == SpecialType.right) || - ((e.showOnlyInDeveloperMode ?? false) && !showDebugInformation)) { - return Container(); + (info.specialType == DasboardSpecialType.right) || + ((info.showOnlyInDeveloperMode ?? false) && !showDebugInformation)) { + return const SizedBox(); } - return device.getEditWidget(context, e, valueModel, ref); + return device.getEditWidget(info, valueModel); } // Widget _buildInput(final ValueStore valueModel, final DashboardPropertyInfo e, final WidgetRef ref) { @@ -44,7 +48,7 @@ class DashboardValueStoreWidget extends ConsumerWidget { // onSubmitted: (final value) async { // final message = Message( // edit.id ?? deviceId, edit.messageType ?? info.editCommand, edit.command, [value, ...?edit.parameters]); - // await ref.read(hubConnectionProvider).invoke(info.hubMethod ?? "Update", args: [message.toJson()]); + // await ref.read(apiProvider).invoke(info.hubMethod ?? "Update", args: [message.toJson()]); // }, // controller: tec, // ); diff --git a/lib/devices/generic/widgets/detail_value_store_widget.dart b/lib/devices/generic/widgets/detail_value_store_widget.dart index fb9cc00..9f2fe2a 100644 --- a/lib/devices/generic/widgets/detail_value_store_widget.dart +++ b/lib/devices/generic/widgets/detail_value_store_widget.dart @@ -1,45 +1,40 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:smarthome/devices/device_exporter.dart'; -import 'package:smarthome/devices/generic/detail_property_info.dart'; -import 'package:smarthome/devices/generic/generic_device_exporter.dart'; import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/helper/settings_manager.dart'; -import 'package:tuple/tuple.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -class DetailValueStoreWidget extends ConsumerWidget { +class DetailValueStoreWidget extends HookConsumerWidget { final DetailPropertyInfo e; final GenericDevice device; - const DetailValueStoreWidget(this.e, this.device, {final Key? key}) : super(key: key); + const DetailValueStoreWidget(this.e, this.device, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { - final valueModel = ref.watch(valueStoreChangedProvider(Tuple2(e.name, e.deviceId ?? device.id))); + final valueModel = + ref.watch(valueStoreChangedProvider(e.name, e.deviceId ?? device.id)); + if (valueModel == null) return Text((e.displayName)); + useListenable(valueModel); final showDebugInformation = ref.watch(debugInformationEnabledProvider); if ((e.showOnlyInDeveloperMode ?? false) && !showDebugInformation) { - return Container(); + return const SizedBox(); } - final text = Text( - (e.displayName ?? "") + - (valueModel?.getValueAsString(format: e.format, precision: e.precision ?? 1) ?? "") + - (e.unitOfMeasurement ?? ""), - style: e.textStyle?.toTextStyle(), - ); - - Widget ret; if (e.editInfo != null) { - ret = Row( - children: [text, device.getEditWidget(context, e, valueModel, ref)], - ); - ret = device.getEditWidget(context, e, valueModel, ref); - } else { - ret = text; + // ret = Row( + // children: [text, device.getEditWidget(e, valueModel)], + // ); + return device.getEditWidget(e, valueModel); } - - return ret; + final jsonVal = valueModel.getFromJson(e)?.toString() ?? ""; + final text = Text( + (e.displayName) + (jsonVal) + (e.unitOfMeasurement), + style: GenericDevice.toTextStyle(e.textStyle), + ); + return text; } } diff --git a/lib/devices/generic/widgets/edits/advanced_slider.dart b/lib/devices/generic/widgets/edits/advanced_slider.dart new file mode 100644 index 0000000..6a382c6 --- /dev/null +++ b/lib/devices/generic/widgets/edits/advanced_slider.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:smarthome/devices/device_exporter.dart'; +import 'package:smarthome/devices/generic/stores/value_store.dart'; +import 'package:smarthome/helper/connection_manager.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +class AdvancedSlider extends HookConsumerWidget { + final int id; + final ValueStore valueModel; + final LayoutBasePropertyInfo info; + + const AdvancedSlider(this.id, this.valueModel, + {super.key, required this.info}); + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final editInfo = info.editInfo; + final valueModel = this.valueModel; + if (valueModel.currentValue is! num || editInfo == null) { + return const SizedBox(); + } + useListenable(valueModel); + final raw = editInfo.extensionData ?? {}; + + final minimum = (raw.containsKey("Min") + ? double.tryParse(raw["Min"].toString()) + : null) ?? + 5.0; + final maximum = (raw.containsKey("Max") + ? double.tryParse(raw["Max"].toString()) + : null) ?? + 35.0; + final interval = (raw.containsKey("Interval") + ? double.tryParse(raw["Interval"].toString()) + : null) ?? + 1.0; + final minorTickInterval = (raw.containsKey("MinorTickInterval") + ? int.tryParse(raw["MinorTickInterval"].toString()) + : null) ?? + 0; + + List gradients; + if (raw.containsKey("GradientColors")) { + final gradientColor = raw["GradientColors"] as List; + gradients = []; + for (final grad in gradientColor) { + if (grad is int) { + gradients.add(Color(grad)); + } else if (grad is List) { + gradients.add(Color.fromARGB(grad[0], grad[1], grad[2], grad[3])); + } + } + } else { + gradients = [Colors.blue, Colors.amber, Colors.red]; + } + + List stops; + if (raw.containsKey("Stops")) { + final stopsInfo = raw["Stops"] as List; + stops = []; + for (final grad in stopsInfo) { + if (grad is double) { + stops.add(grad); + } + } + } else { + stops = [0.3, 0.5, 1]; + } + + return SfLinearGauge( + isMirrored: true, + minimum: minimum, + maximum: maximum, + interval: interval, + minorTicksPerInterval: minorTickInterval, + animateAxis: true, + labelFormatterCallback: (final String value) { + return value + info.unitOfMeasurement; + }, + axisTrackStyle: const LinearAxisTrackStyle(thickness: 1), + barPointers: [ + LinearBarPointer( + value: maximum, + thickness: 4, + enableAnimation: false, + position: LinearElementPosition.outside, + shaderCallback: (final Rect bounds) { + return LinearGradient(colors: gradients, stops: stops) + .createShader(bounds); + }), + ], + markerPointers: [ + LinearWidgetPointer( + value: (valueModel.value as num).toDouble(), + enableAnimation: false, + onChanged: (final value) => valueModel.value = value, + offset: 7, + position: LinearElementPosition.outside, + child: SizedBox( + width: 55, + height: 45, + child: Center( + child: Text( + valueModel.getValueAsString( + precision: info.precision ?? 0) + + info.unitOfMeasurement, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + // color: valueModel.currentValue < 20 + // ? Colors.green + // : valueModel.currentValue < 60 + // ? Colors.orange + // : Colors.red, + ), + )))), + LinearShapePointer( + offset: 4, + enableAnimation: false, + onChanged: (final dynamic value) { + valueModel.value = value as double; + }, + onChangeEnd: (final value) async { + await Device.postMessage( + id, + editInfo, + ref.read(apiProvider), + value, + ); + }, + value: (valueModel.currentValue as num).toDouble(), + // color: valueModel.currentValue < 20 + // ? Colors.green + // : valueModel.currentValue < 60 + // ? Colors.orange + // : Colors.red, + ), + ]); + } +} diff --git a/lib/devices/generic/widgets/edits/basic_edit_types.dart b/lib/devices/generic/widgets/edits/basic_edit_types.dart index 412fedd..d71fccd 100644 --- a/lib/devices/generic/widgets/edits/basic_edit_types.dart +++ b/lib/devices/generic/widgets/edits/basic_edit_types.dart @@ -1,138 +1,268 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:smarthome/controls/gradient_rounded_rect_slider_track_shape.dart'; -import 'package:smarthome/devices/generic/layout_base_property_info.dart'; +import 'package:smarthome/devices/device_exporter.dart'; import 'package:smarthome/devices/generic/stores/value_store.dart'; -import 'package:smarthome/devices/generic_device.dart'; import 'package:smarthome/devices/heater/heater_config.dart'; import 'package:smarthome/devices/heater/temp_scheduling.dart'; import 'package:smarthome/helper/connection_manager.dart'; -import 'package:smarthome/models/message.dart'; -import 'package:tuple/tuple.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:smarthome/restapi/swagger.swagger.dart'; -class BasicEditTypes { - static Widget buildButton(final int id, final BuildContext context, final ValueStore? valueModel, - final LayoutBasePropertyInfo e, final WidgetRef ref, final bool raisedButton) { - final info = e.editInfo!; - final tempSettingsDialog = info.dialog == "HeaterConfig"; +class BasicIcon extends ConsumerWidget { + const BasicIcon({super.key, required this.info}); + final LayoutBasePropertyInfo info; - if (raisedButton) { - return ElevatedButton( - onPressed: (() async { - if (tempSettingsDialog) { - pushTempSettings(context, id, e, ref); - } else { - await ref.read(hubConnectionConnectedProvider)?.invoke(info.hubMethod ?? "Update", - args: [GenericDevice.getMessage(info, info.editParameter.first, id).toJson()]); - } - }), - child: Text(info.display!, - style: TextStyle( - fontWeight: valueModel?.currentValue == info.activeValue ? FontWeight.bold : FontWeight.normal)), - ); + @override + Widget build(final BuildContext context, final WidgetRef ref) { + if (info.editInfo == null) return const SizedBox(); + final edit = GenericDevice.getEditParameter(null, info.editInfo!, "icon"); + if (edit == null) return const SizedBox(); + final raw = edit.extensionData ?? {}; + final color = raw["Color"] as int?; + if (raw["Disable"] == true || !raw.containsKey("CodePoint")) { + return const SizedBox(); } + return Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon( + IconData( + raw["CodePoint"] as int, + fontFamily: raw["FontFamily"] ?? 'MaterialIcons', + ), + color: color == null ? null : Color(color), + size: (raw["Size"] as num?)?.toDouble(), + ), + ); + } +} - return MaterialButton( - onPressed: (() async { - if (tempSettingsDialog) { - pushTempSettings(context, id, e, ref); - } else { - await ref.read(hubConnectionConnectedProvider)?.invoke(info.hubMethod ?? "Update", - args: [GenericDevice.getMessage(info, info.editParameter.first, id).toJson()]); - } - }), - child: Text(info.display!, +class BasicButton extends ConsumerWidget { + const BasicButton({ + super.key, + required this.id, + required this.valueModel, + required this.info, + required this.raisedButton, + }); + final int id; + final ValueStore? valueModel; + final LayoutBasePropertyInfo info; + + final bool raisedButton; + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final editInfo = info.editInfo!; + final tempSettingsDialog = editInfo.dialog == "HeaterConfig"; + final child = Row( + children: [ + BasicIcon(info: info), + Text( + editInfo.display!, style: TextStyle( - fontWeight: valueModel?.currentValue == info.activeValue ? FontWeight.bold : FontWeight.normal)), + fontWeight: valueModel?.currentValue == editInfo.activeValue + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], ); + final onPressed = (() async { + if (tempSettingsDialog) { + pushTempSettings(context, id, info, ref); + } else { + await Device.postMessage(id, editInfo, ref.read(apiProvider), null); + } + }); + if (raisedButton) { + return ElevatedButton(onPressed: onPressed, child: child); + } + + return MaterialButton(onPressed: onPressed, child: child); + } + + static void pushTempSettings( + final BuildContext context, + final int id, + final LayoutBasePropertyInfo e, + final WidgetRef ref, + ) async { + final res = await Navigator.push( + context, + MaterialPageRoute<(bool, List)>( + builder: (final BuildContext context) => TempScheduling(id), + fullscreenDialog: true, + ), + ); + if (res == null || !res.$1) return; + + ref + .read(apiProvider) + .appSmarthomePost( + body: JsonApiSmarthomeMessage( + parameters: ["store", ...res.$2.map((final f) => jsonEncode(f))], + id: id, + messageType: MessageType.options, + command: Command.temp.value!, + ), + ); } +} - static Widget icon(final ValueStore? valueModel, final LayoutBasePropertyInfo e, final WidgetRef ref) { - final edit = GenericDevice.getEditParameter(valueModel, e.editInfo!); +class BasicDropdown extends ConsumerWidget { + const BasicDropdown({ + super.key, + required this.id, + required this.valueModel, + required this.info, + }); + final int id; + final ValueStore? valueModel; + final LayoutBasePropertyInfo info; - return Icon( - IconData(edit.raw["CodePoint"] as int, fontFamily: edit.raw["FontFamily"] ?? 'MaterialIcons'), + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final editInfo = info.editInfo!; + return Row( + children: [ + BasicIcon(info: info), + DropdownButton( + items: editInfo.editParameter + .map( + (final e) => DropdownMenuItem( + value: e.$value, + child: Text(e.displayName ?? e.$value.toString()), + ), + ) + .toList(), + onChanged: (final value) async { + await Device.postMessage( + id, + editInfo, + ref.read(apiProvider), + valueModel?.currentValue, + ); + }, + value: valueModel?.currentValue, + ), + ], ); } +} + +class BasicIconButton extends ConsumerWidget { + const BasicIconButton({ + super.key, + required this.id, + required this.valueModel, + required this.info, + }); - static Widget iconButton( - final int id, final ValueStore? valueModel, final LayoutBasePropertyInfo e, final WidgetRef ref) { - final info = e.editInfo!; - final edit = info.editParameter.firstWhere( - (final element) => valueModel != null && element.value == valueModel.currentValue, - orElse: () => info.editParameter.first); + final int id; + final ValueStore? valueModel; + final LayoutBasePropertyInfo info; + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final editInfo = info.editInfo!; + final edit = editInfo.editParameter.firstWhere( + (final element) => + valueModel != null && element.$value == valueModel!.currentValue, + orElse: () => editInfo.editParameter.first, + ); + final raw = edit.extensionData ?? {}; + final color = raw["Color"] as int?; + if (raw["Disable"] == true) return const SizedBox(); return IconButton( onPressed: (() async { - await ref - .read(hubConnectionConnectedProvider) - ?.invoke(info.hubMethod ?? "Update", args: [GenericDevice.getMessage(info, edit, id).toJson()]); + await Device.postMessage( + id, + editInfo, + ref.read(apiProvider), + valueModel?.currentValue, + ); }), - icon: Icon(IconData(edit.raw["CodePoint"] as int, fontFamily: edit.raw["FontFamily"] ?? 'MaterialIcons')), + icon: Icon( + IconData( + raw["CodePoint"] as int, + fontFamily: raw["FontFamily"] ?? 'MaterialIcons', + ), + color: color == null ? null : Color(color), + size: raw["Size"], + ), ); } +} - static Widget buildToggle( - final int id, final ValueStore? valueModel, final LayoutBasePropertyInfo e, final WidgetRef ref) { - final info = e.editInfo!; - final edit = info.editParameter.firstWhere((final element) => element.value != valueModel?.currentValue); - return Switch( - onChanged: ((final _) async { - await ref - .read(hubConnectionConnectedProvider) - ?.invoke(info.hubMethod ?? "Update", args: [GenericDevice.getMessage(info, edit, id).toJson()]); - }), - value: valueModel?.currentValue == info.activeValue, - ); - } +class BasicToggle extends ConsumerWidget { + const BasicToggle({ + super.key, + required this.id, + required this.valueModel, + required this.info, + }); + + final int id; + final ValueStore? valueModel; + final LayoutBasePropertyInfo info; - static Widget buildDropdown( - final int id, final ValueStore? valueModel, final LayoutBasePropertyInfo e, final WidgetRef ref) { - final info = e.editInfo!; - return DropdownButton( - items: info.editParameter - .map((final e) => DropdownMenuItem( - value: e.value, - child: Text(e.displayName ?? e.value.toString()), - )) - .toList(), - onChanged: (final value) async { - final edit = info.editParameter.firstWhere((final element) => element.value == value); - await ref - .read(hubConnectionConnectedProvider) - ?.invoke(info.hubMethod ?? "Update", args: [GenericDevice.getMessage(info, edit, id).toJson()]); - }, - value: valueModel?.currentValue, + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final editInfo = info.editInfo!; + final edit = editInfo.editParameter.firstWhere( + (final element) => element.$value != valueModel?.currentValue, ); - } + if (edit.extensionData?["Disable"] == true) return const SizedBox(); - static void pushTempSettings( - final BuildContext context, final int id, final LayoutBasePropertyInfo e, final WidgetRef ref) async { - final res = await Navigator.push( - context, - MaterialPageRoute>>( - builder: (final BuildContext context) => TempScheduling(id), fullscreenDialog: true)); - if (res == null || !res.item1) return; - final info = e.editInfo!; - final message = Message( - id, MessageType.Options, Command.Temp.index, ["store", ...res.item2.map((final f) => jsonEncode(f)).toList()]); - ref.read(hubConnectionConnectedProvider)?.invoke(info.hubMethod ?? "Update", args: [message.toJson()]); + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + BasicIcon(info: info), + if (edit.displayName != null) Text(edit.displayName!), + Switch( + onChanged: ((final _) async { + await Device.postMessage( + id, + editInfo, + ref.read(apiProvider), + valueModel?.currentValue, + ); + }), + value: valueModel?.currentValue == editInfo.activeValue, + ), + ], + ); } +} - static final _sliderValueProvider = StateProvider.family>((final _, final __) { - return 0.0; +class BasicSlider extends HookConsumerWidget { + const BasicSlider({ + super.key, + required this.id, + required this.valueModel, + required this.info, }); - static Widget buildSlider(final BuildContext context, final int id, final ValueStore? valueModel, - final LayoutBasePropertyInfo e, final WidgetRef ref) { - final info = e.editInfo!; - final edit = info.editParameter.first; - final json = edit.value; - if (json is! Map) return Container(); + + final int id; + final ValueStore valueModel; + final LayoutBasePropertyInfo info; + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final sliderValue = useState(0.0); + final editInfo = info.editInfo!; + final edit = editInfo.editParameter.first; + final json = edit.$value; + final valueModel = this.valueModel; + if (json is! Map) return const SizedBox(); var sliderTheme = SliderTheme.of(context); - if (info.raw.containsKey("GradientColors")) { - final gradients = info.raw["GradientColors"] as List; + if (editInfo.extensionData?.containsKey("GradientColors") ?? false) { + final gradients = + editInfo.extensionData!["GradientColors"] as List; final List colors = []; for (final grad in gradients) { if (grad is int) { @@ -142,13 +272,17 @@ class BasicEditTypes { } } sliderTheme = sliderTheme.copyWith( - trackShape: GradientRoundedRectSliderTrackShape(LinearGradient(colors: colors)), + trackShape: GradientRoundedRectSliderTrackShape( + LinearGradient(colors: colors), + ), ); } + final double sliderVal = valueModel.currentValue is double + ? valueModel.currentValue + : valueModel.currentValue.toDouble(); if (json.containsKey("Divisions") && json.containsKey("Values")) { - final customLabels = (json["Values"]); - final currentValue = ref.watch(_sliderValueProvider(Tuple2(e.name, id))); + final customLabels = json["Values"]; return SliderTheme( data: sliderTheme, child: Slider( @@ -156,50 +290,58 @@ class BasicEditTypes { max: json["Max"] as double, divisions: json["Divisions"], onChanged: (final value) { - ref.read(_sliderValueProvider(Tuple2(e.name, id)).notifier).state = value; + sliderValue.value = value; }, onChangeEnd: (final value) async { - final message = Message(edit.id ?? id, edit.messageType ?? info.editCommand, edit.command, - [customLabels[value.round()].values.first, ...?edit.parameters]); - await ref - .read(hubConnectionConnectedProvider) - ?.invoke(info.hubMethod ?? "Update", args: [message.toJson()]); + await Device.postMessage( + id, + editInfo, + ref.read(apiProvider), + customLabels[value.round()].values.first, + [], + edit.parameters ?? [], + ); }, - value: currentValue, - label: customLabels[currentValue.round()].keys.first, + value: sliderVal, + label: customLabels[sliderValue.value.round()].keys.first, ), ); } - final double sliderVal; - if (valueModel == null) { - sliderVal = 0.0; - } else { - sliderVal = valueModel.currentValue is double ? valueModel.currentValue : valueModel.currentValue.toDouble(); - } - return SliderTheme( - data: sliderTheme, - child: Slider( - min: json["Min"] as double? ?? 0.0, - max: json["Max"] as double? ?? 1.0, - divisions: json["Divisions"] as int?, - onChanged: (final value) { - if (valueModel == null) return; - if (valueModel.currentValue is double) { - valueModel.setValue(value); - } else if (valueModel.currentValue is int) { - valueModel.setValue(value.toInt()); - } - }, - onChangeEnd: (final value) async { - final message = - Message(edit.id ?? id, edit.messageType ?? info.editCommand, edit.command, [value, ...?edit.parameters]); - await ref - .read(hubConnectionConnectedProvider) - ?.invoke(info.hubMethod ?? "Update", args: [message.toJson()]); - }, - value: sliderVal, - label: info.display ?? valueModel?.getValueAsString(precision: e.precision ?? 1) ?? "", - ), + + return Row( + children: [ + BasicIcon(info: info), + SliderTheme( + data: sliderTheme, + child: Slider( + min: json["Min"] as double? ?? 0.0, + max: json["Max"] as double? ?? 1.0, + divisions: json["Divisions"] as int?, + onChanged: (final value) { + if (valueModel.currentValue is double) { + valueModel.value = value; + } else if (valueModel.currentValue is int) { + valueModel.value = value.toInt(); + } + }, + onChangeEnd: (final value) async { + await Device.postMessage( + id, + editInfo, + ref.read(apiProvider), + value, + ); + }, + value: sliderVal, + label: editInfo.display == "" + ? null + : editInfo.display ?? + valueModel.getValueAsString( + precision: info.precision ?? 1, + ), + ), + ), + ], ); } } diff --git a/lib/devices/generic/widgets/edits/edit_helpers.dart b/lib/devices/generic/widgets/edits/edit_helpers.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/devices/generic/widgets/edits/edit_helpers.dart @@ -0,0 +1 @@ + diff --git a/lib/devices/generic/widgets/edits/gauge_edit.dart b/lib/devices/generic/widgets/edits/gauge_edit.dart index 65b87d7..8d98dfb 100644 --- a/lib/devices/generic/widgets/edits/gauge_edit.dart +++ b/lib/devices/generic/widgets/edits/gauge_edit.dart @@ -1,42 +1,158 @@ // ignore_for_file: prefer_final_parameters import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:smarthome/devices/device_exporter.dart'; -import 'package:smarthome/devices/generic/layout_base_property_info.dart'; -import 'package:smarthome/devices/generic/property_edit_information.dart'; import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/devices/generic/stores/value_store.dart'; import 'package:smarthome/helper/connection_manager.dart'; +import 'package:smarthome/restapi/swagger.swagger.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:tuple/tuple.dart'; -class GaugeEdit { - static final _newValueProvider = StateProvider.family>((final ref, final key) { - return 21.0; +class _Displays { + final String label; + final String value; + final String unit; + + _Displays({required this.label, required this.value, required this.unit}); +} + +class GaugeEdit extends HookConsumerWidget { + final int id; + final ValueStore? valueModel; + final LayoutBasePropertyInfo info; + + const GaugeEdit({ + super.key, + required this.id, + required this.valueModel, + required this.info, }); - static Widget getTempGauge(final int id, final BuildContext context, final ValueStore? valueModel, - final LayoutBasePropertyInfo e, final WidgetRef ref) { - final info = e.editInfo; - if (valueModel == null || valueModel.currentValue is! num || info == null) return Container(); - - final startAngle = - (info.raw.containsKey("StartAngle") ? double.tryParse(info.raw["StartAngle"].toString()) : null) ?? 150.0; - final endAngle = - (info.raw.containsKey("EndAngle") ? double.tryParse(info.raw["EndAngle"].toString()) : null) ?? 30.0; - final radiusFactor = - (info.raw.containsKey("RadiusFactor") ? double.tryParse(info.raw["RadiusFactor"].toString()) : null) ?? 0.9; - final minimum = (info.raw.containsKey("Min") ? double.tryParse(info.raw["Min"].toString()) : null) ?? 5.0; - final maximum = (info.raw.containsKey("Max") ? double.tryParse(info.raw["Max"].toString()) : null) ?? 35.0; - final interval = - (info.raw.containsKey("Interval") ? double.tryParse(info.raw["Interval"].toString()) : null) ?? 1.0; - final angle = (info.raw.containsKey("Angle") ? double.tryParse(info.raw["Angle"].toString()) : null) ?? 180.0; - final margin = (info.raw.containsKey("Margin") ? double.tryParse(info.raw["Margin"].toString()) : null) ?? 0; + static Future _handlePointerValueChangedEnd( + final double value, + final int deviceId, + final PropertyEditInformation info, + final WidgetRef ref, + final ValueStore store) async { + _handlePointerValueChanged(value, store); + // final msg = + // GenericDevice.getMessage(info, info.editParameter.first, deviceId); + // msg.parameters = [value, ...msg.parameters ?? []]; + // await ref + // .read(hubConnectionConnectedProvider) + // ?.invoke(info.hubMethod ?? "Update", args: [msg.toJson()]); + + Device.postMessage(deviceId, info, ref.read(apiProvider), value); + } + + static void _handlePointerValueChanged( + final double value, final ValueStore store) { + _setPointerValue(value, store); + } + + static void _handlePointerValueChanging( + final ValueChangingArgs args, final ValueStore store) { + _setPointerValue(args.value, store); + } + + static void _setPointerValue( + final double value, final ValueStore store) { + store.value = (value.clamp(5, 35) * 10).roundToDouble() / 10; + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final editInfo = info.editInfo; + final valueModel = this.valueModel; + if (valueModel == null || + valueModel.currentValue is! num || + editInfo == null) { + return const SizedBox(); + } + useListenable(valueModel); + final raw = editInfo.extensionData ?? {}; + + final startAngle = (raw.containsKey("StartAngle") + ? double.tryParse(raw["StartAngle"].toString()) + : null) ?? + 150.0; + final endAngle = (raw.containsKey("EndAngle") + ? double.tryParse(raw["EndAngle"].toString()) + : null) ?? + 30.0; + final radiusFactor = (raw.containsKey("RadiusFactor") + ? double.tryParse(raw["RadiusFactor"].toString()) + : null) ?? + 0.9; + final minimum = (raw.containsKey("Min") + ? double.tryParse(raw["Min"].toString()) + : null) ?? + 5.0; + final maximum = (raw.containsKey("Max") + ? double.tryParse(raw["Max"].toString()) + : null) ?? + 35.0; + final interval = (raw.containsKey("Interval") + ? double.tryParse(raw["Interval"].toString()) + : null) ?? + 1.0; + final angle = (raw.containsKey("Angle") + ? double.tryParse(raw["Angle"].toString()) + : null) ?? + 180.0; + final margin = (raw.containsKey("Margin") + ? double.tryParse(raw["Margin"].toString()) + : null) ?? + 0; + final minorTickInterval = (raw.containsKey("MinorTickInterval") + ? double.tryParse(raw["MinorTickInterval"].toString()) + : null) ?? + 0; + final tickOffset = (raw.containsKey("TickOffset") + ? double.tryParse(raw["TickOffset"].toString()) + : null) ?? + 0.1; + final thickness = (raw.containsKey("Thickness") + ? double.tryParse(raw["Thickness"].toString()) + : null) ?? + 0.1; + final centerX = (raw.containsKey("CenterX") + ? double.tryParse(raw["CenterX"].toString()) + : null) ?? + 0.5; + final centerY = (raw.containsKey("CenterY") + ? double.tryParse(raw["CenterY"].toString()) + : null) ?? + 0.5; + final labelOffset = (raw.containsKey("LabelOffset") + ? double.tryParse(raw["LabelOffset"].toString()) + : null) ?? + 0.5; + final curValueProp = (raw.containsKey("CurrentValueProp") + ? raw["CurrentValueProp"].toString() + : null); + final showValueAbove = (raw.containsKey("ShowValueAbove") + ? raw["ShowValueAbove"] as bool + : false); + final heightFactor = (raw.containsKey("HeightFactor") + ? double.tryParse(raw["HeightFactor"].toString()) + : null) ?? + 1.0; + + final val = useMemoized(() { + return ref.read(valueStoreChangedProvider( + curValueProp ?? "", + id, + )) ?? + ValueStore(0, 21.0, "", Command.none); + }, [curValueProp]); + useListenable(val); List gradients; - if (info.raw.containsKey("GradientColors")) { - final gradientColor = info.raw["GradientColors"] as List; + if (raw.containsKey("GradientColors")) { + final gradientColor = raw["GradientColors"] as List; gradients = []; for (final grad in gradientColor) { if (grad is int) { @@ -50,8 +166,8 @@ class GaugeEdit { } List stops; - if (info.raw.containsKey("Stops")) { - final stopsInfo = info.raw["Stops"] as List; + if (raw.containsKey("Stops")) { + final stopsInfo = raw["Stops"] as List; stops = []; for (final grad in stopsInfo) { if (grad is double) { @@ -62,9 +178,9 @@ class GaugeEdit { stops = [0.3, 0.5, 1]; } - final List> displays = []; - if (info.raw.containsKey("Displays")) { - final displayInfos = info.raw["Displays"] as List; + final List<_Displays> displays = []; + if (raw.containsKey("Displays")) { + final displayInfos = raw["Displays"] as List; for (final di in displayInfos) { if (di is Map) { String label = ""; @@ -79,158 +195,131 @@ class GaugeEdit { if (di.containsKey("Unit")) { unit = di["Unit"]; } - displays.add(Tuple3(label, value, unit)); + displays.add(_Displays(label: label, value: value, unit: unit)); } } } - final value = valueModel.currentValue as double; - final selectedValue = ref.watch(_newValueProvider(Tuple2(id, valueModel.key))); + final value = (valueModel.currentValue as num).toDouble(); + final selectedValue = val.currentValue; return Column( + mainAxisSize: MainAxisSize.min, children: [ - Container( - margin: const EdgeInsets.only(top: 16.0, bottom: 8.0), - child: Row( + if (showValueAbove) + Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( selectedValue.toStringAsFixed(1), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 28), + style: + const TextStyle(fontWeight: FontWeight.bold, fontSize: 28), ), - (e.unitOfMeasurement == null - ? Container() + (info.unitOfMeasurement == "" + ? const SizedBox() : Text( - e.unitOfMeasurement!, + info.unitOfMeasurement, style: const TextStyle(fontSize: 24), )) ], ), - ), - Container( - margin: const EdgeInsets.only(), - child: SfRadialGauge( - axes: [ - RadialAxis( - startAngle: startAngle, - endAngle: endAngle, - radiusFactor: radiusFactor, - minimum: minimum, - maximum: maximum, - interval: interval, - axisLineStyle: AxisLineStyle( + ClipRect( + child: Align( + alignment: Alignment.topCenter, + heightFactor: heightFactor, + child: SfRadialGauge( + axes: [ + RadialAxis( + startAngle: startAngle, + endAngle: endAngle, + radiusFactor: radiusFactor, + minimum: minimum, + maximum: maximum, + interval: interval, + centerX: centerX, + centerY: centerY, + axisLineStyle: AxisLineStyle( gradient: SweepGradient(colors: gradients, stops: stops), color: Colors.red, - thickness: 0.04, - thicknessUnit: GaugeSizeUnit.factor), - tickOffset: 0.02, - ticksPosition: ElementsPosition.outside, - labelOffset: 0.05, - offsetUnit: GaugeSizeUnit.factor, - showAxisLine: false, - showLabels: false, - labelsPosition: ElementsPosition.outside, - minorTicksPerInterval: 10, - minorTickStyle: const MinorTickStyle(length: 0.1), - majorTickStyle: const MajorTickStyle(length: 0.05, lengthUnit: GaugeSizeUnit.factor), - ), - RadialAxis( - startAngle: startAngle, - endAngle: endAngle, - radiusFactor: radiusFactor, - minimum: minimum, - maximum: maximum, - interval: interval, - axisLineStyle: AxisLineStyle( - gradient: SweepGradient(colors: gradients, stops: stops), - color: Colors.red, - thickness: 0.04, - thicknessUnit: GaugeSizeUnit.factor), - tickOffset: 0.02, - ticksPosition: ElementsPosition.outside, - labelOffset: 0.05, - offsetUnit: GaugeSizeUnit.factor, - onAxisTapped: (final v) => _handlePointerValueChangedEnd(v, id, valueModel.key, info, ref), - labelsPosition: ElementsPosition.outside, - minorTicksPerInterval: 0, - minorTickStyle: const MinorTickStyle(length: 0.1), - majorTickStyle: const MajorTickStyle(length: 0.05, lengthUnit: GaugeSizeUnit.factor), - pointers: [ - MarkerPointer( - value: selectedValue, - elevation: 1, - markerOffset: -20, - markerHeight: 25, - markerWidth: 20, - enableDragging: true, - onValueChanged: (final v) => _handlePointerValueChanged(v, id, valueModel.key, ref), - onValueChangeEnd: (final v) => _handlePointerValueChangedEnd(v, id, valueModel.key, info, ref), - onValueChanging: (final v) => _handlePointerValueChanging(v, id, valueModel.key, ref), - borderColor: Colors.black, - borderWidth: 1, - color: Colors.white, - ), - MarkerPointer( - value: value, - elevation: 10, - markerOffset: 5, - markerType: MarkerType.triangle, - markerHeight: 15, - markerWidth: 15, - color: Colors.red, + thickness: thickness, + thicknessUnit: GaugeSizeUnit.factor, ), - ], - annotations: [ - GaugeAnnotation( - widget: Container( - margin: EdgeInsets.only(bottom: margin), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: displays.map((e) { - if (e.item2 == "") return Text(e.item1); - return Consumer( - builder: (context, ref, child) { - final valStore = ref.watch(valueStoreChangedProvider(Tuple2(e.item2, id))); - if (valStore == null) return Container(); - - if (e.item1 == "") return Text(valStore.getValueAsString() + e.item3); - return Text(e.item1 + valStore.getValueAsString() + e.item3); - }, - ); - }).toList(), - ), - ), - angle: angle) - ], - ), - ], + tickOffset: tickOffset, + ticksPosition: ElementsPosition.outside, + labelOffset: labelOffset, + labelFormat: '{value}${info.unitOfMeasurement}', + offsetUnit: GaugeSizeUnit.factor, + onAxisTapped: (final v) => + _handlePointerValueChangedEnd(v, id, editInfo, ref, val), + labelsPosition: ElementsPosition.outside, + minorTicksPerInterval: minorTickInterval, + minorTickStyle: const MinorTickStyle(length: 0.1), + majorTickStyle: const MajorTickStyle(length: 6), + pointers: [ + MarkerPointer( + value: (selectedValue as num).toDouble(), + elevation: 1, + markerOffset: -20, + markerHeight: 25, + markerWidth: 20, + enableDragging: true, + onValueChanged: (final v) => + _handlePointerValueChanged(v, val), + onValueChangeEnd: (final v) => + _handlePointerValueChangedEnd( + v, id, editInfo, ref, val), + onValueChanging: (final v) => + _handlePointerValueChanging(v, val), + borderColor: Colors.black, + borderWidth: 1, + color: Colors.white, + ), + MarkerPointer( + value: value, + elevation: 10, + markerOffset: 5, + markerType: MarkerType.triangle, + markerHeight: 15, + markerWidth: 15, + color: Colors.red, + ), + ], + // annotations: [ + // GaugeAnnotation( + // widget: Container( + // margin: EdgeInsets.only(bottom: margin), + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: displays.map((e) { + // return Text("Test"); + // if (e.value == "") return Text(e.label); + // return HookConsumer( + // builder: (context, ref, child) { + // final valStore = ref.watch( + // valueStoreChangedProvider(e.value, id)); + // if (valStore == null) return const SizedBox(); + // useListenable(valStore); + // if (e.label == "") { + // return Text( + // valStore.getValueAsString() + e.unit); + // } + // return Text(e.label + + // valStore.getValueAsString() + + // e.unit); + // }, + // ); + // }).toList(), + // ), + // ), + // angle: angle, + // ), + // ], + ), + ], + ), ), ), ], ); } - - static void _handlePointerValueChanged( - final double value, final int deviceId, final String key, final WidgetRef ref) { - _setPointerValue(value, deviceId, key, ref); - } - - static Future _handlePointerValueChangedEnd(final double value, final int deviceId, final String key, - final PropertyEditInformation info, final WidgetRef ref) async { - _handlePointerValueChanged(value, deviceId, key, ref); - - final msg = GenericDevice.getMessage(info, info.editParameter.first, deviceId); - msg.parameters = [value, ...msg.parameters ?? []]; - await ref.read(hubConnectionConnectedProvider)?.invoke(info.hubMethod ?? "Update", args: [msg.toJson()]); - } - - static void _handlePointerValueChanging( - final ValueChangingArgs args, final int deviceId, final String key, final WidgetRef ref) { - _setPointerValue(args.value, deviceId, key, ref); - } - - static void _setPointerValue(final double value, final int deviceId, final String key, final WidgetRef ref) { - final curValue = ref.read(_newValueProvider(Tuple2(deviceId, key)).notifier); - curValue.state = (value.clamp(5, 35) * 10).roundToDouble() / 10; - } } diff --git a/lib/devices/generic/widgets/history_series_annotation_chart_widget.dart b/lib/devices/generic/widgets/history_series_annotation_chart_widget.dart index 7ea408e..55504d4 100644 --- a/lib/devices/generic/widgets/history_series_annotation_chart_widget.dart +++ b/lib/devices/generic/widgets/history_series_annotation_chart_widget.dart @@ -11,10 +11,9 @@ class HistorySeriesAnnotationChartWidget extends StatelessWidget { final DateTime shownDate; final LoadMoreViewBuilderCallback? loadMoreIndicatorBuilder; - const HistorySeriesAnnotationChartWidget( - this.seriesList, this.min, this.max, this.unit, this.valueName, this.shownDate, - {final Key? key, this.loadMoreIndicatorBuilder}) - : super(key: key); + const HistorySeriesAnnotationChartWidget(this.seriesList, this.min, this.max, + this.unit, this.valueName, this.shownDate, + {super.key, this.loadMoreIndicatorBuilder}); @override Widget build(final BuildContext context) { @@ -27,9 +26,12 @@ class HistorySeriesAnnotationChartWidget extends StatelessWidget { majorGridLines: const MajorGridLines(width: 0), title: AxisTitle(text: DateFormat("dd.MM.yyyy").format(shownDate))), primaryYAxis: NumericAxis( - minimum: (min - (((max - min) < 10 ? 10 : (max - min)) / 10)).roundToDouble(), - maximum: (max + (((max - min) < 10 ? 10 : (max - min)) / 10)).roundToDouble(), - interval: (((max - min) < 10 ? 10 : (max - min)) / 10).roundToDouble(), + minimum: (min - (((max - min) < 10 ? 10 : (max - min)) / 10)) + .roundToDouble(), + maximum: (max + (((max - min) < 10 ? 10 : (max - min)) / 10)) + .roundToDouble(), + interval: + (((max - min) < 10 ? 10 : (max - min)) / 10).roundToDouble(), axisLine: const AxisLine(width: 0), labelFormat: '{value}$unit', majorTickLines: const MajorTickLines(size: 0), @@ -38,7 +40,8 @@ class HistorySeriesAnnotationChartWidget extends StatelessWidget { trackballBehavior: TrackballBehavior( enable: true, activationMode: ActivationMode.singleTap, - tooltipSettings: const InteractiveTooltip(format: '{point.x} : {point.y}'), + tooltipSettings: + const InteractiveTooltip(format: '{point.x} : {point.y}'), ), zoomPanBehavior: ZoomPanBehavior( /// To enable the pinch zooming as true. diff --git a/lib/devices/generic_device.dart b/lib/devices/generic_device.dart index aad74f3..c7de1d1 100644 --- a/lib/devices/generic_device.dart +++ b/lib/devices/generic_device.dart @@ -1,31 +1,21 @@ -import 'package:adaptive_theme/adaptive_theme.dart'; + import 'package:flutter/material.dart'; -import 'package:smarthome/controls/blurry_card.dart'; -import 'package:smarthome/devices/base_model.dart'; -import 'package:smarthome/devices/device.dart'; +import 'package:smarthome/devices/device_exporter.dart'; import 'package:smarthome/devices/device_manager.dart'; import 'package:smarthome/devices/generic/device_layout_service.dart'; -import 'package:smarthome/devices/generic/edit_parameter.dart'; import 'package:smarthome/devices/generic/generic_device_exporter.dart'; -import 'package:smarthome/devices/generic/layout_base_property_info.dart'; -import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/devices/generic/stores/value_store.dart'; +import 'package:smarthome/devices/generic/widgets/edits/advanced_slider.dart'; import 'package:smarthome/devices/generic/widgets/edits/basic_edit_types.dart'; import 'package:smarthome/devices/generic/widgets/edits/gauge_edit.dart'; -import 'package:smarthome/devices/zigbee/iobroker_history_model.dart'; -import 'package:smarthome/helper/connection_manager.dart'; +import 'package:smarthome/devices/generic_device_screen.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; -import 'package:smarthome/helper/settings_manager.dart'; +import 'package:smarthome/helper/notification_service.dart'; import 'package:smarthome/models/message.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:tuple/tuple.dart'; -import 'package:visibility_detector/visibility_detector.dart'; - -import '../helper/theme_manager.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; class GenericDevice extends Device { - GenericDevice(final int id, final String typeName) : super(id, typeName); + GenericDevice(super.id, super.typeName); @override DeviceTypes getDeviceType() { @@ -34,7 +24,11 @@ class GenericDevice extends Device { @override void navigateToDevice(final BuildContext context) { - Navigator.push(context, MaterialPageRoute(builder: (final BuildContext context) => GenericDeviceScreen(this))); + Navigator.push( + context, + MaterialPageRoute( + builder: (final BuildContext context) => + GenericDeviceScreen(this))); } @override @@ -42,9 +36,10 @@ class GenericDevice extends Device { return Consumer( builder: (final context, final ref, final child) { final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel == null) return Container(); - final dashboardDeviceLayout = ref.watch(dashboardNoSpecialTypeLayoutProvider(Tuple2(id, baseModel.typeName))); - if (dashboardDeviceLayout?.isEmpty ?? true) return Container(); + if (baseModel == null) return const SizedBox(); + final dashboardDeviceLayout = ref.watch( + dashboardNoSpecialTypeLayoutProvider(id, baseModel.typeName)); + if (dashboardDeviceLayout?.isEmpty ?? true) return const SizedBox(); return DashboardLayoutWidget(this, dashboardDeviceLayout!); }, @@ -57,508 +52,148 @@ class GenericDevice extends Device { builder: (final context, final ref, final child) { final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel == null) return Container(); + if (baseModel == null) return const SizedBox(); - final properties = ref.watch(dashboardSpecialTypeLayoutProvider(Tuple2(id, baseModel.typeName))); - if (properties?.isEmpty ?? true) return Container(); + final properties = ref + .watch(dashboardSpecialTypeLayoutProvider(id, baseModel.typeName)); + if (properties?.isEmpty ?? true) return const SizedBox(); properties!.sort((final a, final b) => a.order.compareTo(b.order)); return Column( - children: properties.map((final e) => DashboardRightValueStoreWidget(e, this)).toList(), + children: properties + .map((final e) => DashboardRightValueStoreWidget(e, this)) + .toList(), ); }, ); } - Widget getEditWidget(final BuildContext context, final LayoutBasePropertyInfo e, final ValueStore? valueModel, - final WidgetRef ref) => - GenericDevice.getEditWidgetFor(context, id, e, valueModel, ref); - - static Widget getEditWidgetFor(final BuildContext context, final int id, final LayoutBasePropertyInfo e, - final ValueStore? valueModel, final WidgetRef ref) { - switch (e.editInfo?.editType) { - case EditType.button: - case EditType.raisedButton: - return BasicEditTypes.buildButton( - id, context, valueModel, e, ref, e.editInfo!.editType == EditType.raisedButton); - case EditType.toggle: - return BasicEditTypes.buildToggle(id, valueModel, e, ref); - case EditType.dropdown: - return BasicEditTypes.buildDropdown(id, valueModel, e, ref); - case EditType.slider: - return BasicEditTypes.buildSlider(context, id, valueModel, e, ref); - case EditType.iconButton: - return BasicEditTypes.iconButton(id, valueModel, e, ref); - case EditType.icon: - return BasicEditTypes.icon(valueModel, e, ref); - case EditType.radial: - return GaugeEdit.getTempGauge(id, context, valueModel, e, ref); + @override + Future iconPressed(final BuildContext context, final WidgetRef ref) async { + final baseModel = ref.watch(BaseModel.byIdProvider(id)); + if (baseModel == null) return; + final layout = + ref.read(deviceLayoutsProvider.notifier).getLayout(id, typeName); + if (layout == null) return; + final notifications = layout.notificationSetup?.where((final x) => + !x.global && + (x.deviceIds == null || + x.deviceIds!.isEmpty || + x.deviceIds!.contains(id))); + if (notifications == null) return; + ref.read(notificationServiceProvider.notifier).showNotificationDialog( + context, + notifications.map((final x) => (baseModel.friendlyName, id, x)).toList()); + } + + Widget getEditWidget( + final LayoutBasePropertyInfo e, final ValueStore valueModel) => + GenericDevice.getEditWidgetFor(id, e, valueModel); + + static Widget getEditWidgetFor(final int id, final LayoutBasePropertyInfo e, + final ValueStore valueModel) { + switch (e.editInfo?.editType.toLowerCase()) { + case "button": + case "raisedbutton": + return BasicButton( + id: id, + valueModel: valueModel, + info: e, + raisedButton: e.editInfo!.editType.toLowerCase() == "raisedbutton"); + // case "buttionwithicon": + // return ButtonWithIcon( + // id: id, + // valueModel: valueModel, + // info: e, + // raisedButton: e.editInfo!.editType.toLowerCase() == "raisedbutton"); + case "toggle": + return BasicToggle(id: id, valueModel: valueModel, info: e); + case "dropdown": + return BasicDropdown(id: id, valueModel: valueModel, info: e); + case "slider": + return BasicSlider(id: id, valueModel: valueModel, info: e); + case "advancedslider": + return AdvancedSlider(id, valueModel, info: e); + case "iconbutton": + return BasicIconButton(id: id, valueModel: valueModel, info: e); + case "icon": + return BasicIcon(info: e); + case "radial": + return GaugeEdit(id: id, valueModel: valueModel, info: e); // case EditType.input: // return _buildInput(valueModel, e, ref); //https://github.com/mchome/flutter_colorpicker //FAB - case EditType.floatingActionButton: - return Container(); + case "floatingactionbutton": + return const SizedBox(); default: + //BasicIcon(info: e) + return Text( - (valueModel?.getValueAsString(format: e.format, precision: e.precision ?? 1) ?? "") + - (e.unitOfMeasurement ?? ""), - style: e.textStyle?.toTextStyle(), + (valueModel.getValueAsString( + format: e.format, + precision: e.precision ?? 1, + asHex: e.extensionData?["Hex"] ?? false)) + + (e.unitOfMeasurement), + style: toTextStyle(e.textStyle), + maxLines: 100, + softWrap: true, + overflow: TextOverflow.clip, ); } } - static Message getMessage(final PropertyEditInformation info, final EditParameter edit, final int id) { - return Message(edit.id ?? id, edit.messageType ?? info.editCommand, edit.command, edit.parameters); + static TextStyle toTextStyle(final TextSettings? setting) { + var ts = const TextStyle(); + if (setting == null) return ts; + if (setting.fontSize != null) ts = ts.copyWith(fontSize: setting.fontSize); + if (setting.fontFamily != "") { + ts = ts.copyWith(fontFamily: setting.fontFamily); + } + ts = ts.copyWith(fontStyle: FontStyle.values[setting.fontStyle.index - 1]); + ts = ts.copyWith( + fontWeight: setting.fontWeight == FontWeightSetting.bold + ? FontWeight.bold + : FontWeight.normal); + return ts; + } + + static Message getMessage(final PropertyEditInformation info, + final EditParameter edit, final int id) { + return Message(edit.id ?? id, edit.messageType ?? info.messageType, + edit.command, edit.parameters); } - static EditParameter getEditParameter(final ValueStore? valueModel, final PropertyEditInformation info) { - if (valueModel == null) return info.editParameter.first; + static EditParameter? getEditParameter(final ValueStore? valueModel, + final PropertyEditInformation info, final String name) { + if (valueModel == null) { + return info.editParameter + .firstOrDefault((final x) => x.extensionData?['Name'] == name); + } if (valueModel.currentValue is num) { final val = (valueModel.currentValue as num); - return info.editParameter.firstWhere((final element) { - final lower = element.raw["Min"] as num?; - final upper = element.raw["Max"] as num?; + return info.editParameter.firstOrDefault((final element) { + final lower = element.extensionData?["Min"] as num?; + final upper = element.extensionData?["Max"] as num?; if (lower != null && upper != null) { return val >= lower && val < upper; } - return element.value == val; - }, orElse: () => info.editParameter.first); + return element.$value == val; + }); } else if (valueModel.currentValue is bool) { final val = (valueModel.currentValue as bool); - return info.editParameter.firstWhere((final element) { - return element.value == val; - }, orElse: () => info.editParameter.first); + return info.editParameter.firstOrDefault((final element) { + return element.$value == val; + }); } else if (valueModel.currentValue is String) { final val = (valueModel.currentValue as String); - return info.editParameter.firstWhere((final element) { - return element.value == val; - }, orElse: () => info.editParameter.first); + return info.editParameter.firstOrDefault((final element) { + return element.$value == val; + }); } else { - return info.editParameter.first; - } - } -} - -class GenericDeviceScreen extends ConsumerStatefulWidget { - final GenericDevice genericDevice; - const GenericDeviceScreen(this.genericDevice, {final Key? key}) : super(key: key); - - @override - GenericDeviceScreenState createState() => GenericDeviceScreenState(); -} - -class GenericDeviceScreenState extends ConsumerState { - final _currentShownTimeProvider = StateProvider((final _) => DateTime.now()); - - final _currentShownTabProvider = StateProvider.family>((final ref, final _) { - return false; - }); - final _tabKeyProvider = StateProvider.autoDispose.family>((final ref, final key) { - return Key(key.toString()); - }); - GenericDeviceScreenState(); - - @override - Widget build(final BuildContext context) { - final name = ref.watch(BaseModel.typeNameProvider(widget.genericDevice.id)); - final historyProps = ref.watch(detailHistoryLayoutProvider(Tuple2(widget.genericDevice.id, name))); - final tabInfos = ref.watch(detailTabInfoLayoutProvider(Tuple2(widget.genericDevice.id, name))); - - if (historyProps?.isEmpty ?? true) return buildWithoutHistory(context, tabInfos); - return buildWithHistory(context, historyProps!, tabInfos); - } - - Widget buildWithoutHistory(final BuildContext context, final List? tabInfos) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(widget.genericDevice.id)); - if (tabInfos == null || tabInfos.isEmpty || tabInfos.length == 1) { - return Scaffold( - floatingActionButton: _buildFab(), - appBar: AppBar( - title: Text(friendlyName), - ), - body: buildBody(tabInfos?.first)); + return info.editParameter + .firstOrDefault((final x) => x.extensionData?['Name'] == name); } - return DefaultTabController( - length: tabInfos.length, - child: Scaffold( - floatingActionButton: _buildFab(), - appBar: AppBar( - title: TabBar( - tabs: tabInfos.map((final e) { - final icon = ref.watch(iconWidgetSingleProvider( - Tuple4(e.iconName, widget.genericDevice, AdaptiveTheme.of(context), true))); - return Tab(icon: icon); - }).toList(), - ), - ), - body: TabBarView(children: tabInfos.map((final e) => buildBody(e)).toList(growable: false)))); - } - - Widget buildWithHistory( - final BuildContext context, final List histProps, final List? tabInfos) { - if (tabInfos == null || tabInfos.isEmpty) { - return DefaultTabController( - length: histProps.length + 1, - child: Scaffold( - floatingActionButton: _buildFab(), - appBar: AppBar( - title: TabBar( - tabs: histProps - .map((final e) { - final icon = ref.watch(iconWidgetSingleProvider( - Tuple4(e.iconName, widget.genericDevice, AdaptiveTheme.of(context), true))); - return Tab(icon: icon); - }) - .injectForIndex((final index) => index == 0 ? const Tab(icon: Icon(Icons.home)) : null) - .toList(), - ), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: TabBarView( - children: histProps - .map((final e) => buildGraph(e)) - .injectForIndex((final index) => index == 0 ? buildBody() : null) - .toList(), - ), - ), - ), - ); - } - return DefaultTabController( - length: histProps.length + tabInfos.length, - child: Scaffold( - floatingActionButton: _buildFab(), - appBar: AppBar( - title: TabBar( - tabs: [ - ...tabInfos.map((final e) { - final icon = ref.watch(iconWidgetSingleProvider( - Tuple4(e.iconName, widget.genericDevice, AdaptiveTheme.of(context), true))); - return Tab(icon: icon); - }), - ...histProps.map((final e) { - final icon = ref.watch(iconWidgetSingleProvider( - Tuple4(e.iconName, widget.genericDevice, AdaptiveTheme.of(context), true))); - return Tab(icon: icon); - }), - ].toList(), - ), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: TabBarView( - children: - [...tabInfos.map((final e) => buildBody(e)), ...histProps.map((final e) => buildGraph(e))].toList(), - ), - ), - ), - ); - } - - Widget? _buildFab() { - final name = ref.watch(BaseModel.typeNameProvider(widget.genericDevice.id)); - final fabLayout = ref.watch(fabLayoutProvider(Tuple2(widget.genericDevice.id, name))); - if (fabLayout == null) return null; - - final valueModel = - ref.watch(valueStoreChangedProvider(Tuple2(fabLayout.name, fabLayout.deviceId ?? widget.genericDevice.id))); - if (valueModel == null) return null; - // final showDebugInformation = ref.watch(debugInformationEnabledProvider); - - final info = fabLayout.editInfo!; - final EditParameter edit; - edit = GenericDevice.getEditParameter(valueModel, info); - final message = Message( - edit.id ?? widget.genericDevice.id, edit.messageType ?? info.editCommand, edit.command, edit.parameters); - - return FloatingActionButton( - onPressed: (() async { - await ref - .read(hubConnectionConnectedProvider) - ?.invoke(info.hubMethod ?? "Update", args: [message.toJson()]); - }), - child: Icon( - IconData(edit.raw["CodePoint"] as int, fontFamily: edit.raw["FontFamily"] ?? 'MaterialIcons'), - ), - ); - } - - final emptyContainer = Container(); - Widget buildBody([final DetailTabInfo? tabInfo]) { - final name = ref.watch(BaseModel.typeNameProvider(widget.genericDevice.id)); - var detailProperties = ref.watch(detailPropertyInfoLayoutProvider(Tuple2(widget.genericDevice.id, name))); - final showDebugInformation = ref.watch(debugInformationEnabledProvider); - if (detailProperties == null || detailProperties.isEmpty) return Container(); - detailProperties = detailProperties - .where((final element) => tabInfo == null || tabInfo.id == element.tabInfoId) - .toList(growable: false); - // final widgets = []; - detailProperties.sort((final a, final b) => a.order.compareTo(b.order)); - - // for (final detProp in detailProperties) { - // widgets.add(DetailValueStoreWidget(detProp, widget.genericDevice)); - // } - // return ListView( - // children: widgets, - // ); - final props = detailProperties - .where((final e) => - !(e.showOnlyInDeveloperMode ?? false) || (showDebugInformation && e.showOnlyInDeveloperMode == true)) - .where((final e) => e.editInfo == null || e.editInfo!.editType != EditType.floatingActionButton) - .groupBy((final g) => g.rowNr) - .map((final row, final elements) { - final children = elements.map((final e) { - final exp = e.expanded; - if (exp == null || !exp) return DetailValueStoreWidget(e, widget.genericDevice); - return Expanded(child: DetailValueStoreWidget(e, widget.genericDevice)); - }).toList(growable: false); - - final displayBlurry = elements.any(((final element) => element.blurryCard == true)); - return MapEntry( - row, - ListTile( - leading: ref.watch(debugInformationEnabledProvider) ? Text("Row: $row") : null, - title: displayBlurry - ? BlurryCard( - // margin: const EdgeInsets.only(left: 8, right: 8), - child: Container( - margin: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4), - child: Row(children: children), - ), - ) - : Row( - // runAlignment: WrapAlignment.spaceBetween, - // spacing: 8, - children: children, - ), - ), - // ), - ); - }); - - return ListView( - children: props.values.toList(), - ); - } - - Widget buildGraph(final HistoryPropertyInfo info) { - final currentShownTime = ref.watch(_currentShownTimeProvider); - final showBody = ref.watch(_currentShownTabProvider(Tuple2(widget.genericDevice.id, info.propertyName))); - return VisibilityDetector( - key: ref.watch(_tabKeyProvider(Tuple2(widget.genericDevice.id, info.propertyName))), - child: !showBody - ? Container() - : ref - .watch(historyPropertyNameProvider( - Tuple3(widget.genericDevice.id, currentShownTime.toString(), info.propertyName))) - .when( - data: (final data) { - if (data.historyRecords.isNotEmpty) { - return buildHistorySeriesAnnotationChart( - data, - info, - AdaptiveTheme.of(context).brightness == Brightness.light - ? Color(info.brightThemeColor) - : Color(info.darkThemeColor), - currentShownTime); - } - return buildDataMissing(currentShownTime); - }, - error: (final e, final o) => Text(e.toString()), - loading: () => Column( - children: [ - Container( - margin: const EdgeInsets.only(top: 50), - child: Text( - 'Lade weitere History Daten...', - style: Theme.of(context).textTheme.titleLarge, - ), - ), - Container( - margin: const EdgeInsets.only(top: 25), - child: const CircularProgressIndicator(), - ), - ], - ), - ), - onVisibilityChanged: (final i) { - try { - ref.read(_currentShownTabProvider(Tuple2(widget.genericDevice.id, info.propertyName)).notifier).state = - !i.visibleBounds.isEmpty; - } catch (e) { - //Happends in debug when exiting in the history tab - } - }); - } - - Widget buildDataMissing(final DateTime currentShownTime) { - return Flex( - direction: Axis.vertical, - children: [ - Expanded( - child: Text( - "Daten werden geladen oder sind nicht vorhanden für ${currentShownTime.day}.${currentShownTime.month}.${currentShownTime.year}")), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: MaterialButton( - child: const Text("Früher"), - onPressed: () { - getNewData(currentShownTime.subtract(const Duration(days: 1))); - }, - )), - Expanded( - child: MaterialButton( - onPressed: () => _openDatePicker(currentShownTime), child: const Text('Datum auswählen')), - ), - Expanded( - child: MaterialButton( - child: const Text("Später"), - onPressed: () { - getNewData(currentShownTime.add(const Duration(days: 1))); - }, - )), - ], - ) - ], - ); - } - - Widget buildHistorySeriesAnnotationChart( - final HistoryModel h, final HistoryPropertyInfo info, final Color lineColor, final DateTime currentShownTime) { - h.historyRecords = h.historyRecords.where((final x) => x.value != null).toList(growable: false); - final chartType = info.chartType; - final dynamic seriesList = _getSeriesList(chartType, h, lineColor); - return Flex( - direction: Axis.vertical, - children: [ - Expanded( - child: HistorySeriesAnnotationChartWidget( - seriesList, - h.historyRecords - .where((final x) => x.value != null) - .map((final x) => x.value!) - .minBy(10000, (final e) => e) - .toDouble(), - h.historyRecords - .where((final x) => x.value != null) - .map((final x) => x.value!) - .maxBy(0, (final e) => e) - .toDouble(), - info.unitOfMeasurement, - info.xAxisName, - currentShownTime, - loadMoreIndicatorBuilder: (final context, final direction) { - return Consumer( - builder: (final context, final ref, final child) { - Future.delayed(const Duration(milliseconds: 10), (() { - final current = ref.read(_currentShownTimeProvider.notifier); - if (direction == ChartSwipeDirection.end) { - if (current.state.day < DateTime.now().day) { - current.state = current.state.add(const Duration(days: 1)); - } - } else { - current.state = current.state.add(const Duration(days: -1)); - } - })); - return Container(); - }, - ); - }, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: MaterialButton( - child: const Text("Früher"), - onPressed: () { - getNewData(currentShownTime.subtract(const Duration(days: 1))); - }, - )), - Expanded( - child: MaterialButton( - onPressed: () => _openDatePicker(currentShownTime), child: const Text('Datum auswählen')), - ), - currentShownTime.isAfter(DateTime.now().add(const Duration(hours: -23))) - ? Expanded( - child: Container(), - ) - : Expanded( - child: MaterialButton( - child: const Text("Später"), - onPressed: () { - getNewData(currentShownTime.add(const Duration(days: 1))); - }, - )), - ], - ) - ], - ); - } - - dynamic _getSeriesList(final String charType, final HistoryModel h, final Color lineColor) { - switch (charType) { - case "step": - return [ - StepLineSeries( - enableTooltip: true, - animationDuration: 500, - // markerSettings: MarkerSettings(shape: DataMarkerType.circle, color: Colors.green, width: 5, height: 5, isVisible: true), - markerSettings: const MarkerSettings( - isVisible: true, - shape: DataMarkerType.circle, - ), - dataSource: h.historyRecords - .map((final x) => - GraphTimeSeriesValue(DateTime(1970).add(Duration(milliseconds: x.timeStamp)), x.value, lineColor)) - .toList(), - xValueMapper: (final GraphTimeSeriesValue value, final _) => value.time, - yValueMapper: (final GraphTimeSeriesValue value, final _) => value.value, - pointColorMapper: (final GraphTimeSeriesValue value, final _) => value.lineColor, - width: 2) - ]; - default: - return [ - LineSeries( - enableTooltip: true, - animationDuration: 500, - // markerSettings: MarkerSettings(shape: DataMarkerType.circle, color: Colors.green, width: 5, height: 5, isVisible: true), - markerSettings: const MarkerSettings( - isVisible: true, - shape: DataMarkerType.circle, - ), - dataSource: h.historyRecords - .map((final x) => - GraphTimeSeriesValue(DateTime(1970).add(Duration(milliseconds: x.timeStamp)), x.value, lineColor)) - .toList(), - xValueMapper: (final GraphTimeSeriesValue value, final _) => value.time, - yValueMapper: (final GraphTimeSeriesValue value, final _) => value.value, - pointColorMapper: (final GraphTimeSeriesValue value, final _) => value.lineColor, - width: 2) - ]; - } - } - - void _openDatePicker(final DateTime initial) { - // showDatePicker is a pre-made funtion of Flutter - showDatePicker(context: context, initialDate: initial, firstDate: DateTime(2018), lastDate: DateTime.now()) - .then((final pickedDate) { - // Check if no date is selected - if (pickedDate == null) { - return; - } - getNewData(pickedDate); - }); - } - - getNewData(final DateTime dt) { - if (dt.millisecondsSinceEpoch > DateTime.now().millisecondsSinceEpoch) return; - ref.read(_currentShownTimeProvider.notifier).state = dt; } } diff --git a/lib/devices/generic_device_screen.dart b/lib/devices/generic_device_screen.dart new file mode 100644 index 0000000..7ce631c --- /dev/null +++ b/lib/devices/generic_device_screen.dart @@ -0,0 +1,489 @@ +import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:smarthome/controls/blurry_card.dart'; +import 'package:smarthome/devices/device_exporter.dart'; +import 'package:smarthome/devices/generic/device_layout_service.dart'; +import 'package:smarthome/devices/generic/generic_device_exporter.dart'; +import 'package:smarthome/devices/generic/stores/store_service.dart'; +import 'package:smarthome/helper/connection_manager.dart'; +import 'package:smarthome/helper/iterable_extensions.dart'; +import 'package:smarthome/helper/settings_manager.dart'; +import 'package:smarthome/helper/theme_manager.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:visibility_detector/visibility_detector.dart'; + +class GenericDeviceScreen extends HookConsumerWidget { + final GenericDevice genericDevice; + const GenericDeviceScreen(this.genericDevice, {super.key}); + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final name = ref.watch(BaseModel.typeNameProvider(genericDevice.id)); + final historyProps = + ref.watch(detailHistoryLayoutProvider(genericDevice.id, name)); + final tabInfos = + ref.watch(detailTabInfoLayoutProvider(genericDevice.id, name)); + + if (historyProps?.isEmpty ?? true) { + return buildWithoutHistory(context, ref, tabInfos); + } + return buildWithHistory(context, ref, historyProps!, tabInfos); + } + + Widget buildWithoutHistory(final BuildContext context, final WidgetRef ref, + final List? tabInfos) { + final friendlyName = + ref.watch(BaseModel.friendlyNameProvider(genericDevice.id)); + if (tabInfos == null || tabInfos.isEmpty || tabInfos.length == 1) { + return Scaffold( + floatingActionButton: _GenericDeviceFab(genericDevice: genericDevice), + appBar: AppBar( + title: Text(friendlyName), + ), + body: buildBody(ref, tabInfos?.firstOrNull)); + } + return DefaultTabController( + length: tabInfos.length, + child: Scaffold( + floatingActionButton: + _GenericDeviceFab(genericDevice: genericDevice), + appBar: AppBar( + title: TabBar( + tabs: tabInfos.map((final e) { + final icon = ref.watch(iconWidgetSingleProvider(e.iconName, + genericDevice, AdaptiveTheme.of(context), true)); + return Tab(icon: icon); + }).toList(), + ), + ), + body: TabBarView( + children: tabInfos + .map((final e) => buildBody(ref, e)) + .toList(growable: false)))); + } + + Widget buildWithHistory( + final BuildContext context, + final WidgetRef ref, + final List histProps, + final List? tabInfos) { + if (tabInfos == null || tabInfos.isEmpty) { + return DefaultTabController( + length: histProps.length + 1, + child: Scaffold( + floatingActionButton: _GenericDeviceFab(genericDevice: genericDevice), + appBar: AppBar( + title: TabBar( + tabs: histProps + .map((final e) { + final icon = ref.watch(iconWidgetSingleProvider(e.iconName, + genericDevice, AdaptiveTheme.of(context), true)); + return Tab(icon: icon); + }) + .injectForIndex((final index) => + index == 0 ? const Tab(icon: Icon(Icons.home)) : null) + .toList(), + ), + ), + body: Container( + decoration: ThemeManager.getBackgroundDecoration(context), + child: TabBarView( + children: histProps + .map((final e) => buildGraph(context, ref, e)) + .injectForIndex( + (final index) => index == 0 ? buildBody(ref) : null) + .toList(), + ), + ), + ), + ); + } + return DefaultTabController( + length: histProps.length + tabInfos.length, + child: Scaffold( + floatingActionButton: _GenericDeviceFab(genericDevice: genericDevice), + appBar: AppBar( + title: TabBar( + tabs: [ + ...tabInfos.map((final e) { + final icon = ref.watch(iconWidgetSingleProvider(e.iconName, + genericDevice, AdaptiveTheme.of(context), true)); + return Tab(icon: icon); + }), + ...histProps.map((final e) { + final icon = ref.watch(iconWidgetSingleProvider(e.iconName, + genericDevice, AdaptiveTheme.of(context), true)); + return Tab(icon: icon); + }), + ].toList(), + ), + ), + body: Container( + decoration: ThemeManager.getBackgroundDecoration(context), + child: TabBarView( + children: [ + ...tabInfos.map((final e) => buildBody(ref, e)), + ...histProps.map((final e) => buildGraph(context, ref, e)) + ].toList(), + ), + ), + ), + ); + } + + final emptyContainer = const SizedBox(); + Widget buildBody(final WidgetRef ref, [final DetailTabInfo? tabInfo]) { + final name = ref.watch(BaseModel.typeNameProvider(genericDevice.id)); + var detailProperties = + ref.watch(detailPropertyInfoLayoutProvider(genericDevice.id, name)); + final showDebugInformation = ref.watch(debugInformationEnabledProvider); + if (detailProperties == null || detailProperties.isEmpty) { + return const SizedBox(); + } + detailProperties = detailProperties + .where((final element) => + tabInfo == null || tabInfo.id == element.tabInfoId) + .toList(growable: false); + // final widgets = []; + detailProperties.sort( + (final a, final b) => (a.rowNr ?? 0).compareTo(b.rowNr ?? 0xFFFF)); + // for (final detProp in detailProperties) { + // widgets.add(DetailValueStoreWidget(detProp, genericDevice)); + // } + // return ListView( + // children: widgets, + // ); + final props = detailProperties + .where((final e) => + !(e.showOnlyInDeveloperMode ?? false) || + (showDebugInformation && e.showOnlyInDeveloperMode == true)) + .where((final e) => + e.editInfo == null || + e.editInfo!.editType.toLowerCase() != "floatingactionbutton") + .groupBy((final g) => g.rowNr) + .map((final row, final elements) { + final children = elements.map((final e) { + final exp = e.expanded; + final widget = DetailValueStoreWidget(e, genericDevice); + if (exp == null || !exp) { + return widget; + } + return Expanded(child: widget); + }).toList(growable: false); + + final displayBlurry = + elements.any(((final element) => element.blurryCard == true)); + return MapEntry( + row, + ListTile( + leading: ref.watch(debugInformationEnabledProvider) + ? Text("Row: $row") + : null, + title: displayBlurry + ? BlurryCard( + // margin: const EdgeInsets.only(left: 8, right: 8), + child: Container( + margin: const EdgeInsets.only( + left: 8, right: 8, top: 4, bottom: 4), + child: Row(children: children), + ), + ) + : Row( + // runAlignment: WrapAlignment.spaceBetween, + // spacing: 8, + children: children, + ), + ), + // ), + ); + }); + + return ListView( + children: props.values.toList(), + ); + } + + Widget buildGraph(final BuildContext context, final WidgetRef ref, + final HistoryPropertyInfo info) { + final currentShownTime = useState(DateTime.now()); + final showBody = useState(false); + + final from = DateTime.utc(currentShownTime.value.year, + currentShownTime.value.month, currentShownTime.value.day) + .subtract(currentShownTime.value.timeZoneOffset); + + return VisibilityDetector( + key: Key(genericDevice.id.toString() + info.propertyName), + child: !showBody.value + ? const SizedBox() + : ref + .watch(historyPropertyNameProvider(genericDevice.id, from, + from.add(Duration(days: 1)), info.propertyName)) + .when( + data: (final data) { + if (data.historyRecords.isNotEmpty) { + return buildHistorySeriesAnnotationChart( + context, + data, + info, + AdaptiveTheme.of(context).brightness == + Brightness.light + ? Color(info.brightThemeColor) + : Color(info.darkThemeColor), + currentShownTime); + } + return buildDataMissing(context, currentShownTime); + }, + error: (final e, final o) => Text(e.toString()), + loading: () => Column( + children: [ + Container( + margin: const EdgeInsets.only(top: 50), + child: Text( + 'Lade weitere History Daten...', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + Container( + margin: const EdgeInsets.only(top: 25), + child: const CircularProgressIndicator(), + ), + ], + ), + ), + onVisibilityChanged: (final i) { + if (context.mounted) showBody.value = !i.visibleBounds.isEmpty; + }); + } + + Widget buildDataMissing( + final BuildContext context, final ValueNotifier notifier) { + final currentShownTime = notifier.value; + return Flex( + direction: Axis.vertical, + children: [ + Expanded( + child: Text( + "Daten werden geladen oder sind nicht vorhanden für ${currentShownTime.day}.${currentShownTime.month}.${currentShownTime.year}")), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: MaterialButton( + child: const Text("Früher"), + onPressed: () { + getNewData(currentShownTime.subtract(const Duration(days: 1)), + notifier); + }, + )), + Expanded( + child: MaterialButton( + onPressed: () => _openDatePicker(context, notifier), + child: const Text('Datum auswählen')), + ), + Expanded( + child: MaterialButton( + child: const Text("Später"), + onPressed: () { + getNewData( + currentShownTime.add(const Duration(days: 1)), notifier); + }, + )), + ], + ) + ], + ); + } + + Widget buildHistorySeriesAnnotationChart( + final BuildContext context, + final History h, + final HistoryPropertyInfo info, + final Color lineColor, + final ValueNotifier currentShownTime) { + final historyRecords = h.historyRecords + .where((final x) => x.val != null) + .toList(growable: false); + final chartType = info.chartType; + final dynamic seriesList = _getSeriesList(chartType, h, lineColor); + return Flex( + direction: Axis.vertical, + children: [ + Expanded( + child: HistorySeriesAnnotationChartWidget( + seriesList, + historyRecords.minBy(10000, (final e) => e.val!).toDouble(), + historyRecords.maxBy(0, (final e) => e.val!).toDouble(), + info.unitOfMeasurement, + info.xAxisName, + currentShownTime.value, + loadMoreIndicatorBuilder: (final context, final direction) { + return Consumer( + builder: (final context, final ref, final child) { + Future.delayed(const Duration(milliseconds: 10), (() { + final current = currentShownTime; + if (direction == ChartSwipeDirection.end) { + if (current.value.day < DateTime.now().day) { + current.value = + current.value.add(const Duration(days: 1)); + } + } else { + current.value = + current.value.add(const Duration(days: -1)); + } + })); + return const SizedBox(); + }, + ); + }, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: MaterialButton( + child: const Text("Früher"), + onPressed: () { + getNewData( + currentShownTime.value.subtract(const Duration(days: 1)), + currentShownTime); + }, + )), + Expanded( + child: MaterialButton( + onPressed: () => _openDatePicker(context, currentShownTime), + child: const Text('Datum auswählen')), + ), + currentShownTime.value + .isAfter(DateTime.now().add(const Duration(hours: -23))) + ? Expanded( + child: const SizedBox(), + ) + : Expanded( + child: MaterialButton( + child: const Text("Später"), + onPressed: () { + getNewData( + currentShownTime.value.add(const Duration(days: 1)), + currentShownTime); + }, + )), + ], + ) + ], + ); + } + + dynamic _getSeriesList( + final String charType, final History h, final Color lineColor) { + switch (charType) { + case "step": + return [ + StepLineSeries( + animationDuration: 500, + // markerSettings: MarkerSettings(shape: DataMarkerType.circle, color: Colors.green, width: 5, height: 5, isVisible: true), + markerSettings: const MarkerSettings( + isVisible: true, + ), + dataSource: h.historyRecords + .map((final x) => GraphTimeSeriesValue( + DateTime.utc(1).add(Duration(milliseconds: x.ts)), + x.val, + lineColor)) + .toList(), + xValueMapper: (final GraphTimeSeriesValue value, final _) => + value.time, + yValueMapper: (final GraphTimeSeriesValue value, final _) => + value.value, + pointColorMapper: (final GraphTimeSeriesValue value, final _) => + value.lineColor) + ]; + default: + return [ + LineSeries( + animationDuration: 500, + // markerSettings: MarkerSettings(shape: DataMarkerType.circle, color: Colors.green, width: 5, height: 5, isVisible: true), + markerSettings: const MarkerSettings( + isVisible: true, + ), + dataSource: h.historyRecords + .map((final x) => GraphTimeSeriesValue( + DateTime.utc(1).add(Duration(milliseconds: x.ts)), + x.val, + lineColor)) + .toList(), + xValueMapper: (final GraphTimeSeriesValue value, final _) => + value.time, + yValueMapper: (final GraphTimeSeriesValue value, final _) => + value.value, + pointColorMapper: (final GraphTimeSeriesValue value, final _) => + value.lineColor) + ]; + } + } + + void _openDatePicker( + final BuildContext context, final ValueNotifier initial) { + // showDatePicker is a pre-made funtion of Flutter + showDatePicker( + context: context, + initialDate: initial.value, + firstDate: DateTime(2018), + lastDate: DateTime.now()) + .then((final pickedDate) { + // Check if no date is selected + if (pickedDate == null) { + return; + } + getNewData(pickedDate, initial); + }); + } + + void getNewData(final DateTime dt, final ValueNotifier state) { + if (dt.millisecondsSinceEpoch > DateTime.now().millisecondsSinceEpoch) { + return; + } + state.value = dt; + } +} + +class _GenericDeviceFab extends HookConsumerWidget { + const _GenericDeviceFab({ + required this.genericDevice, + }); + + final GenericDevice genericDevice; + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final name = ref.watch(BaseModel.typeNameProvider(genericDevice.id)); + final fabLayout = ref.watch(fabLayoutProvider(genericDevice.id, name)); + if (fabLayout == null) return const SizedBox(); + + final valueModel = ref.watch(valueStoreChangedProvider( + fabLayout.name, + fabLayout.deviceId ?? genericDevice.id, + )); + if (valueModel == null) return const SizedBox(); + useListenable(valueModel); + // final showDebugInformation = ref.watch(debugInformationEnabledProvider); + + final info = fabLayout.editInfo!; + final edit = GenericDevice.getEditParameter(valueModel, info, "Fab"); + if (edit == null) return const SizedBox(); + + return FloatingActionButton( + onPressed: (() async { + await Device.postMessage( + genericDevice.id, info, ref.read(apiProvider), valueModel.value); + }), + child: Icon( + IconData(edit.extensionData!["CodePoint"] as int, + fontFamily: edit.extensionData!["FontFamily"] ?? 'MaterialIcons'), + ), + ); + } +} diff --git a/lib/devices/heater/heater.dart b/lib/devices/heater/heater.dart index b225270..57c038b 100644 --- a/lib/devices/heater/heater.dart +++ b/lib/devices/heater/heater.dart @@ -9,25 +9,28 @@ import 'package:smarthome/devices/device_manager.dart'; import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/devices/generic/stores/value_store.dart'; import 'package:smarthome/devices/heater/heater_config.dart'; -import 'package:smarthome/devices/heater/log_screen.dart'; import 'package:smarthome/helper/connection_manager.dart'; import 'package:smarthome/helper/settings_manager.dart'; import 'package:smarthome/helper/theme_manager.dart'; import 'package:smarthome/icons/smarthome_icons.dart'; -import 'package:smarthome/models/message.dart' as sm; import 'package:smarthome/icons/icons.dart'; +import 'package:smarthome/restapi/swagger.enums.swagger.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:tuple/tuple.dart'; import 'temp_scheduling.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class Heater extends Device { - Heater(final int id, final String typeName, final IconData icon) : super(id, typeName, iconData: icon); + Heater(super.id, super.typeName, final IconData icon) : super(iconData: icon); @override void navigateToDevice(final BuildContext context) { - Navigator.push(context, MaterialPageRoute(builder: (final BuildContext context) => HeaterScreen(this))); + Navigator.push( + context, + MaterialPageRoute( + builder: (final BuildContext context) => HeaterScreen(this), + ), + ); } // @override @@ -40,12 +43,16 @@ class Heater extends Device { Widget getRightWidgets() { return Column( children: [ - Consumer(builder: (final context, final ref, final child) { - return Icon( - ref.watch(HeaterModel.disableHeatingProvider(id)) ? Icons.power_off_outlined : Icons.power_outlined, - size: 20, - ); - }), + Consumer( + builder: (final context, final ref, final child) { + return Icon( + ref.watch(HeaterModel.disableHeatingProvider(id)) + ? Icons.power_off_outlined + : Icons.power_outlined, + size: 20, + ); + }, + ), ], ); } @@ -55,61 +62,76 @@ class Heater extends Device { // XiaomiTempSensor xs = DeviceManager.devices.firstWhere((x) => x.id == baseModel.xiaomiTempSensor, orElse: () { // return XiaomiTempSensor(0, TempSensorModel(0, "", false), connection, icon); // }) as XiaomiTempSensor; - return Column(children: [ - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Consumer( - builder: (final context, final ref, final child) => Text( - (ref.watch(HeaterModel.temperatureProvider(id))?.temperature.toStringAsFixed(1) ?? ""), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24), - ), + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Consumer( + builder: (final context, final ref, final child) => Text( + (ref + .watch(HeaterModel.temperatureProvider(id)) + ?.temperature + .toStringAsFixed(1) ?? + ""), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + ), + const Text(" °C", style: TextStyle(fontSize: 18)), + ], ), - const Text( - " °C", - style: TextStyle(fontSize: 18), + Container(height: 2), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Consumer( + builder: (final context, final ref, final child) { + final currentConfig = ref.watch( + HeaterModel.currentConfigProvider(id), + ); + return Text( + currentConfig == null + ? "" + : ((currentConfig.temperature.toStringAsFixed(1))), + style: const TextStyle(fontWeight: FontWeight.normal), + ); + }, + ), + const Text("°C", style: TextStyle(fontWeight: FontWeight.normal)), + ], ), - ]), - Container( - height: 2, - ), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer( builder: (final context, final ref, final child) { - final currentConfig = ref.watch(HeaterModel.currentConfigProvider(id)); - return Text( - currentConfig == null ? "" : ((currentConfig.temperature.toStringAsFixed(1))), - style: const TextStyle(fontWeight: FontWeight.normal), - ); + final showDebug = ref.watch(debugInformationEnabledProvider); + return !showDebug + ? const SizedBox() + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Consumer( + builder: (final context, final ref, final child) { + return Text( + ref.watch(HeaterModel.versionProvider(id)), + ); + }, + ), + ], + ); }, ), - const Text( - "°C", - style: TextStyle(fontWeight: FontWeight.normal), + Consumer( + builder: (final context, final ref, final child) { + final showDebug = ref.watch(debugInformationEnabledProvider); + return showDebug ? Text(id.toString()) : const SizedBox(); + }, ), - ]), - Consumer(builder: (final context, final ref, final child) { - final showDebug = ref.watch(debugInformationEnabledProvider); - return !showDebug - ? Container() - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Consumer( - builder: (final context, final ref, final child) { - return Text(ref.watch(HeaterModel.versionProvider(id))); - }, - ) - ], - ); - }), - Consumer( - builder: (final context, final ref, final child) { - final showDebug = ref.watch(debugInformationEnabledProvider); - return showDebug ? Text(id.toString()) : Container(); - }, - ) - - // (xs.id == 0 ? Text(baseModel.xiaomiTempSensor.toString()) : Text(xs.baseModel.friendlyName)), - ]); + + // (xs.id == 0 ? Text(baseModel.xiaomiTempSensor.toString()) : Text(xs.baseModel.friendlyName)), + ], + ); } @override @@ -121,7 +143,7 @@ class Heater extends Device { class HeaterScreen extends ConsumerStatefulWidget { final Heater device; - const HeaterScreen(this.device, {final Key? key}) : super(key: key); + const HeaterScreen(this.device, {super.key}); @override _HeaterScreenState createState() => _HeaterScreenState(); @@ -138,7 +160,9 @@ class _HeaterScreenState extends ConsumerState { @override void initState() { heater = widget.device; - final currentConfig = ref.read(HeaterModel.currentConfigProvider(widget.device.id)); + final currentConfig = ref.read( + HeaterModel.currentConfigProvider(widget.device.id), + ); handlePointerValueChanged(currentConfig?.temperature ?? 21); textEditingController = TextEditingController(text: tempString()); @@ -150,8 +174,12 @@ class _HeaterScreenState extends ConsumerState { Widget build(final BuildContext context) { final double width = MediaQuery.of(context).size.width; - final xiaomiTempSensor = ref.watch(HeaterModel.xiaomiProvider(widget.device.id)); - final tempSensorDevice = ref.watch(valueStoreChangedProvider(Tuple2("temperature", xiaomiTempSensor ?? -1))); + final xiaomiTempSensor = ref.watch( + HeaterModel.xiaomiProvider(widget.device.id), + ); + final tempSensorDevice = ref.watch( + valueStoreChangedProvider("temperature", xiaomiTempSensor ?? -1), + ); return DefaultTabController( length: 2, @@ -161,15 +189,21 @@ class _HeaterScreenState extends ConsumerState { tabs: [ Consumer( builder: (final context, final ref, final child) { - final typeNames = ref.watch(BaseModel.typeNamesProvider(widget.device.id)); + final typeNames = ref.watch( + BaseModel.typeNamesProvider(widget.device.id), + ); final icon = ref.watch( - iconWidgetProvider(Tuple4(typeNames ?? [], widget.device, AdaptiveTheme.of(context), true))); + iconWidgetProvider( + typeNames ?? [], + widget.device, + AdaptiveTheme.of(context), + true, + ), + ); return Tab(icon: icon); }, ), - const Tab( - icon: Icon(Icons.settings), - ), + const Tab(icon: Icon(Icons.settings)), ], ), ), @@ -177,7 +211,12 @@ class _HeaterScreenState extends ConsumerState { decoration: ThemeManager.getBackgroundDecoration(context), child: TabBarView( children: [ - buildColumnView(width, tempSensorDevice is ValueStore ? tempSensorDevice.currentValue : 21.0), + buildColumnView( + width, + tempSensorDevice is ValueStore + ? tempSensorDevice.currentValue + : 21.0, + ), buildSettingsView(width), ], ), @@ -186,26 +225,37 @@ class _HeaterScreenState extends ConsumerState { ); } - _pushTempSettings(final BuildContext context) async { + Future _pushTempSettings(final BuildContext context) async { final res = await Navigator.push( - context, - MaterialPageRoute>>( - builder: (final BuildContext context) => TempScheduling(widget.device.id), fullscreenDialog: true)); - if (res == null || !res.item1) return; + context, + MaterialPageRoute<(bool, List)>( + builder: (final BuildContext context) => + TempScheduling(widget.device.id), + fullscreenDialog: true, + ), + ); + if (res == null || !res.$1) return; - widget.device.sendToServer(sm.MessageType.Options, sm.Command.Temp, - res.item2.map((final f) => jsonEncode(f)).toList(), ref.read(hubConnectionProvider)); + widget.device.sendToServer( + MessageType.options, + Command.temp, + res.$2.map((final f) => jsonEncode(f)).toList(), + ref.read(apiProvider), + ); } - _pushLogView(final BuildContext context) async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (final BuildContext context) => LogScreen(HeaterModel.logsProvider, heater.id), - fullscreenDialog: true)); - } + // Future _pushLogView(final BuildContext context) async { + // await Navigator.push( + // context, + // MaterialPageRoute( + // builder: (final BuildContext context) => + // LogScreen(HeaterModel.logsProvider, heater.id), + // fullscreenDialog: true, + // ), + // ); + // } - buildColumnView(final double width, final double value) { + Column buildColumnView(final double width, final double value) { return Column( children: [ BlurryCard( @@ -213,10 +263,17 @@ class _HeaterScreenState extends ConsumerState { child: Row( children: [ Container( - margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), child: const Icon(Icons.person)), + margin: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: const Icon(Icons.person), + ), Consumer( builder: (final context, final ref, final child) { - return Text(ref.watch(BaseModel.friendlyNameProvider(widget.device.id))); + return Text( + ref.watch(BaseModel.friendlyNameProvider(widget.device.id)), + ); }, ), ], @@ -227,14 +284,23 @@ class _HeaterScreenState extends ConsumerState { child: Row( children: [ Container( - margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), child: const Icon(Icons.timer)), + margin: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: const Icon(Icons.timer), + ), const Text("Zuletzt Empfangen: "), Consumer( builder: (final context, final ref, final child) { - final temperature = ref.watch(HeaterModel.temperatureProvider(widget.device.id)); - return Text(temperature == null - ? "Keine Daten vorliegend" - : "${dayOfWeekToStringMap[temperature.dayOfWeek]!} ${temperature.timeOfDay.format(context)}"); + final temperature = ref.watch( + HeaterModel.temperatureProvider(widget.device.id), + ); + return Text( + temperature == null + ? "Keine Daten vorliegend" + : "${dayOfWeekToStringMap[temperature.dayOfWeek]!} ${temperature.timeOfDay.format(context)}", + ); }, ), ], @@ -245,14 +311,25 @@ class _HeaterScreenState extends ConsumerState { child: Row( children: [ Container( - margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), child: const Icon(Icons.receipt)), + margin: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: const Icon(Icons.receipt), + ), const Text("Kalibrierung: "), - Consumer(builder: (final context, final ref, final child) { - final currentCalibration = ref.watch(HeaterModel.currentCalibrationProvider(widget.device.id)); - return Text(currentCalibration?.temperature == null - ? "Kein Ziel" - : "${currentCalibration!.temperature.toStringAsFixed(1)}°C (${dayOfWeekToStringMap[currentCalibration.dayOfWeek]!} ${currentCalibration.timeOfDay.format(context)})"); - }), + Consumer( + builder: (final context, final ref, final child) { + final currentCalibration = ref.watch( + HeaterModel.currentCalibrationProvider(widget.device.id), + ); + return Text( + currentCalibration?.temperature == null + ? "Kein Ziel" + : "${currentCalibration!.temperature.toStringAsFixed(1)}°C (${dayOfWeekToStringMap[currentCalibration.dayOfWeek]!} ${currentCalibration.timeOfDay.format(context)})", + ); + }, + ), ], ), ), @@ -262,19 +339,25 @@ class _HeaterScreenState extends ConsumerState { child: Row( children: [ Container( - margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), - child: const Icon(Icons.receipt)), + margin: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: const Icon(Icons.receipt), + ), Consumer( builder: (final context, final ref, final child) { - final version = ref.watch(HeaterModel.versionProvider(widget.device.id)); + final version = ref.watch( + HeaterModel.versionProvider(widget.device.id), + ); return Text(version); }, ), ], ), ) - : Container(), - getTempGauge(value) + : const SizedBox(), + getTempGauge(value), ], ); } @@ -285,14 +368,18 @@ class _HeaterScreenState extends ConsumerState { void handlePointerValueChangedEnd(final double value) { handlePointerValueChanged(value); - widget.device.sendToServer( - sm.MessageType.Update, sm.Command.Temp, [_annotationValue], ref.read(hubConnectionProvider)); + widget.device.sendToServer(MessageType.update, Command.temp, [ + _annotationValue, + ], ref.read(apiProvider)); } void handlePointerValueChanging(final ValueChangingArgs args) { - final model = ref.read(BaseModel.byIdProvider(widget.device.id)); - if (model is! HeaterModel || model.currentConfig == null) return; - _setPointerValue(model.currentConfig!.temperature); + final config = ref.read( + HeaterModel.currentConfigProvider(widget.device.id), + ); + + if (config == null) return; + _setPointerValue(config.temperature); } /// method to set the pointer value @@ -304,7 +391,7 @@ class _HeaterScreenState extends ConsumerState { }); } - buildSettingsView(final double width) { + Column buildSettingsView(final double width) { return Column( children: [ BlurryCard( @@ -312,10 +399,14 @@ class _HeaterScreenState extends ConsumerState { child: Row( children: [ Container( - margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), - child: const Icon(SmarthomeIcons.temperature)), + margin: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: const Icon(SmarthomeIcons.temperature), + ), const Text("Sensor: "), - TemperatureSensorDropdown(widget.device) + TemperatureSensorDropdown(widget.device), ], ), ), @@ -325,30 +416,48 @@ class _HeaterScreenState extends ConsumerState { children: [ Consumer( builder: (final context, final ref, final child) { - final heaterIcon = ref.watch(iconWidgetSingleProvider( - Tuple4(widget.device.typeName, widget.device, AdaptiveTheme.of(context), false))); + final heaterIcon = ref.watch( + iconWidgetSingleProvider( + widget.device.typeName, + widget.device, + AdaptiveTheme.of(context), + false, + ), + ); return Container( - margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), + margin: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), child: heaterIcon, ); }, ), const Text("Heizung: "), - Consumer(builder: (final context, final ref, final child) { - final disableHeater = ref.watch(HeaterModel.disableHeatingProvider(widget.device.id)); - return Switch( - value: !disableHeater, - onChanged: (final val) { - sm.Command command; - if (val) { - command = sm.Command.On; - } else { - command = sm.Command.Off; - } - widget.device.sendToServer(sm.MessageType.Update, command, [], ref.read(hubConnectionProvider)); - }, - ); - }), + Consumer( + builder: (final context, final ref, final child) { + final disableHeater = ref.watch( + HeaterModel.disableHeatingProvider(widget.device.id), + ); + return Switch( + value: !disableHeater, + onChanged: (final val) { + Command command; + if (val) { + command = Command.on; + } else { + command = Command.off; + } + widget.device.sendToServer( + MessageType.update, + command, + [], + ref.read(apiProvider), + ); + }, + ); + }, + ), ], ), ), @@ -357,23 +466,33 @@ class _HeaterScreenState extends ConsumerState { child: Row( children: [ Container( - margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), + margin: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), child: const Icon(SmarthomeIcons.lamp), ), const Text("Blaue Led: "), Consumer( builder: (final context, final ref, final child) { - final disableLed = ref.watch(HeaterModel.disableLedProvider(widget.device.id)); + final disableLed = ref.watch( + HeaterModel.disableLedProvider(widget.device.id), + ); return Switch( value: !disableLed, onChanged: (final val) { - sm.Command command; + Command command; if (val) { - command = sm.Command.On; + command = Command.on; } else { - command = sm.Command.Off; + command = Command.off; } - widget.device.sendToServer(sm.MessageType.Options, command, [], ref.read(hubConnectionProvider)); + widget.device.sendToServer( + MessageType.options, + command, + [], + ref.read(apiProvider), + ); }, ); }, @@ -399,24 +518,24 @@ class _HeaterScreenState extends ConsumerState { ), ), ), - BlurryCard( - margin: const EdgeInsets.only(left: 8, right: 8, top: 8), - child: MaterialButton( - onPressed: () => _pushLogView(context), - child: Row( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 4.0), - child: const Icon(Icons.settings), - ), - TextButton( - onPressed: () => _pushLogView(context), - child: const Text("Logs"), - ), - ], - ), - ), - ), + // BlurryCard( + // margin: const EdgeInsets.only(left: 8, right: 8, top: 8), + // child: MaterialButton( + // onPressed: () => _pushLogView(context), + // child: Row( + // children: [ + // Container( + // margin: const EdgeInsets.symmetric(vertical: 4.0), + // child: const Icon(Icons.settings), + // ), + // TextButton( + // onPressed: () => _pushLogView(context), + // child: const Text("Logs"), + // ), + // ], + // ), + // ), + // ), ], ); } @@ -431,12 +550,12 @@ class _HeaterScreenState extends ConsumerState { children: [ Text( _annotationValue, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 28), - ), - const Text( - ' °C', - style: TextStyle(fontSize: 24), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 28, + ), ), + const Text(' °C', style: TextStyle(fontSize: 24)), ], ), ), @@ -452,10 +571,14 @@ class _HeaterScreenState extends ConsumerState { maximum: 35, interval: 1, axisLineStyle: const AxisLineStyle( - gradient: SweepGradient(colors: [Colors.blue, Colors.amber, Colors.red], stops: [0.3, 0.5, 1]), - color: Colors.red, - thickness: 0.04, - thicknessUnit: GaugeSizeUnit.factor), + gradient: SweepGradient( + colors: [Colors.blue, Colors.amber, Colors.red], + stops: [0.3, 0.5, 1], + ), + color: Colors.red, + thickness: 0.04, + thicknessUnit: GaugeSizeUnit.factor, + ), tickOffset: 0.02, ticksPosition: ElementsPosition.outside, labelOffset: 0.05, @@ -465,7 +588,10 @@ class _HeaterScreenState extends ConsumerState { labelsPosition: ElementsPosition.outside, minorTicksPerInterval: 10, minorTickStyle: const MinorTickStyle(length: 0.1), - majorTickStyle: const MajorTickStyle(length: 0.05, lengthUnit: GaugeSizeUnit.factor), + majorTickStyle: const MajorTickStyle( + length: 0.05, + lengthUnit: GaugeSizeUnit.factor, + ), ), RadialAxis( startAngle: 150, @@ -475,10 +601,14 @@ class _HeaterScreenState extends ConsumerState { maximum: 35, interval: 5, axisLineStyle: const AxisLineStyle( - gradient: SweepGradient(colors: [Colors.blue, Colors.amber, Colors.red], stops: [0.3, 0.5, 1]), - color: Colors.red, - thickness: 0.04, - thicknessUnit: GaugeSizeUnit.factor), + gradient: SweepGradient( + colors: [Colors.blue, Colors.amber, Colors.red], + stops: [0.3, 0.5, 1], + ), + color: Colors.red, + thickness: 0.04, + thicknessUnit: GaugeSizeUnit.factor, + ), tickOffset: 0.02, ticksPosition: ElementsPosition.outside, labelOffset: 0.05, @@ -487,7 +617,10 @@ class _HeaterScreenState extends ConsumerState { labelsPosition: ElementsPosition.outside, minorTicksPerInterval: 0, minorTickStyle: const MinorTickStyle(length: 0.1), - majorTickStyle: const MajorTickStyle(length: 0.05, lengthUnit: GaugeSizeUnit.factor), + majorTickStyle: const MajorTickStyle( + length: 0.05, + lengthUnit: GaugeSizeUnit.factor, + ), pointers: [ MarkerPointer( value: _value, @@ -515,68 +648,97 @@ class _HeaterScreenState extends ConsumerState { ], annotations: [ GaugeAnnotation( - widget: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Ausgelesen"), - Container( - margin: const EdgeInsets.only(bottom: 16.0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // Text("Ausgelesen: "), - // Icon(Icons.search), - Consumer( - builder: (final context, final ref, final child) { - final temperature = ref.watch(HeaterModel.temperatureProvider(widget.device.id)); - return temperature?.temperature == null - ? const Text("Kein Messergebnis", style: TextStyle(fontSize: 24)) - : Row(children: [ - Text( - temperature!.temperature.toStringAsFixed(1), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24), - ), - Text( - "°C (${dayOfWeekToStringMap[temperature.dayOfWeek]!} ${temperature.timeOfDay.format(context)})", - style: const TextStyle(fontSize: 24)) - ]); - }, - ) - ], - ), - ), - const Text("Ziel"), - Row( + widget: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("Ausgelesen"), + Container( + margin: const EdgeInsets.only(bottom: 16.0), + child: Row( mainAxisSize: MainAxisSize.min, children: [ - // Text("Ziel: "), - // Icon(Icons.), - + // Text("Ausgelesen: "), + // Icon(Icons.search), Consumer( builder: (final context, final ref, final child) { - final currentConfig = ref.watch(HeaterModel.currentConfigProvider(widget.device.id)); - return currentConfig?.temperature == null - ? const Text("Keins", style: TextStyle(fontSize: 24)) + final temperature = ref.watch( + HeaterModel.temperatureProvider( + widget.device.id, + ), + ); + return temperature?.temperature == null + ? const Text( + "Kein Messergebnis", + style: TextStyle(fontSize: 24), + ) : Row( children: [ Text( - currentConfig!.temperature.toStringAsFixed(1), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24), + temperature!.temperature + .toStringAsFixed(1), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), ), Text( - "°C (${dayOfWeekToStringMap[currentConfig.dayOfWeek]!} ${currentConfig.timeOfDay.format(context)})", - style: const TextStyle(fontSize: 24), + "°C (${dayOfWeekToStringMap[temperature.dayOfWeek]!} ${temperature.timeOfDay.format(context)})", + style: const TextStyle( + fontSize: 24, + ), ), ], ); }, - ) + ), ], ), - ], - ), - // positionFactor: 0.7, - angle: 180) + ), + const Text("Ziel"), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Text("Ziel: "), + // Icon(Icons.), + Consumer( + builder: (final context, final ref, final child) { + final currentConfig = ref.watch( + HeaterModel.currentConfigProvider( + widget.device.id, + ), + ); + return currentConfig?.temperature == null + ? const Text( + "Keins", + style: TextStyle(fontSize: 24), + ) + : Row( + children: [ + Text( + currentConfig!.temperature + .toStringAsFixed(1), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + Text( + "°C (${dayOfWeekToStringMap[currentConfig.dayOfWeek]!} ${currentConfig.timeOfDay.format(context)})", + style: const TextStyle( + fontSize: 24, + ), + ), + ], + ); + }, + ), + ], + ), + ], + ), + // positionFactor: 0.7, + angle: 180, + ), ], ), ], @@ -590,34 +752,41 @@ class _HeaterScreenState extends ConsumerState { @immutable class TemperatureSensorDropdown extends ConsumerWidget { final Heater device; - const TemperatureSensorDropdown(this.device, {final Key? key}) : super(key: key); + const TemperatureSensorDropdown(this.device, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { - final possibleDevices = ref.watch(devicesByValueStoreKeyProvider("temperature")); - final model = ref.watch(device.baseModelTProvider(device.id)); - if (model is! HeaterModel) return Container(); - final currentDevice = ref.watch(deviceByIdProvider(model.xiaomiTempSensor ?? -1)); + final possibleDevices = ref.watch( + devicesByValueStoreKeyProvider("temperature"), + ); + final model = ref.watch(HeaterModel.xiaomiProvider(device.id)); + final currentDevice = ref.watch(deviceByIdProvider(model ?? -1)); return DropdownButton( items: (possibleDevices - .map((final f) => DropdownMenuItem( - value: f, - child: Consumer( - builder: (final context, final ref, final child) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(f.id)); - return Text(friendlyName); - }, - ), - )) + .map( + (final f) => DropdownMenuItem( + value: f, + child: Consumer( + builder: (final context, final ref, final child) { + final friendlyName = ref.watch( + BaseModel.friendlyNameProvider(f.id), + ); + return Text(friendlyName); + }, + ), + ), + ) .toList()), onChanged: (final dynamic a) { - device.sendToServer(sm.MessageType.Update, sm.Command.DeviceMapping, - [a.id.toString(), currentDevice?.id.toString() ?? "0"], ref.read(hubConnectionProvider)); - final models = ref.read(baseModelProvider.notifier); - final newList = models.state.toList(); - newList.remove(model); - newList.add(model.copyWith(xiaomiTempSensor: a.id)); - models.state = newList; + device.sendToServer(MessageType.update, Command.devicemapping, [ + a.id.toString(), + currentDevice?.id.toString() ?? "0", + ], ref.read(apiProvider)); + + // final newList = ref.read(baseModelsProvider).toList(); + // newList.remove(model); + // newList.add(model.copyWith(xiaomiTempSensor: a.id)); + // ref.read(baseModelsProvider.notifier).storeModels(newList); }, value: currentDevice, ); diff --git a/lib/devices/heater/heater_config.dart b/lib/devices/heater/heater_config.dart index 0aa6d3a..7f4d34e 100644 --- a/lib/devices/heater/heater_config.dart +++ b/lib/devices/heater/heater_config.dart @@ -18,33 +18,33 @@ const dayOfWeekToStringMap = { }; const dayOfWeekToFlagMap = { - DayOfWeek.Mon: 1 << 1, - DayOfWeek.Tue: 1 << 2, - DayOfWeek.Wed: 1 << 3, - DayOfWeek.Thu: 1 << 4, - DayOfWeek.Fri: 1 << 5, - DayOfWeek.Sat: 1 << 6, - DayOfWeek.Sun: 1 << 7 + DayOfWeek.Mon: 1 << 0, + DayOfWeek.Tue: 1 << 1, + DayOfWeek.Wed: 1 << 2, + DayOfWeek.Thu: 1 << 3, + DayOfWeek.Fri: 1 << 4, + DayOfWeek.Sat: 1 << 5, + DayOfWeek.Sun: 1 << 6 }; const flagToDayOfWeekMap = { - 1 << 1: DayOfWeek.Mon, - 1 << 2: DayOfWeek.Tue, - 1 << 3: DayOfWeek.Wed, - 1 << 4: DayOfWeek.Thu, - 1 << 5: DayOfWeek.Fri, - 1 << 6: DayOfWeek.Sat, - 1 << 7: DayOfWeek.Sun + 1 << 0: DayOfWeek.Mon, + 1 << 1: DayOfWeek.Tue, + 1 << 2: DayOfWeek.Wed, + 1 << 3: DayOfWeek.Thu, + 1 << 4: DayOfWeek.Fri, + 1 << 5: DayOfWeek.Sat, + 1 << 6: DayOfWeek.Sun }; const dayOfWeekStringToFlagMap = { - 'Mo': 1 << 1, - 'Di': 1 << 2, - 'Mi': 1 << 3, - 'Do': 1 << 4, - 'Fr': 1 << 5, - 'Sa': 1 << 6, - 'So': 1 << 7 + 'Mo': 1 << 0, + 'Di': 1 << 1, + 'Mi': 1 << 2, + 'Do': 1 << 3, + 'Fr': 1 << 4, + 'Sa': 1 << 5, + 'So': 1 << 6 }; const dayOfWeekToLongStringMap = { @@ -73,24 +73,26 @@ class HeaterConfig extends Equatable implements Comparable { static String timeOfDayToJson(final TimeOfDay? val) { if (val == null) return ""; - final now = DateTime.now(); - return (DateTime(now.year, now.month, now.day, val.hour, val.minute)).toIso8601String(); + return (DateTime.utc(2000, 1, 1, val.hour, val.minute)).toIso8601String(); } static TimeOfDay timeOfDayFromJson(final String val) { - final dt = DateTime.tryParse(val)!.toLocal(); + final dt = DateTime.tryParse(val)!; const TimeOfDay(hour: 0, minute: 0); return TimeOfDay.fromDateTime(dt); } - factory HeaterConfig.fromJson(final Map json) => _$HeaterConfigFromJson(json); + factory HeaterConfig.fromJson(final Map json) => + _$HeaterConfigFromJson(json); Map toJson() => _$HeaterConfigToJson(this); @override int compareTo(final other) { return (dayOfWeek.index * 1440 + timeOfDay.hour * 60 + timeOfDay.minute) - - (other.dayOfWeek.index * 1440 + other.timeOfDay.hour * 60 + other.timeOfDay.minute) as int; + (other.dayOfWeek.index * 1440 + + other.timeOfDay.hour * 60 + + other.timeOfDay.minute) as int; } @override diff --git a/lib/devices/heater/heater_model.dart b/lib/devices/heater/heater_model.dart index 70c79c3..49994bd 100644 --- a/lib/devices/heater/heater_model.dart +++ b/lib/devices/heater/heater_model.dart @@ -1,97 +1,127 @@ import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/base_model.dart'; import 'package:smarthome/devices/connection_base_model.dart'; +import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/devices/heater/heater_config.dart'; import 'package:riverpod/riverpod.dart'; part 'heater_model.g.dart'; +// These baseModel Providers should be moved to store architecture +// + @JsonSerializable() @immutable class HeaterModel extends ConnectionBaseModel { - final HeaterConfig? temperature; - final int? xiaomiTempSensor; - final HeaterConfig? currentConfig; - final HeaterConfig? currentCalibration; - @JsonKey(name: 'version') - final String? version; - final bool? disableLed; - final bool? disableHeating; - final String? logs; + // final HeaterConfig? temperature; + // final int? xiaomiTempSensor; + // final HeaterConfig? currentConfig; + // final HeaterConfig? currentCalibration; + // @JsonKey(name: 'version') + // final String? version; + // final bool? disableLed; + // final bool? disableHeating; + // final String? logs; - static final temperatureProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is HeaterModel) return baseModel.temperature; - return null; - }); - static final currentConfigProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is HeaterModel) return baseModel.currentConfig; + static final temperatureProvider = Provider.family(( + final ref, + final id, + ) { + final temp = ref.watch(valueStoreChangedProvider("temperature", id))?.value; + if (temp is Map) return HeaterConfig.fromJson(temp); return null; }); - static final currentCalibrationProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is HeaterModel) return baseModel.currentCalibration; + static final currentConfigProvider = Provider.family(( + final ref, + final id, + ) { + final temp = ref + .watch(valueStoreChangedProvider("currentConfig", id)) + ?.value; + if (temp is Map) return HeaterConfig.fromJson(temp); return null; }); - static final disableHeatingProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is! HeaterModel) return false; - return baseModel.disableHeating ?? false; + static final currentCalibrationProvider = Provider.family( + (final ref, final id) { + final temp = ref + .watch(valueStoreChangedProvider("currentCalibration", id)) + ?.value; + if (temp is Map) return HeaterConfig.fromJson(temp); + return null; + }, + ); + static final disableHeatingProvider = Provider.family(( + final ref, + final id, + ) { + return ref.watch(valueStoreChangedProvider("disableHeating", id))?.value + as bool? ?? + false; }); - static final disableLedProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is! HeaterModel) return false; - return baseModel.disableLed ?? false; + static final disableLedProvider = Provider.family(( + final ref, + final id, + ) { + return ref.watch(valueStoreChangedProvider("disableLed", id))?.value + as bool? ?? + false; }); - static final versionProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is! HeaterModel) return ""; - return baseModel.version ?? ""; + static final versionProvider = Provider.family(( + final ref, + final id, + ) { + return ref.watch(valueStoreChangedProvider("version", id))?.value + as String? ?? + ""; }); - static final xiaomiProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is! HeaterModel) return null; - return baseModel.xiaomiTempSensor; + static final xiaomiProvider = Provider.family(( + final ref, + final id, + ) { + return ref.watch(valueStoreChangedProvider("xiaomiTempSensor", id))?.value + as int?; }); - static final logsProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is! HeaterModel) return ""; - return baseModel.logs ?? ""; + static final logsProvider = Provider.family(( + final ref, + final id, + ) { + return ref.watch(valueStoreChangedProvider("logs", id))?.value as String? ?? + ""; }); const HeaterModel( - final int id, - final String friendlyName, - final String typeName, - final bool isConnected, - this.temperature, - this.xiaomiTempSensor, - this.currentConfig, - this.currentCalibration, - this.version, - this.disableLed, - this.disableHeating, - this.logs, - ) : super(id, friendlyName, typeName, isConnected); + super.id, + super.friendlyName, + super.typeName, + super.isConnected, + // this.temperature, + // this.xiaomiTempSensor, + // this.currentConfig, + // this.currentCalibration, + // this.version, + // this.disableLed, + // this.disableHeating, + // this.logs, + ); - factory HeaterModel.fromJson(final Map json) => _$HeaterModelFromJson(json); + factory HeaterModel.fromJson(final Map json) => + _$HeaterModelFromJson(json); @override HeaterModel getModelFromJson(final Map json) { return HeaterModel.fromJson(json); } - @override - BaseModel updateFromJson(final Map json) { - final updatedModel = getModelFromJson(json); - bool updated = false; - if (updatedModel != this) { - updated = true; - } - return updated ? updatedModel : this; - } + // @override + // bool updateModelFromJson(final Map json) { + // final updatedModel = getModelFromJson(json); + // bool updated = false; + // if (updatedModel != this) { + // updated = true; + // } + + // return updated ? updatedModel : this; + // } @override Map toJson() => _$HeaterModelToJson(this); @@ -100,45 +130,59 @@ class HeaterModel extends ConnectionBaseModel { bool operator ==(final Object other) => other is HeaterModel && other.id == id && - other.friendlyName == friendlyName && - other.disableHeating == disableHeating && - other.disableLed == disableLed && - other.version == version && - xiaomiTempSensor == other.xiaomiTempSensor && - currentCalibration == other.currentCalibration && - currentConfig == other.currentConfig && - temperature == other.temperature && - logs == other.logs; + other.friendlyName == + friendlyName //&& + // other.disableHeating == disableHeating && + // other.disableLed == disableLed && + // other.version == version && + // xiaomiTempSensor == other.xiaomiTempSensor && + // currentCalibration == other.currentCalibration && + // currentConfig == other.currentConfig && + // temperature == other.temperature && + // logs == other.logs + ; @override - int get hashCode => Object.hash(id, friendlyName, disableHeating, disableLed, version, xiaomiTempSensor, - currentCalibration, currentConfig, temperature, logs); + int get hashCode => Object.hash( + id, + friendlyName, + // disableHeating, + // disableLed, + // version, + // xiaomiTempSensor, + // currentCalibration, + // currentConfig, + // temperature, + // logs, + ); - HeaterModel copyWith( - {final int? id, - final String? friendlyName, - final String? typeName, - final bool? isConnected, - final bool? disableHeating, - final bool? disableLed, - final String? version, - final int? xiaomiTempSensor, - final HeaterConfig? temperature, - final HeaterConfig? currentConfig, - final HeaterConfig? currentCalibration, - final String? logs}) { + HeaterModel copyWith({ + final int? id, + final String? friendlyName, + final String? typeName, + final bool? isConnected, + // final bool? disableHeating, + // final bool? disableLed, + // final String? version, + // final int? xiaomiTempSensor, + // final HeaterConfig? temperature, + // final HeaterConfig? currentConfig, + // final HeaterConfig? currentCalibration, + // final String? logs, + }) { return HeaterModel( - id ?? this.id, - friendlyName ?? this.friendlyName, - typeName ?? this.typeName, - isConnected ?? this.isConnected, - temperature ?? this.temperature, - xiaomiTempSensor ?? this.xiaomiTempSensor, - currentConfig ?? this.currentConfig, - currentCalibration ?? this.currentCalibration, - version ?? this.version, - disableLed ?? this.disableLed, - disableHeating ?? this.disableHeating, - logs ?? this.logs); + id ?? this.id, + friendlyName ?? this.friendlyName, + typeName ?? this.typeName, + isConnected ?? this.isConnected, + // temperature ?? this.temperature, + // xiaomiTempSensor ?? this.xiaomiTempSensor, + // currentConfig ?? this.currentConfig, + // currentCalibration ?? this.currentCalibration, + // version ?? this.version, + // disableLed ?? this.disableLed, + // disableHeating ?? this.disableHeating, + // logs ?? this.logs, + ); } } diff --git a/lib/devices/heater/heater_temp_settings.dart b/lib/devices/heater/heater_temp_settings.dart index cd971c2..a5000ab 100644 --- a/lib/devices/heater/heater_temp_settings.dart +++ b/lib/devices/heater/heater_temp_settings.dart @@ -1,123 +1,143 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; import 'package:smarthome/helper/theme_manager.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:tuple/tuple.dart'; import 'heater_config.dart'; -class HeaterTempSettings extends StatefulWidget { +class HeaterTempSettings extends HookWidget { final List configs; - final Tuple2 timeTemp; - const HeaterTempSettings(this.timeTemp, this.configs, {final Key? key}) : super(key: key); + final (TimeOfDay?, double?) timeTemp; + HeaterTempSettings(this.timeTemp, this.configs, {super.key}); - @override - HeaterTempSettingsState createState() => HeaterTempSettingsState(); -} - -class HeaterTempSettingsState extends State { final GlobalKey _scaffoldKey = GlobalKey(); final GlobalKey _formKey = GlobalKey(); - bool _saveNeeded = false; - late List heaterConfigs; - int selected = 0; - double _value = 21.0; - String _annotationValue = '21.0'; - late TimeOfDay initialDate; - - @override - void initState() { - super.initState(); - heaterConfigs = widget.configs; - selected = widget.configs.bitOr((final x) => dayOfWeekToFlagMap[x.dayOfWeek]!); - _setPointerValue(widget.timeTemp.item2!); - initialDate = widget.timeTemp.item1!; - //"${widget.timeTemp.item1!.hour.toString().padLeft(2, "0")}:${widget.timeTemp.item1!.minute.toString().padLeft(2, "0")}"; - } + // bool _saveNeeded = false; + // late List heaterConfigs; + // int selected = 0; + // double _value = 21.0; + // String _annotationValue = '21.0'; + // late TimeOfDay selectedDate; - Future _onWillPop() async { - if (!_saveNeeded) return true; + Future _onWillPop( + final BuildContext context, + final ValueNotifier saveNeeded, + ) async { + if (!saveNeeded.value) return true; final ThemeData theme = Theme.of(context); - final TextStyle dialogTextStyle = theme.textTheme.titleMedium!.copyWith(color: theme.textTheme.bodySmall!.color); + final TextStyle dialogTextStyle = theme.textTheme.titleMedium!.copyWith( + color: theme.textTheme.bodySmall!.color, + ); return await (showDialog( - context: context, - builder: (final BuildContext context) => AlertDialog( - content: Text("Neue Temperatureinstellung verwerfen?", style: dialogTextStyle), - actions: [ - TextButton( - child: const Text("Abbrechen"), - onPressed: () { - Navigator.of(context).pop(false); - }), - TextButton( - child: const Text("Verwerfen"), - onPressed: () { - Navigator.of(context).pop(true); - }) - ]))) ?? + context: context, + builder: (final BuildContext context) => AlertDialog( + content: Text( + "Neue Temperatureinstellung verwerfen?", + style: dialogTextStyle, + ), + actions: [ + TextButton( + child: const Text("Abbrechen"), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + TextButton( + child: const Text("Verwerfen"), + onPressed: () { + saveNeeded.value = false; + WidgetsBinding.instance.addPostFrameCallback((final _) { + Navigator.of(context).pop(true); + Navigator.of(context).pop(); + }); + }, + ), + ], + ), + )) ?? false; } - void showInSnackBar(final String value) { + void showInSnackBar(final BuildContext context, final String value) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(value))); } - Future _handleSubmitted() async { + Future _handleSubmitted( + final BuildContext context, + final bool saveNeeded, + final TimeOfDay selectedDate, + final int selected, + final double value, + ) async { final FormState form = _formKey.currentState!; if (!form.validate()) { return false; } else { form.save(); //double realWeight = recursiveParsing(weight); - if (_saveNeeded == false) { - Navigator.of(context).pop(const Tuple2(false, [])); + if (saveNeeded == false) { + Navigator.of(context).pop(const (false, [])); return true; } final configs = []; - final tod = initialDate; + final tod = selectedDate; for (int i = 0; i < 7; i++) { final flag = 1 << (i + 1); if (selected & flag == 0) continue; final dayOfWeek = flagToDayOfWeekMap[flag]!; - configs.add(HeaterConfig(dayOfWeek, tod, _value)); + configs.add(HeaterConfig(dayOfWeek, tod, value)); } - Navigator.of(context).pop(Tuple2(true, configs)); + Navigator.of(context).pop((true, configs)); } return true; } - void handlePointerValueChanged(final double value) { - _setPointerValue(value); - _saveNeeded = true; - } - - void handlePointerValueChangedEnd(final double value) { - handlePointerValueChanged(value); - _saveNeeded = true; + void handlePointerValueChangedEnd( + final double value, + final ValueNotifier valueNot, + final ValueNotifier annotationValue, + final ValueNotifier saveNeeded, + ) { + _setPointerValue(value, valueNot, annotationValue); + saveNeeded.value = true; } - void handlePointerValueChanging(final ValueChangingArgs args) { - _value = _value.clamp(5, 35); - _setPointerValue(_value); - _saveNeeded = true; + void handlePointerValueChanging( + final ValueChangingArgs args, + final ValueNotifier value, + final ValueNotifier annotationValue, + final ValueNotifier saveNeeded, + ) { + value.value = value.value.clamp(5, 35); + _setPointerValue(value.value, value, annotationValue); + saveNeeded.value = true; } /// method to set the pointer value - void _setPointerValue(final double value) { - setState(() { - _value = (value.clamp(5, 35) * 10).roundToDouble() / 10; - _annotationValue = _value.toStringAsFixed(1); - }); + void _setPointerValue( + final double value, + final ValueNotifier valueNot, + final ValueNotifier annotationValue, + ) { + valueNot.value = (value.clamp(5, 35) * 10).roundToDouble() / 10; + annotationValue.value = valueNot.value.toStringAsFixed(1); } Future displayTimePicker( - final BuildContext context, final TimeOfDay initalTime, final Function(TimeOfDay time) selectedValue) async { - final time = await showTimePicker(context: context, initialTime: initalTime); + final BuildContext context, + final TimeOfDay initalTime, + final Function(TimeOfDay time) selectedValue, + ) async { + final time = await showTimePicker( + context: context, + initialTime: initalTime, + ); if (time != null) { selectedValue(time); @@ -126,74 +146,98 @@ class HeaterTempSettingsState extends State { @override Widget build(final BuildContext context) { + final selected = useState( + configs.bitOr((final x) => dayOfWeekToFlagMap[x.dayOfWeek]!), + ); + final saveNeeded = useState(false); + final value = useState(0.0); + final annotationValue = useState(""); + final selectedDate = useState(timeTemp.$1!); + useEffect(() { + _setPointerValue(timeTemp.$2!, value, annotationValue); + return null; + }, [timeTemp.$2]); + return Scaffold( key: _scaffoldKey, - appBar: AppBar( - title: const Text("Temperatur Einstellungen"), - ), + appBar: AppBar(title: const Text("Temperatur Einstellungen")), floatingActionButton: FloatingActionButton( child: const Icon(Icons.save), - onPressed: () => _handleSubmitted(), + onPressed: () => _handleSubmitted( + context, + saveNeeded.value, + selectedDate.value, + selected.value, + value.value, + ), ), body: Container( decoration: ThemeManager.getBackgroundDecoration(context), child: Form( key: _formKey, - onWillPop: _onWillPop, + canPop: !saveNeeded.value, + onPopInvokedWithResult: (final didPop, final result) async { + if (!didPop) await _onWillPop(context, saveNeeded); + }, // autovalidate: _autovalidate, child: ListView( padding: const EdgeInsets.all(16.0), children: //heaterConfigs!.map((x) => heaterConfigToWidget(x)).toList() - [ + [ Wrap( - children: weekdayChips(), + spacing: 8.0, + runSpacing: 8.0, + children: [ + ElevatedButton( + onPressed: () { + selected.value = selected.value == 0x7F ? 0 : 0x7F; + saveNeeded.value = true; + }, + child: Text("Alle Tage"), + ), + ElevatedButton( + onPressed: () { + selected.value = (selected.value & 0x1f) == 0x1F + ? selected.value & 0x60 + : selected.value | 0x1F; + saveNeeded.value = true; + }, + child: Text("Wochentage"), + ), + ElevatedButton( + onPressed: () { + selected.value = (selected.value & 0x60) == 0x60 + ? selected.value & 0x1F + : selected.value | 0x60; + saveNeeded.value = true; + }, + child: Text("Wochenende"), + ), + ], ), + Wrap(children: weekdayChips(context, selected, saveNeeded)), Container( margin: const EdgeInsets.only(top: 32.0), child: ElevatedButton( - onPressed: () => displayTimePicker(context, initialDate, ((final time) => initialDate = time)), - child: Text("$initialDate")), + onPressed: () => displayTimePicker( + context, + selectedDate.value, + ((final time) { + selectedDate.value = time; + saveNeeded.value = true; + }), + ), + child: Text( + "Uhrzeit einstellen: ${selectedDate.value.format(context)}", + ), + ), ), - // DateTimePicker( - // initialValue: initialDate, - // type: DateTimePickerType.time, - // textAlign: TextAlign.center, - // onChanged: (final val) { - // initialDate = val; - // _saveNeeded = true; - // }, - // style: const TextStyle(fontSize: 24), - // ), Container( margin: const EdgeInsets.only(top: 16.0), child: Stack( children: [ SfRadialGauge( axes: [ - RadialAxis( - startAngle: 150, - endAngle: 30, - radiusFactor: 0.9, - minimum: 5, - maximum: 35, - interval: 1, - axisLineStyle: const AxisLineStyle( - gradient: - SweepGradient(colors: [Colors.blue, Colors.amber, Colors.red], stops: [0.3, 0.5, 1]), - color: Colors.red, - thickness: 0.04, - thicknessUnit: GaugeSizeUnit.factor), - tickOffset: 0.02, - ticksPosition: ElementsPosition.outside, - labelOffset: 0.05, - offsetUnit: GaugeSizeUnit.factor, - showAxisLine: false, - showLabels: false, - labelsPosition: ElementsPosition.outside, - minorTicksPerInterval: 10, - minorTickStyle: const MinorTickStyle(length: 0.1), - majorTickStyle: const MajorTickStyle(length: 0.05, lengthUnit: GaugeSizeUnit.factor), - ), RadialAxis( startAngle: 150, endAngle: 30, @@ -202,20 +246,29 @@ class HeaterTempSettingsState extends State { maximum: 35, interval: 5, axisLineStyle: const AxisLineStyle( - gradient: - SweepGradient(colors: [Colors.blue, Colors.amber, Colors.red], stops: [0.3, 0.5, 1]), - color: Colors.red, - thickness: 0.04, - thicknessUnit: GaugeSizeUnit.factor), + gradient: SweepGradient( + colors: [Colors.blue, Colors.amber, Colors.red], + stops: [0.3, 0.5, 1], + ), + color: Colors.red, + thickness: 0.04, + thicknessUnit: GaugeSizeUnit.factor, + ), tickOffset: 0.02, ticksPosition: ElementsPosition.outside, labelOffset: 0.05, offsetUnit: GaugeSizeUnit.factor, - onAxisTapped: handlePointerValueChangedEnd, + onAxisTapped: (final v) => + handlePointerValueChangedEnd( + v, + value, + annotationValue, + saveNeeded, + ), labelsPosition: ElementsPosition.outside, - minorTicksPerInterval: 0, - minorTickStyle: const MinorTickStyle(length: 0.1), - majorTickStyle: const MajorTickStyle(length: 0.05, lengthUnit: GaugeSizeUnit.factor), + minorTicksPerInterval: 5, + minorTickStyle: const MinorTickStyle(length: 1), + majorTickStyle: const MajorTickStyle(length: 5), pointers: [ // RangePointer( // color: Colors.transparent, @@ -227,33 +280,74 @@ class HeaterTempSettingsState extends State { // cornerStyle: CornerStyle.endCurve, // width: 0.055, // sizeUnit: GaugeSizeUnit.factor), + + // MarkerPointer( + // value: _value, + // overlayColor: + // const Color.fromRGBO(202, 94, 230, 0.125), + // onValueChanged: handlePointerValueChanged, + // onValueChangeEnd: handlePointerValueChanged, + // onValueChanging: handlePointerValueChanging, + // enableDragging: true, + // elevation: 5, + // color: Colors.white, + // borderWidth: 3, + // borderColor: Colors.black, + // markerHeight: 25, + // markerWidth: 25, + // markerType: MarkerType.circle, + // ), MarkerPointer( - value: _value, + value: value.value, elevation: 1, markerOffset: -20, markerHeight: 25, markerWidth: 20, enableDragging: true, - onValueChanged: handlePointerValueChanged, - onValueChangeEnd: handlePointerValueChangedEnd, - onValueChanging: handlePointerValueChanging, + onValueChanged: (final v) => + handlePointerValueChangedEnd( + v, + value, + annotationValue, + saveNeeded, + ), + onValueChangeEnd: (final v) => + handlePointerValueChangedEnd( + v, + value, + annotationValue, + saveNeeded, + ), + onValueChanging: (final v) => + handlePointerValueChanging( + v, + value, + annotationValue, + saveNeeded, + ), borderColor: Colors.black, borderWidth: 1, color: Colors.white, - ) + ), ], annotations: [ GaugeAnnotation( - widget: Row(mainAxisSize: MainAxisSize.min, children: [ - Text( - _annotationValue, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 56), - ), - const Text( - ' °C', - style: TextStyle(fontSize: 56), - ), - ]), + widget: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + annotationValue.value, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 56, + ), + ), + const Text( + ' °C', + style: TextStyle(fontSize: 56), + ), + ], + ), verticalAlignment: GaugeAlignment.far, angle: 90, positionFactor: 0.1, @@ -268,7 +362,12 @@ class HeaterTempSettingsState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ MaterialButton( - onPressed: () => handlePointerValueChanged(_value - 0.1), + onPressed: () => handlePointerValueChangedEnd( + value.value - 0.1, + value, + annotationValue, + saveNeeded, + ), child: const Text( "−", style: TextStyle( @@ -278,7 +377,12 @@ class HeaterTempSettingsState extends State { ), ), MaterialButton( - onPressed: () => handlePointerValueChanged(_value + 0.1), + onPressed: () => handlePointerValueChangedEnd( + value.value + 0.1, + value, + annotationValue, + saveNeeded, + ), child: const Text( "+", style: TextStyle( @@ -300,7 +404,11 @@ class HeaterTempSettingsState extends State { ); } - List weekdayChips() { + List weekdayChips( + final BuildContext context, + final ValueNotifier selected, + final ValueNotifier saveNeeded, + ) { return dayOfWeekToStringMap.values .map( (final value) => Container( @@ -308,21 +416,30 @@ class HeaterTempSettingsState extends State { child: FilterChip( onSelected: (final a) { if (a) { - selected |= dayOfWeekStringToFlagMap[value] ?? 0; + selected.value |= dayOfWeekStringToFlagMap[value] ?? 0; } else { - selected &= ~(dayOfWeekStringToFlagMap[value] ?? 0); + selected.value &= ~(dayOfWeekStringToFlagMap[value] ?? 0); } - _saveNeeded = true; - setState(() {}); + saveNeeded.value = true; }, - selected: selected & (dayOfWeekStringToFlagMap[value] ?? 0) > 0, - showCheckmark: false, + selected: + selected.value & (dayOfWeekStringToFlagMap[value] ?? 0) > 0, + showCheckmark: true, labelStyle: TextStyle( - color: (selected & (dayOfWeekStringToFlagMap[value] ?? 0) < 1 + color: + (selected.value & (dayOfWeekStringToFlagMap[value] ?? 0) < 1 ? Theme.of(context).textTheme.bodyLarge!.color - : (Theme.of(context).colorScheme.secondary.computeLuminance() > 0.5 ? Colors.black : Colors.white)), + : (Theme.of( + context, + ).colorScheme.secondary.computeLuminance() > + 0.5 + ? Colors.black + : Colors.white)), + ), + padding: const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 4.0, ), - padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0), selectedColor: Theme.of(context).colorScheme.secondary, label: Text(value), ), @@ -332,13 +449,26 @@ class HeaterTempSettingsState extends State { .toList(growable: false); } - final List weekdayListText = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; + final List weekdayListText = [ + "Mo", + "Di", + "Mi", + "Do", + "Fr", + "Sa", + "So", + ]; final List itemsForDropdown = buildItems(); static List buildItems() { final menuItems = []; for (double d = 5.0; d <= 35.0; d += 0.1) { - menuItems.add(DropdownMenuItem(value: (d * 10).round(), child: Text(d.toStringAsFixed(1)))); + menuItems.add( + DropdownMenuItem( + value: (d * 10).round(), + child: Text(d.toStringAsFixed(1)), + ), + ); } return menuItems; } diff --git a/lib/devices/heater/log_screen.dart b/lib/devices/heater/log_screen.dart index a5bd412..d06ab37 100644 --- a/lib/devices/heater/log_screen.dart +++ b/lib/devices/heater/log_screen.dart @@ -2,13 +2,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:smarthome/controls/blurry_card.dart'; import 'package:smarthome/helper/theme_manager.dart'; +import 'package:riverpod/misc.dart'; + class LogScreen extends ConsumerWidget { final ProviderFamily _logs; final int _id; final GlobalKey _scaffoldKey = GlobalKey(); - LogScreen(this._logs, this._id, {final Key? key}) : super(key: key); + LogScreen(this._logs, this._id, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { @@ -20,7 +22,9 @@ class LogScreen extends ConsumerWidget { ), body: Container( decoration: ThemeManager.getBackgroundDecoration(context), - child: ListView(padding: const EdgeInsets.all(16.0), children: logsArray.map(_logLineWidget).toList())), + child: ListView( + padding: const EdgeInsets.all(16.0), + children: logsArray.map(_logLineWidget).toList())), ); } diff --git a/lib/devices/heater/temp_scheduling.dart b/lib/devices/heater/temp_scheduling.dart index 02980bc..e53bd54 100644 --- a/lib/devices/heater/temp_scheduling.dart +++ b/lib/devices/heater/temp_scheduling.dart @@ -5,7 +5,7 @@ import 'package:smarthome/controls/controls_exporter.dart'; import 'package:smarthome/helper/connection_manager.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; import 'package:smarthome/helper/theme_manager.dart'; -import 'package:tuple/tuple.dart'; +import 'package:flutter_riverpod/legacy.dart'; import 'heater_config.dart'; import 'heater_temp_settings.dart'; @@ -17,12 +17,14 @@ enum DismissDialogAction { save, } -final heaterConfigProvider = StateProvider.family, int>((final ref, final id) { +final heaterConfigProvider = + StateProvider.family, int>((final ref, final id) { return []; }); final _groupedHeaterConfigProvider = - FutureProvider.family, List>, int>((final ref, final id) async { + FutureProvider.family>, int>( + (final ref, final id) async { final configs = ref.watch(heaterConfigProvider(id)); if (configs.isEmpty) { @@ -31,20 +33,26 @@ final _groupedHeaterConfigProvider = final dc = await connection?.invoke("GetConfig", args: [id]); if (dc is! String) return {}; if (dc != "[]") { - final notifier = ref.read(heaterConfigProvider(id).notifier); - notifier.state = List.from(jsonDecode(dc).map((final f) => HeaterConfig.fromJson(f))); + try { + final notifier = ref.read(heaterConfigProvider(id).notifier); + final initState = List.from( + jsonDecode(dc).map((final f) => HeaterConfig.fromJson(f))); + notifier.state = initState; + } catch (e) { + print(e); + } } return {}; } configs.sort((final x, final y) => x.compareTo(y)); - return configs.groupBy((final x) => Tuple2(x.timeOfDay, x.temperature)); + return configs.groupBy((final x) => (x.timeOfDay, x.temperature)); }); const List emptyConfigs = []; class TempScheduling extends ConsumerStatefulWidget { final int id; - const TempScheduling(this.id, {final Key? key}) : super(key: key); + const TempScheduling(this.id, {super.key}); @override TempSchedulingState createState() => TempSchedulingState(); @@ -72,7 +80,8 @@ class TempSchedulingState extends ConsumerState { if (!_saveNeeded) return true; final ThemeData theme = Theme.of(context); - final TextStyle dialogTextStyle = theme.textTheme.titleMedium!.copyWith(color: theme.textTheme.bodySmall!.color); + final TextStyle dialogTextStyle = theme.textTheme.titleMedium! + .copyWith(color: theme.textTheme.bodySmall!.color); return await (showDialog( context: context, @@ -107,11 +116,11 @@ class TempSchedulingState extends ConsumerState { form.save(); final configs = ref.read(heaterConfigProvider(widget.id)); if (!_saveNeeded) { - Navigator.of(context).pop(Tuple2(false, configs)); + Navigator.of(context).pop((false, configs)); return true; } - Navigator.of(context).pop(Tuple2(true, configs)); + Navigator.of(context).pop((true, configs)); } return true; } @@ -122,14 +131,19 @@ class TempSchedulingState extends ConsumerState { key: _scaffoldKey, appBar: AppBar( title: const Text("Temperatur Einstellungen"), - actions: [IconButton(icon: const Icon(Icons.save), onPressed: () => _handleSubmitted())]), + actions: [ + IconButton( + icon: const Icon(Icons.save), + onPressed: () => _handleSubmitted()) + ]), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () async { final res = await Navigator.push( context, - MaterialPageRoute>>( - builder: (final BuildContext context) => HeaterTempSettings(Tuple2(TimeOfDay.now(), 21.0), const []), + MaterialPageRoute<(bool, List)>( + builder: (final BuildContext context) => + HeaterTempSettings((TimeOfDay.now(), 21.0), const []), fullscreenDialog: true)); storeNewTempConfigs(res, []); }, @@ -142,14 +156,26 @@ class TempSchedulingState extends ConsumerState { autovalidateMode: AutovalidateMode.onUserInteraction, child: Consumer( builder: (final context, final ref, final child) { - final hConfig = ref.watch(_groupedHeaterConfigProvider(widget.id)); + final hConfig = + ref.watch(_groupedHeaterConfigProvider(widget.id)); return hConfig.when( data: (final data) { - return ListView( - padding: const EdgeInsets.all(16.0), - children: data.entries.map((final x) => newHeaterConfigToWidget(x.key, x.value)).toList()); + return RefreshIndicator( + onRefresh: () async { + ref.invalidate(_groupedHeaterConfigProvider(widget.id)); + ref.invalidate(heaterConfigProvider(widget.id)); + }, + child: ListView( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.all(16.0), + children: data.entries + .map((final x) => + newHeaterConfigToWidget(x.key, x.value)) + .toList()), + ); }, - error: (final error, final stackTrace) => ListView(padding: const EdgeInsets.all(16.0)), + error: (final error, final stackTrace) => + ListView(padding: const EdgeInsets.all(16.0)), loading: () => Container( margin: const EdgeInsets.only(top: 25), child: const CircularProgressIndicator(), @@ -162,7 +188,8 @@ class TempSchedulingState extends ConsumerState { ); } - Widget newHeaterConfigToWidget(final Tuple2 x, final List value) { + Widget newHeaterConfigToWidget( + final (TimeOfDay?, double?) x, final List value) { return Container( padding: const EdgeInsets.only(top: 8.0), child: BlurryCard( @@ -171,8 +198,10 @@ class TempSchedulingState extends ConsumerState { onPressed: () async { final res = await Navigator.push( context, - MaterialPageRoute>>( - builder: (final BuildContext context) => HeaterTempSettings(x, value), fullscreenDialog: true)); + MaterialPageRoute<(bool, List)>( + builder: (final BuildContext context) => + HeaterTempSettings(x, value), + fullscreenDialog: true)); storeNewTempConfigs(res, value); }, child: Column( @@ -183,14 +212,18 @@ class TempSchedulingState extends ConsumerState { children: [ Expanded( child: Wrap( - children: value.map((final x) => dayOfWeekChip(x.dayOfWeek)).toList(growable: false), + children: value + .map((final x) => dayOfWeekChip(x.dayOfWeek)) + .toList(growable: false), ), ), IconButton( icon: const Icon(Icons.delete), onPressed: () { - final heaterConfigsNotifier = ref.read(heaterConfigProvider(widget.id).notifier); - final heaterConfigs = heaterConfigsNotifier.state.toList(); + final heaterConfigsNotifier = + ref.read(heaterConfigProvider(widget.id).notifier); + final heaterConfigs = + heaterConfigsNotifier.state.toList(); heaterConfigs.removeElements(value); heaterConfigsNotifier.state = heaterConfigs; _saveNeeded = true; @@ -204,14 +237,14 @@ class TempSchedulingState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - x.item1!.format(context), + x.$1!.format(context), style: const TextStyle(fontSize: 18), ), Container( margin: const EdgeInsets.symmetric(horizontal: 8.0), ), Text( - "${x.item2!.toStringAsFixed(1)}°C", + "${x.$2!.toStringAsFixed(1)}°C", style: const TextStyle(fontSize: 18), ), ], @@ -335,20 +368,24 @@ class TempSchedulingState extends ConsumerState { static List buildItems() { final menuItems = []; for (double d = 5.0; d <= 35.0; d += 0.1) { - menuItems.add(DropdownMenuItem(value: (d * 10).round(), child: Text(d.toStringAsFixed(1)))); + menuItems.add(DropdownMenuItem( + value: (d * 10).round(), child: Text(d.toStringAsFixed(1)))); } return menuItems; } - void storeNewTempConfigs(final Tuple2>? res, final List value) { - if (res == null || res.item1 == false) return; - final heaterConfigsNotifier = ref.read(heaterConfigProvider(widget.id).notifier); + void storeNewTempConfigs( + final (bool, List)? res, final List value) { + if (res == null || res.$1 == false) return; + final heaterConfigsNotifier = + ref.read(heaterConfigProvider(widget.id).notifier); final heaterConfigs = heaterConfigsNotifier.state.toList(); heaterConfigs.removeElements(value); - for (final element in res.item2) { - final hc = heaterConfigs - .firstOrNull((final x) => x.dayOfWeek.index == element.dayOfWeek.index && x.timeOfDay == element.timeOfDay); + for (final element in res.$2) { + final hc = heaterConfigs.firstOrDefault((final x) => + x.dayOfWeek.index == element.dayOfWeek.index && + x.timeOfDay == element.timeOfDay); if (hc != null) heaterConfigs.remove(hc); heaterConfigs.add(element); } diff --git a/lib/devices/lamp.dart b/lib/devices/lamp.dart index 5b4c9b0..f60df17 100644 --- a/lib/devices/lamp.dart +++ b/lib/devices/lamp.dart @@ -24,7 +24,6 @@ // // TODO: implement navigateToDevice // } - // } // class _LampState extends State { diff --git a/lib/devices/osramb40rw/osram_b40_rw.dart b/lib/devices/osramb40rw/osram_b40_rw.dart deleted file mode 100644 index ba69843..0000000 --- a/lib/devices/osramb40rw/osram_b40_rw.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:smarthome/devices/device_manager.dart'; - -import '../zigbee/zigbeelamp/zigbee_lamp.dart'; - -class OsramB40RW extends ZigbeeLamp { - OsramB40RW(final int id, final String typeName, final IconData icon) : super(id, typeName, icon); - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.OsramB40RW; - } -} diff --git a/lib/devices/osramplug/osram_plug.dart b/lib/devices/osramplug/osram_plug.dart deleted file mode 100644 index f2ad1e8..0000000 --- a/lib/devices/osramplug/osram_plug.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:smarthome/devices/base_model.dart'; -import 'package:smarthome/devices/device.dart'; -import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/zigbee/zigbee_model.dart'; -import 'package:smarthome/devices/zigbee/zigbee_switch_model.dart'; -import 'package:smarthome/helper/connection_manager.dart'; -import 'package:smarthome/helper/theme_manager.dart'; -import 'package:smarthome/models/message.dart' as sm; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class OsramPlug extends Device { - OsramPlug(final int id, final String typeName, final IconData icon) : super(id, typeName, iconData: icon); - - @override - void navigateToDevice(final BuildContext context) { - Navigator.push(context, MaterialPageRoute(builder: (final BuildContext context) => OsramPlugScreen(this))); - } - - @override - Widget dashboardCardBody() { - return Wrap( - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.spaceEvenly, - children: [ - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(ZigbeeSwitchModel.stateProvider(id)); - return MaterialButton( - child: Text( - "An", - style: (state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => sendToServer(sm.MessageType.Update, sm.Command.On, [], ref.read(hubConnectionProvider)), - ); - }, - ), - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(ZigbeeSwitchModel.stateProvider(id)); - return MaterialButton( - child: Text( - "Aus", - style: !(state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => sendToServer(sm.MessageType.Update, sm.Command.Off, [], ref.read(hubConnectionProvider)), - ); - }, - ), - ], - ); - } - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.OsramPlug; - } -} - -class OsramPlugScreen extends ConsumerWidget { - final OsramPlug device; - const OsramPlugScreen(this.device, {final Key? key}) : super(key: key); - - @override - Widget build(final BuildContext context, final WidgetRef ref) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(device.id)); - return Scaffold( - appBar: AppBar( - title: Text(friendlyName), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: buildBody(ref), - ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.power_settings_new), - onPressed: () { - final state = ref.watch(ZigbeeSwitchModel.stateProvider(device.id)); - device.sendToServer( - sm.MessageType.Update, state ? sm.Command.Off : sm.Command.On, [], ref.read(hubConnectionProvider)); - }), - ); - } - - Widget buildBody(final WidgetRef ref) { - final state = ref.watch(ZigbeeSwitchModel.stateProvider(device.id)); - final available = ref.watch(ZigbeeModel.availableProvider(device.id)); - final linkQuality = ref.watch(ZigbeeModel.linkQualityProvider(device.id)); - - return ListView( - children: [ - ListTile( - title: Text("Angeschaltet: ${state ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verfügbar: ${available ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verbindungsqualität: $linkQuality"), - ), - ], - ); - } -} diff --git a/lib/devices/painless_led_strip/led_strip.dart b/lib/devices/painless_led_strip/led_strip.dart index 7e2b319..8989824 100644 --- a/lib/devices/painless_led_strip/led_strip.dart +++ b/lib/devices/painless_led_strip/led_strip.dart @@ -4,88 +4,123 @@ import 'package:smarthome/controls/gradient_rounded_rect_slider_track_shape.dart import 'package:smarthome/devices/base_model.dart'; import 'package:smarthome/devices/device.dart'; import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/painless_led_strip/led_strip_model.dart'; +import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/helper/connection_manager.dart'; -import 'package:smarthome/models/message.dart' as sm; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; import 'package:smarthome/helper/theme_manager.dart'; -import 'package:smarthome/models/message.dart'; +import 'package:smarthome/models/command.dart'; -class LedStrip extends Device { - LedStrip(final int id, final String typeName, final IconData icon) : super(id, typeName, iconData: icon); +import '../../restapi/swagger.enums.swagger.dart'; + +class LedStrip extends Device { + LedStrip(super.id, super.typeName, final IconData icon) + : super(iconData: icon); final colorModeProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is LedStripModel) return baseModel.colorMode; - return "Off"; + return ref.watch(valueStoreChangedProvider("colorMode", id))?.value + as String? ?? + "Off"; }); @override void navigateToDevice(final BuildContext context) { - Navigator.push(context, MaterialPageRoute(builder: (final BuildContext context) => LedStripScreen(this))); + Navigator.push( + context, + MaterialPageRoute( + builder: (final BuildContext context) => LedStripScreen(this), + ), + ); } @override Widget dashboardCardBody() { - return Column(children: [ - Wrap( - runAlignment: WrapAlignment.spaceEvenly, - alignment: WrapAlignment.center, - children: [ - Consumer( - builder: (final context, final ref, final child) { - final colorMode = ref.watch(colorModeProvider(id)); - return MaterialButton( - child: Text( - "An", - textAlign: TextAlign.center, - style: colorMode != "Off" && colorMode != "Mode" - ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) - : const TextStyle(), - ), - onPressed: () => sendToServer( - sm.MessageType.Update, sm.Command.SingleColor, ["0xFF000000"], ref.read(hubConnectionProvider)), - ); - }, - ), - Consumer( - builder: (final context, final ref, final child) { - final colorMode = ref.watch(colorModeProvider(id)); - return MaterialButton( - child: Text( - "Aus", - textAlign: TextAlign.center, - style: colorMode == "Off" - ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) - : const TextStyle(), - ), - onPressed: () => - sendToServer(sm.MessageType.Update, sm.Command.Off, [], ref.read(hubConnectionProvider)), - ); - }, - ), - ], - ), - Consumer( - builder: (final context, final ref, final child) { - final colorMode = ref.watch(colorModeProvider(id)); - return MaterialButton( - child: Container( + return Column( + children: [ + Wrap( + runAlignment: WrapAlignment.spaceEvenly, + alignment: WrapAlignment.center, + children: [ + Consumer( + builder: (final context, final ref, final child) { + final colorMode = ref.watch(colorModeProvider(id)); + return MaterialButton( + child: Text( + "An", + textAlign: TextAlign.center, + style: colorMode != "Off" && colorMode != "Mode" + ? const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ) + : const TextStyle(), + ), + onPressed: () => sendToServer( + MessageType.update, + Command.singlecolor, + ["0xFF000000"], + ref.read(apiProvider), + ), + ); + }, + ), + Consumer( + builder: (final context, final ref, final child) { + final colorMode = ref.watch(colorModeProvider(id)); + return MaterialButton( + child: Text( + "Aus", + textAlign: TextAlign.center, + style: colorMode == "Off" + ? const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ) + : const TextStyle(), + ), + onPressed: () => sendToServer( + MessageType.update, + Command.off, + [], + ref.read(apiProvider), + ), + ); + }, + ), + ], + ), + Consumer( + builder: (final context, final ref, final child) { + final colorMode = ref.watch(colorModeProvider(id)); + return MaterialButton( + child: Container( margin: const EdgeInsets.only(bottom: 4.0), child: Text( "Essen fertig", textAlign: TextAlign.center, style: colorMode == "Mode" - ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) + ? const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ) : const TextStyle(), - )), - onPressed: () => sendToServer(sm.MessageType.Update, sm.Command.Mode, [], ref.read(hubConnectionProvider)), - ); - }, - ), - (DeviceManager.showDebugInformation ? Text(id.toString()) : Container()) - ]); + ), + ), + onPressed: () => sendToServer( + MessageType.update, + Command.mode, + [], + ref.read(apiProvider), + ), + ); + }, + ), + (DeviceManager.showDebugInformation + ? Text(id.toString()) + : const SizedBox()), + ], + ); } @override @@ -96,44 +131,53 @@ class LedStrip extends Device { class LedStripScreen extends ConsumerStatefulWidget { final LedStrip device; - const LedStripScreen(this.device, {final Key? key}) : super(key: key); + const LedStripScreen(this.device, {super.key}); @override _LedStripScreenState createState() => _LedStripScreenState(); } -final _rgbwProvider = StateProvider.family((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); +final _rgbwProvider = StateProvider.family((final ref, final id) { + final colorNumber = + ref.watch(valueStoreChangedProvider("colorNumber", id))?.value as int? ?? + 0; final rgbw = RGBW(); - if (model is! LedStripModel) return rgbw; - - rgbw.r = (model.colorNumber & 0xFF) >> 0; - rgbw.g = (model.colorNumber & 0xFF00) >> 8; - rgbw.b = (model.colorNumber & 0xFF0000) >> 16; - rgbw.w = (model.colorNumber & 0xFF000000) >> 24; + rgbw.r = (colorNumber & 0xFF) >> 0; + rgbw.g = (colorNumber & 0xFF00) >> 8; + rgbw.b = (colorNumber & 0xFF0000) >> 16; + rgbw.w = (colorNumber & 0xFF000000) >> 24; return rgbw; }); -final _brightnessProvider = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - - return model?.brightness ?? 0; +final _brightnessProvider = StateProvider.family(( + final ref, + final id, +) { + return ref.watch(valueStoreChangedProvider("brightness", id))?.value + as int? ?? + 0; }); -final _colorModeProvider = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - - return model?.colorMode ?? "Off"; +final _colorModeProvider = StateProvider.family(( + final ref, + final id, +) { + return ref.watch(valueStoreChangedProvider("colorMode", id))?.value + as String? ?? + "Off"; }); -final _delayProvider = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - - return model?.delay ?? 0; +final _delayProvider = StateProvider.family((final ref, final id) { + return ref.watch(valueStoreChangedProvider("delay", id))?.value as int? ?? 0; +}); +final _numLedsProvider = StateProvider.family((final ref, final id) { + return ref.watch(valueStoreChangedProvider("numberOfLeds", id))?.value + as int? ?? + 0; }); -final _numLedsProvider = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - return model?.numberOfLeds ?? 0; +final _reverseProvider = StateProvider.family((final ref, final id) { + return ref.watch(valueStoreChangedProvider("reverse", id))?.value as bool? ?? + false; }); class _LedStripScreenState extends ConsumerState { @@ -143,74 +187,115 @@ class _LedStripScreenState extends ConsumerState { DateTime dateTime = DateTime.now(); static const int colordelay = 1000; - static const TextStyle selectedTextStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 18); - - void _sliderChange(final WidgetRef ref, final Function f, final int dateTimeMilliseconds, final T val) { - final sendToServer = DateTime.now().isAfter(dateTime.add(Duration(milliseconds: dateTimeMilliseconds))); + static const TextStyle selectedTextStyle = TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ); + + void _sliderChange( + final WidgetRef ref, + final Function f, + final int dateTimeMilliseconds, + final T val, + ) { + final sendToServer = DateTime.now().isAfter( + dateTime.add(Duration(milliseconds: dateTimeMilliseconds)), + ); if (sendToServer) { dateTime = DateTime.now(); } Function.apply(f, [ref, val, sendToServer]); } - void _colorModeChange(final WidgetRef ref, final Command newMode, [final bool sendToServer = true]) { - final oldMode = ref.watch(_colorModeProvider(widget.device).notifier); + void _colorModeChange( + final WidgetRef ref, + final Command newMode, [ + final bool sendToServer = true, + ]) { + final oldMode = ref.watch(_colorModeProvider(widget.device.id).notifier); oldMode.state = newMode.name; - if (sendToServer) widget.device.sendToServer(sm.MessageType.Update, newMode, [], ref.read(hubConnectionProvider)); + if (sendToServer) { + widget.device.sendToServer( + MessageType.update, + newMode, + [], + ref.read(apiProvider), + ); + } } - void _changeColor(final WidgetRef ref, final RGBW rgbw, [final bool sendToServer = true]) { - final oldMode = ref.watch(_rgbwProvider(widget.device).notifier); + void _changeColor( + final WidgetRef ref, + final RGBW rgbw, [ + final bool sendToServer = true, + ]) { + final oldMode = ref.watch(_rgbwProvider(widget.device.id).notifier); oldMode.state = rgbw; if (sendToServer) { - widget.device.sendToServer(sm.MessageType.Options, sm.Command.Color, - ["0x${rgbw.hw + rgbw.hb + rgbw.hg + rgbw.hr}"], ref.read(hubConnectionProvider)); + widget.device.sendToServer(MessageType.options, Command.color, [ + "0x${rgbw.hw + rgbw.hb + rgbw.hg + rgbw.hr}", + ], ref.read(apiProvider)); } } - void _changeDelay(final WidgetRef ref, final int delay, [final bool sendToServer = true]) { + void _changeDelay( + final WidgetRef ref, + final int delay, [ + final bool sendToServer = true, + ]) { if (sendToServer) { - widget.device.sendToServer( - sm.MessageType.Options, sm.Command.Delay, ["0x${delay.toRadixString(16)}"], ref.read(hubConnectionProvider)); + widget.device.sendToServer(MessageType.options, Command.delay, [ + "0x${delay.toRadixString(16)}", + ], ref.read(apiProvider)); } - final oldDelay = ref.watch(_delayProvider(widget.device).notifier); + final oldDelay = ref.watch(_delayProvider(widget.device.id).notifier); oldDelay.state = delay; } - void _changeNumLeds(final WidgetRef ref, final int numLeds, [final bool sendToServer = true]) { + void _changeNumLeds( + final WidgetRef ref, + final int numLeds, [ + final bool sendToServer = true, + ]) { if (sendToServer) { - widget.device.sendToServer(sm.MessageType.Options, sm.Command.Calibration, - ["0x${(numLeds.toInt()).toRadixString(16)}"], ref.read(hubConnectionProvider)); + widget.device.sendToServer(MessageType.options, Command.calibration, [ + "0x${(numLeds.toInt()).toRadixString(16)}", + ], ref.read(apiProvider)); } - final oldNumLeds = ref.watch(_numLedsProvider(widget.device).notifier); + final oldNumLeds = ref.watch(_numLedsProvider(widget.device.id).notifier); oldNumLeds.state = numLeds; } - void _changeBrightness(final WidgetRef ref, final int brightness, [final bool sendToServer = true]) { + void _changeBrightness( + final WidgetRef ref, + final int brightness, [ + final bool sendToServer = true, + ]) { if (sendToServer) { - widget.device.sendToServer(sm.MessageType.Options, sm.Command.Brightness, - ["0x${(brightness.toInt()).toRadixString(16)}"], ref.read(hubConnectionProvider)); + widget.device.sendToServer(MessageType.options, Command.brightness, [ + "0x${(brightness.toInt()).toRadixString(16)}", + ], ref.read(apiProvider)); } - final oldBrightness = ref.watch(_brightnessProvider(widget.device).notifier); + final oldBrightness = ref.watch( + _brightnessProvider(widget.device.id).notifier, + ); oldBrightness.state = brightness; } @override Widget build(final BuildContext context) { - final model = ref.read(widget.device.baseModelTProvider(widget.device.id)); - if (model is! LedStripModel) return Container(); return Scaffold( - appBar: AppBar( - title: const Text("LED Strip "), - ), + appBar: AppBar(title: const Text("LED Strip ")), body: Container( decoration: ThemeManager.getBackgroundDecoration(context), child: ListView( children: [ Consumer( builder: (final context, final ref, final child) { - final colorMode = ref.watch(_colorModeProvider(widget.device)); + final colorMode = ref.watch( + _colorModeProvider(widget.device.id), + ); return ListView( physics: const ClampingScrollPhysics(), shrinkWrap: true, @@ -218,85 +303,64 @@ class _LedStripScreenState extends ConsumerState { ListTile( title: Center( child: colorMode == "Off" - ? const Text( - 'Off', - style: selectedTextStyle, - ) + ? const Text('Off', style: selectedTextStyle) : const Text("Off"), ), - onTap: () => _colorModeChange(ref, sm.Command.Off), + onTap: () => _colorModeChange(ref, Command.off), trailing: const Text(""), ), ListTile( title: Center( child: colorMode == "RGB" - ? const Text( - 'Fast RGB', - style: selectedTextStyle, - ) + ? const Text('Fast RGB', style: selectedTextStyle) : const Text("Fast RGB"), ), - onTap: () => _colorModeChange(ref, sm.Command.RGB), + onTap: () => _colorModeChange(ref, Command.rgb), trailing: const Text(""), ), ListTile( title: Center( child: colorMode == "Mode" - ? const Text( - 'Flicker', - style: selectedTextStyle, - ) + ? const Text('Flicker', style: selectedTextStyle) : const Text("Flicker"), ), - onTap: () => _colorModeChange(ref, sm.Command.Mode), + onTap: () => _colorModeChange(ref, Command.mode), trailing: const Text(""), ), ListTile( title: Center( child: colorMode == "Strobo" - ? const Text( - 'Strobo', - style: selectedTextStyle, - ) + ? const Text('Strobo', style: selectedTextStyle) : const Text("Strobo"), ), - onTap: () => _colorModeChange(ref, sm.Command.Strobo), + onTap: () => _colorModeChange(ref, Command.strobo), trailing: const Text(""), ), ListTile( title: Center( child: colorMode == "RGBCycle" - ? const Text( - 'RGBCycle', - style: selectedTextStyle, - ) + ? const Text('RGBCycle', style: selectedTextStyle) : const Text("RGBCycle"), ), - onTap: () => _colorModeChange(ref, sm.Command.RGBCycle), + onTap: () => _colorModeChange(ref, Command.rgbcycle), trailing: const Text(""), ), ListTile( title: Center( child: colorMode == "LightWander" - ? const Text( - 'Wander', - style: selectedTextStyle, - ) + ? const Text('Wander', style: selectedTextStyle) : const Text("Wander"), ), - onTap: () => _colorModeChange(ref, sm.Command.LightWander), + onTap: () => _colorModeChange(ref, Command.lightwander), trailing: const Text(""), ), ListTile( title: Center( child: colorMode == "RGBWander" - ? const Text( - 'Wander RGB', - style: selectedTextStyle, - ) + ? const Text('Wander RGB', style: selectedTextStyle) : const Text("Wander RGB"), ), - onTap: () => _colorModeChange(ref, sm.Command.RGBWander), + onTap: () => _colorModeChange(ref, Command.rgbwander), trailing: const Text(""), ), ], @@ -306,21 +370,24 @@ class _LedStripScreenState extends ConsumerState { Consumer( builder: (final context, final ref, final child) { final colorMode = ref.watch( - widget.device.baseModelTProvider(widget.device.id).select((final value) => value!.colorMode)); - final colorNumber = ref.watch( - widget.device.baseModelTProvider(widget.device.id).select((final value) => value!.colorNumber)); + _colorModeProvider(widget.device.id), + ); + final colorNumber = ref.watch(_rgbwProvider(widget.device.id)); return ListTile( title: Center( - child: (colorMode == "SingleColor" && colorNumber == 0xFF000000) - ? const Text( - 'White', - style: selectedTextStyle, - ) + child: + (colorMode == "SingleColor" && + colorNumber.rawValue == 0xFF000000) + ? const Text('White', style: selectedTextStyle) : const Text("White"), ), onTap: () { widget.device.sendToServer( - sm.MessageType.Update, sm.Command.SingleColor, ["0xFF000000"], ref.read(hubConnectionProvider)); + MessageType.update, + Command.singlecolor, + ["0xFF000000"], + ref.read(apiProvider), + ); }, trailing: const Text(""), ); @@ -328,112 +395,136 @@ class _LedStripScreenState extends ConsumerState { ), Consumer( builder: (final context, final ref, final child) { - final reversed = ref - .watch(widget.device.baseModelTProvider(widget.device.id).select((final value) => value!.reverse)); + final reversed = ref.watch(_reverseProvider(widget.device.id)); return ListTile( title: Center( child: reversed - ? const Text( - 'Reverse', - style: selectedTextStyle, - ) + ? const Text('Reverse', style: selectedTextStyle) : const Text("Reverse"), ), - onTap: () => widget.device - .sendToServer(sm.MessageType.Options, sm.Command.Reverse, [], ref.read(hubConnectionProvider)), + onTap: () => widget.device.sendToServer( + MessageType.options, + Command.reverse, + [], + ref.read(apiProvider), + ), trailing: const Text(""), ); }, ), Consumer( builder: (final context, final ref, final child) { - final rgbw = ref.watch(_rgbwProvider(widget.device)); + final rgbw = ref.watch(_rgbwProvider(widget.device.id)); final colorMode = ref.watch( - widget.device.baseModelTProvider(widget.device.id).select((final value) => value!.colorMode)); + _colorModeProvider(widget.device.id), + ); return ExpansionTile( - title: (colorMode == "SingleColor" && model.colorNumber != 0xFF000000) - ? const Text( - 'SingleColor', - style: selectedTextStyle, - ) + title: + (colorMode == "SingleColor" && + rgbw.rawValue != 0xFF000000) + ? const Text('SingleColor', style: selectedTextStyle) : const Text("SingleColor"), children: [ ListTile( - subtitle: Text("Red: ${rgbw.hr} | ${rgbw.r}"), - title: Slider( - value: rgbw.dr, - onChanged: (final d) { - final newRGBW = rgbw.copyWith(red: d.round()); - ref.read(_rgbwProvider(widget.device).notifier).state = newRGBW; - _sliderChange(ref, _changeColor, colordelay, newRGBW); - }, - onChangeEnd: (final d) { - final newRGBW = rgbw.copyWith(red: d.round()); - _changeColor(ref, newRGBW); - }, - max: 255.0, - label: 'R', - )), + subtitle: Text("Red: ${rgbw.hr} | ${rgbw.r}"), + title: Slider( + value: rgbw.dr, + onChanged: (final d) { + final newRGBW = rgbw.copyWith(red: d.round()); + ref + .read( + _rgbwProvider(widget.device.id).notifier, + ) + .state = + newRGBW; + _sliderChange(ref, _changeColor, colordelay, newRGBW); + }, + onChangeEnd: (final d) { + final newRGBW = rgbw.copyWith(red: d.round()); + _changeColor(ref, newRGBW); + }, + max: 255.0, + label: 'R', + ), + ), ListTile( - subtitle: Text("Green: ${rgbw.hg} | ${rgbw.g}"), - title: Slider( - value: rgbw.dg, - onChanged: (final d) { - final newRGBW = rgbw.copyWith(green: d.round()); - ref.read(_rgbwProvider(widget.device).notifier).state = newRGBW; - _sliderChange(ref, _changeColor, colordelay, newRGBW); - }, - onChangeEnd: (final d) { - final newRGBW = rgbw.copyWith(green: d.round()); - _changeColor(ref, newRGBW); - }, - max: 255.0, - label: 'G', - )), + subtitle: Text("Green: ${rgbw.hg} | ${rgbw.g}"), + title: Slider( + value: rgbw.dg, + onChanged: (final d) { + final newRGBW = rgbw.copyWith(green: d.round()); + ref + .read( + _rgbwProvider(widget.device.id).notifier, + ) + .state = + newRGBW; + _sliderChange(ref, _changeColor, colordelay, newRGBW); + }, + onChangeEnd: (final d) { + final newRGBW = rgbw.copyWith(green: d.round()); + _changeColor(ref, newRGBW); + }, + max: 255.0, + label: 'G', + ), + ), ListTile( - subtitle: Text("Blue: ${rgbw.hb} | ${rgbw.b}"), - title: Slider( - value: rgbw.db, - onChanged: (final d) { - final newRGBW = rgbw.copyWith(blue: d.round()); - ref.read(_rgbwProvider(widget.device).notifier).state = newRGBW; - _sliderChange(ref, _changeColor, colordelay, newRGBW); - }, - onChangeEnd: (final d) { - final newRGBW = rgbw.copyWith(blue: d.round()); - _changeColor(ref, newRGBW); - }, - max: 255.0, - label: 'B', - )), + subtitle: Text("Blue: ${rgbw.hb} | ${rgbw.b}"), + title: Slider( + value: rgbw.db, + onChanged: (final d) { + final newRGBW = rgbw.copyWith(blue: d.round()); + ref + .read( + _rgbwProvider(widget.device.id).notifier, + ) + .state = + newRGBW; + _sliderChange(ref, _changeColor, colordelay, newRGBW); + }, + onChangeEnd: (final d) { + final newRGBW = rgbw.copyWith(blue: d.round()); + _changeColor(ref, newRGBW); + }, + max: 255.0, + label: 'B', + ), + ), ListTile( - subtitle: Text("White: ${rgbw.hw} | ${rgbw.w}"), - title: Slider( - value: rgbw.dw, - onChanged: (final d) { - final newRGBW = rgbw.copyWith(white: d.round()); - ref.read(_rgbwProvider(widget.device).notifier).state = newRGBW; - _sliderChange(ref, _changeColor, colordelay, newRGBW); - }, - onChangeEnd: (final d) { - final newRGBW = rgbw.copyWith(white: d.round()); - _changeColor(ref, newRGBW); - }, - max: 255.0, - label: 'W', - )), + subtitle: Text("White: ${rgbw.hw} | ${rgbw.w}"), + title: Slider( + value: rgbw.dw, + onChanged: (final d) { + final newRGBW = rgbw.copyWith(white: d.round()); + ref + .read( + _rgbwProvider(widget.device.id).notifier, + ) + .state = + newRGBW; + _sliderChange(ref, _changeColor, colordelay, newRGBW); + }, + onChangeEnd: (final d) { + final newRGBW = rgbw.copyWith(white: d.round()); + _changeColor(ref, newRGBW); + }, + max: 255.0, + label: 'W', + ), + ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - 'Color: ${rgbw.hw + rgbw.hb + rgbw.hg + rgbw.hr}', - ), + Text('Color: ${rgbw.hw + rgbw.hb + rgbw.hg + rgbw.hr}'), MaterialButton( - child: const Text( - 'SingleColor', + child: const Text('SingleColor'), + onPressed: () => widget.device.sendToServer( + MessageType.update, + Command.singlecolor, + ["0x${rgbw.hw + rgbw.hb + rgbw.hg + rgbw.hr}"], + ref.read(apiProvider), ), - onPressed: () => widget.device.sendToServer(sm.MessageType.Update, sm.Command.SingleColor, - ["0x${rgbw.hw + rgbw.hb + rgbw.hg + rgbw.hr}"], ref.read(hubConnectionProvider)), ), ], ), @@ -442,31 +533,37 @@ class _LedStripScreenState extends ConsumerState { }, ), - Consumer(builder: (final context, final ref, final child) { - final delay = ref.watch(_delayProvider(widget.device)); - return ListTile( - subtitle: Text("Delay ${delay.toString()}ms"), - title: Slider( - value: delay.toDouble(), - onChanged: (final d) { - _sliderChange(ref, _changeDelay, 1000, d.round()); - }, - onChangeEnd: (final d) => _changeDelay(ref, d.round()), - max: 1000.0, - label: '${delay.round()}', - ), - ); - }), Consumer( builder: (final context, final ref, final child) { - final res = ref.watch(_brightnessProvider(widget.device)); + final delay = ref.watch(_delayProvider(widget.device.id)); + return ListTile( + subtitle: Text("Delay ${delay.toString()}ms"), + title: Slider( + value: delay.toDouble(), + onChanged: (final d) { + _sliderChange(ref, _changeDelay, 1000, d.round()); + }, + onChangeEnd: (final d) => _changeDelay(ref, d.round()), + max: 1000.0, + label: '${delay.round()}', + ), + ); + }, + ), + Consumer( + builder: (final context, final ref, final child) { + final res = ref.watch(_brightnessProvider(widget.device.id)); return ListTile( subtitle: Text("Brightness ${res.toInt().toString()}"), title: GestureDetector( child: SliderTheme( data: SliderTheme.of(context).copyWith( - trackShape: GradientRoundedRectSliderTrackShape( - LinearGradient(colors: [Colors.grey.shade800, Colors.white]))), + trackShape: GradientRoundedRectSliderTrackShape( + LinearGradient( + colors: [Colors.grey.shade800, Colors.white], + ), + ), + ), child: Slider( value: res.toDouble(), onChanged: (final d) { @@ -483,14 +580,16 @@ class _LedStripScreenState extends ConsumerState { ), Consumer( builder: (final context, final ref, final child) { - final numLeds = ref.watch(_numLedsProvider(widget.device)); + final numLeds = ref.watch(_numLedsProvider(widget.device.id)); return ListTile( subtitle: Text("Num Leds ${numLeds.toInt().toString()}"), title: GestureDetector( child: Slider( - onChangeEnd: (final value) => _changeNumLeds(ref, value.round()), + onChangeEnd: (final value) => + _changeNumLeds(ref, value.round()), value: numLeds.toDouble(), - onChanged: (final d) => _changeNumLeds(ref, d.round(), false), + onChanged: (final d) => + _changeNumLeds(ref, d.round(), false), max: 255.0, label: '${numLeds.round()}', ), @@ -547,6 +646,8 @@ class RGBW { w = val.toInt(); } + int get rawValue => (r << 0) | (g << 8) | (b << 16) | (w << 24); + String _toRadixBase16(final int val) { String ret = val.toRadixString(16); if (ret.length == 1) ret = "0$ret"; @@ -572,7 +673,12 @@ class RGBW { RGBW.rgb(c.red, c.green, c.blue); } - RGBW copyWith({final int? red, final int? green, final int? blue, final int? white}) { + RGBW copyWith({ + final int? red, + final int? green, + final int? blue, + final int? white, + }) { return RGBW.rgbw(red ?? r, green ?? g, blue ?? b, white ?? w); } } diff --git a/lib/devices/painless_led_strip/led_strip_model.dart b/lib/devices/painless_led_strip/led_strip_model.dart index 7e6fdc0..2763e84 100644 --- a/lib/devices/painless_led_strip/led_strip_model.dart +++ b/lib/devices/painless_led_strip/led_strip_model.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/base_model.dart'; import '../connection_base_model.dart'; @@ -35,7 +34,8 @@ class LedStripModel extends ConnectionBaseModel { final bool isConnected) : super(id, friendlyName, typeName, isConnected); - factory LedStripModel.fromJson(final Map json) => _$LedStripModelFromJson(json); + factory LedStripModel.fromJson(final Map json) => + _$LedStripModelFromJson(json); @override Map toJson() => _$LedStripModelToJson(this); @@ -45,19 +45,10 @@ class LedStripModel extends ConnectionBaseModel { return LedStripModel.fromJson(json); } - @override - BaseModel updateFromJson(final Map json) { - final updatedModel = getModelFromJson(json); - bool updated = false; - if (updatedModel != this) { - updated = true; - } - return updated ? updatedModel : this; - } @override - int get hashCode => - Object.hash(super.hashCode, colorMode, delay, numberOfLeds, brightness, step, reverse, colorNumber, version); + int get hashCode => Object.hash(super.hashCode, colorMode, delay, + numberOfLeds, brightness, step, reverse, colorNumber, version); @override bool operator ==(final Object other) => diff --git a/lib/devices/property_info.dart b/lib/devices/property_info.dart new file mode 100644 index 0000000..cd7821e --- /dev/null +++ b/lib/devices/property_info.dart @@ -0,0 +1,467 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:smarthome/restapi/swagger.swagger.dart'; + +part 'property_info.g.dart'; + +@JsonSerializable(explicitToJson: true) +class DashboardPropertyInfo extends LayoutBasePropertyInfo { + const DashboardPropertyInfo({ + required this.specialType, + required super.name, + required super.order, + super.textStyle, + super.editInfo, + super.rowNr, + required super.unitOfMeasurement, + required super.format, + super.showOnlyInDeveloperMode, + super.deviceId, + super.expanded, + super.precision, + super.extensionData, + required super.displayName, + }); + + factory DashboardPropertyInfo.fromJson(final Map json) => + _$DashboardPropertyInfoFromJson(json); + + static const toJsonFactory = _$DashboardPropertyInfoToJson; + @override + Map toJson() => _$DashboardPropertyInfoToJson(this); + + @JsonKey( + name: 'specialType', + toJson: dasboardSpecialTypeToJson, + fromJson: dasboardSpecialTypeFromJson, + ) + final DasboardSpecialType specialType; + + static const fromJsonFactory = _$DashboardPropertyInfoFromJson; + + @override + bool operator ==(final Object other) { + return identical(this, other) || + ((other is LayoutBasePropertyInfo && super == (other)) && + (other is DashboardPropertyInfo && + (identical(other.specialType, specialType) || + const DeepCollectionEquality() + .equals(other.specialType, specialType)))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(specialType) ^ + const DeepCollectionEquality().hash(super.hashCode) ^ + runtimeType.hashCode; +} + +extension $DashboardPropertyInfoExtension on DashboardPropertyInfo { + DashboardPropertyInfo copyWith( + {final DasboardSpecialType? specialType, + final String? name, + final int? order, + final TextSettings? textStyle, + final PropertyEditInformation? editInfo, + final int? rowNr, + final String? unitOfMeasurement, + final String? format, + final bool? showOnlyInDeveloperMode, + final int? deviceId, + final bool? expanded, + final int? precision, + final Map? extensionData, + final String? displayName}) { + return DashboardPropertyInfo( + specialType: specialType ?? this.specialType, + name: name ?? this.name, + order: order ?? this.order, + textStyle: textStyle ?? this.textStyle, + editInfo: editInfo ?? this.editInfo, + rowNr: rowNr ?? this.rowNr, + unitOfMeasurement: unitOfMeasurement ?? this.unitOfMeasurement, + format: format ?? this.format, + showOnlyInDeveloperMode: + showOnlyInDeveloperMode ?? this.showOnlyInDeveloperMode, + deviceId: deviceId ?? this.deviceId, + expanded: expanded ?? this.expanded, + precision: precision ?? this.precision, + extensionData: extensionData ?? this.extensionData, + displayName: displayName ?? this.displayName); + } + + DashboardPropertyInfo copyWithWrapped( + {final Wrapped? specialType, + final Wrapped? name, + final Wrapped? order, + final Wrapped? textStyle, + final Wrapped? editInfo, + final Wrapped? rowNr, + final Wrapped? unitOfMeasurement, + final Wrapped? format, + final Wrapped? showOnlyInDeveloperMode, + final Wrapped? deviceId, + final Wrapped? expanded, + final Wrapped? precision, + final Wrapped?>? extensionData, + final Wrapped? displayName}) { + return DashboardPropertyInfo( + specialType: + (specialType != null ? specialType.value : this.specialType), + name: (name != null ? name.value : this.name), + order: (order != null ? order.value : this.order), + textStyle: (textStyle != null ? textStyle.value : this.textStyle), + editInfo: (editInfo != null ? editInfo.value : this.editInfo), + rowNr: (rowNr != null ? rowNr.value : this.rowNr), + unitOfMeasurement: (unitOfMeasurement != null + ? unitOfMeasurement.value + : this.unitOfMeasurement), + format: (format != null ? format.value : this.format), + showOnlyInDeveloperMode: (showOnlyInDeveloperMode != null + ? showOnlyInDeveloperMode.value + : this.showOnlyInDeveloperMode), + deviceId: (deviceId != null ? deviceId.value : this.deviceId), + expanded: (expanded != null ? expanded.value : this.expanded), + precision: (precision != null ? precision.value : this.precision), + extensionData: + (extensionData != null ? extensionData.value : this.extensionData), + displayName: + (displayName != null ? displayName.value : this.displayName)); + } +} + +@JsonSerializable(explicitToJson: true) +class DetailPropertyInfo extends LayoutBasePropertyInfo { + const DetailPropertyInfo({ + this.tabInfoId, + required this.blurryCard, + required this.specialType, + required super.name, + required super.order, + super.textStyle, + super.editInfo, + super.rowNr, + required super.unitOfMeasurement, + required super.format, + super.showOnlyInDeveloperMode, + super.deviceId, + super.expanded, + super.precision, + super.extensionData, + required super.displayName, + }); + + factory DetailPropertyInfo.fromJson(final Map json) => + _$DetailPropertyInfoFromJson(json); + + static const toJsonFactory = _$DetailPropertyInfoToJson; + @override + Map toJson() => _$DetailPropertyInfoToJson(this); + + @JsonKey(name: 'tabInfoId') + final int? tabInfoId; + @JsonKey(name: 'blurryCard') + final bool blurryCard; + @JsonKey(name: 'specialType') + final String specialType; + + static const fromJsonFactory = _$DetailPropertyInfoFromJson; + + @override + bool operator ==(final Object other) { + return identical(this, other) || + (other is LayoutBasePropertyInfo && super == (other)) && + (other is DetailPropertyInfo && + (identical(other.tabInfoId, tabInfoId) || + const DeepCollectionEquality() + .equals(other.tabInfoId, tabInfoId)) && + (identical(other.blurryCard, blurryCard) || + const DeepCollectionEquality() + .equals(other.blurryCard, blurryCard)) && + (identical(other.specialType, specialType) || + const DeepCollectionEquality() + .equals(other.specialType, specialType))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(tabInfoId) ^ + const DeepCollectionEquality().hash(blurryCard) ^ + const DeepCollectionEquality().hash(specialType) ^ + const DeepCollectionEquality().hash(super.hashCode) ^ + runtimeType.hashCode; +} + +extension $DetailPropertyInfoExtension on DetailPropertyInfo { + DetailPropertyInfo copyWith( + {final int? tabInfoId, + final bool? blurryCard, + final String? specialType, + final String? name, + final int? order, + final TextSettings? textStyle, + final PropertyEditInformation? editInfo, + final int? rowNr, + final String? unitOfMeasurement, + final String? format, + final bool? showOnlyInDeveloperMode, + final int? deviceId, + final bool? expanded, + final int? precision, + final Map? extensionData, + final String? displayName}) { + return DetailPropertyInfo( + tabInfoId: tabInfoId ?? this.tabInfoId, + blurryCard: blurryCard ?? this.blurryCard, + specialType: specialType ?? this.specialType, + name: name ?? this.name, + order: order ?? this.order, + textStyle: textStyle ?? this.textStyle, + editInfo: editInfo ?? this.editInfo, + rowNr: rowNr ?? this.rowNr, + unitOfMeasurement: unitOfMeasurement ?? this.unitOfMeasurement, + format: format ?? this.format, + showOnlyInDeveloperMode: + showOnlyInDeveloperMode ?? this.showOnlyInDeveloperMode, + deviceId: deviceId ?? this.deviceId, + expanded: expanded ?? this.expanded, + precision: precision ?? this.precision, + extensionData: extensionData ?? this.extensionData, + displayName: displayName ?? this.displayName); + } + + DetailPropertyInfo copyWithWrapped( + {final Wrapped? tabInfoId, + final Wrapped? blurryCard, + final Wrapped? specialType, + final Wrapped? name, + final Wrapped? order, + final Wrapped? textStyle, + final Wrapped? editInfo, + final Wrapped? rowNr, + final Wrapped? unitOfMeasurement, + final Wrapped? format, + final Wrapped? showOnlyInDeveloperMode, + final Wrapped? deviceId, + final Wrapped? expanded, + final Wrapped? precision, + final Wrapped?>? extensionData, + final Wrapped? displayName}) { + return DetailPropertyInfo( + tabInfoId: (tabInfoId != null ? tabInfoId.value : this.tabInfoId), + blurryCard: (blurryCard != null ? blurryCard.value : this.blurryCard), + specialType: + (specialType != null ? specialType.value : this.specialType), + name: (name != null ? name.value : this.name), + order: (order != null ? order.value : this.order), + textStyle: (textStyle != null ? textStyle.value : this.textStyle), + editInfo: (editInfo != null ? editInfo.value : this.editInfo), + rowNr: (rowNr != null ? rowNr.value : this.rowNr), + unitOfMeasurement: (unitOfMeasurement != null + ? unitOfMeasurement.value + : this.unitOfMeasurement), + format: (format != null ? format.value : this.format), + showOnlyInDeveloperMode: (showOnlyInDeveloperMode != null + ? showOnlyInDeveloperMode.value + : this.showOnlyInDeveloperMode), + deviceId: (deviceId != null ? deviceId.value : this.deviceId), + expanded: (expanded != null ? expanded.value : this.expanded), + precision: (precision != null ? precision.value : this.precision), + extensionData: + (extensionData != null ? extensionData.value : this.extensionData), + displayName: + (displayName != null ? displayName.value : this.displayName)); + } +} + +@JsonSerializable(explicitToJson: true) +class LayoutBasePropertyInfo { + const LayoutBasePropertyInfo({ + required this.name, + required this.order, + this.textStyle, + this.editInfo, + this.rowNr, + required this.unitOfMeasurement, + required this.format, + this.showOnlyInDeveloperMode, + this.deviceId, + this.expanded, + this.precision, + this.extensionData, + required this.displayName, + }); + + factory LayoutBasePropertyInfo.fromJson(final Map json) => + _$LayoutBasePropertyInfoFromJson(json); + + static const toJsonFactory = _$LayoutBasePropertyInfoToJson; + Map toJson() => _$LayoutBasePropertyInfoToJson(this); + + @JsonKey(name: 'name') + final String name; + @JsonKey(name: 'order') + final int order; + @JsonKey(name: 'textStyle') + final TextSettings? textStyle; + @JsonKey(name: 'editInfo') + final PropertyEditInformation? editInfo; + @JsonKey(name: 'rowNr') + final int? rowNr; + @JsonKey(name: 'unitOfMeasurement') + final String unitOfMeasurement; + @JsonKey(name: 'format') + final String format; + @JsonKey(name: 'showOnlyInDeveloperMode') + final bool? showOnlyInDeveloperMode; + @JsonKey(name: 'deviceId') + final int? deviceId; + @JsonKey(name: 'expanded') + final bool? expanded; + @JsonKey(name: 'precision') + final int? precision; + @JsonKey(name: 'extensionData') + final Map? extensionData; + @JsonKey(name: 'displayName') + final String displayName; + static const fromJsonFactory = _$LayoutBasePropertyInfoFromJson; + + @override + bool operator ==(final Object other) { + return identical(this, other) || + (other is LayoutBasePropertyInfo && + (identical(other.name, name) || + const DeepCollectionEquality().equals(other.name, name)) && + (identical(other.order, order) || + const DeepCollectionEquality().equals(other.order, order)) && + (identical(other.textStyle, textStyle) || + const DeepCollectionEquality() + .equals(other.textStyle, textStyle)) && + (identical(other.editInfo, editInfo) || + const DeepCollectionEquality() + .equals(other.editInfo, editInfo)) && + (identical(other.rowNr, rowNr) || + const DeepCollectionEquality().equals(other.rowNr, rowNr)) && + (identical(other.unitOfMeasurement, unitOfMeasurement) || + const DeepCollectionEquality() + .equals(other.unitOfMeasurement, unitOfMeasurement)) && + (identical(other.format, format) || + const DeepCollectionEquality().equals(other.format, format)) && + (identical( + other.showOnlyInDeveloperMode, showOnlyInDeveloperMode) || + const DeepCollectionEquality().equals( + other.showOnlyInDeveloperMode, showOnlyInDeveloperMode)) && + (identical(other.deviceId, deviceId) || + const DeepCollectionEquality() + .equals(other.deviceId, deviceId)) && + (identical(other.expanded, expanded) || + const DeepCollectionEquality() + .equals(other.expanded, expanded)) && + (identical(other.precision, precision) || + const DeepCollectionEquality() + .equals(other.precision, precision)) && + (identical(other.extensionData, extensionData) || + const DeepCollectionEquality() + .equals(other.extensionData, extensionData)) && + (identical(other.displayName, displayName) || + const DeepCollectionEquality() + .equals(other.displayName, displayName))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(name) ^ + const DeepCollectionEquality().hash(order) ^ + const DeepCollectionEquality().hash(textStyle) ^ + const DeepCollectionEquality().hash(editInfo) ^ + const DeepCollectionEquality().hash(rowNr) ^ + const DeepCollectionEquality().hash(unitOfMeasurement) ^ + const DeepCollectionEquality().hash(format) ^ + const DeepCollectionEquality().hash(showOnlyInDeveloperMode) ^ + const DeepCollectionEquality().hash(deviceId) ^ + const DeepCollectionEquality().hash(expanded) ^ + const DeepCollectionEquality().hash(precision) ^ + const DeepCollectionEquality().hash(extensionData) ^ + const DeepCollectionEquality().hash(displayName) ^ + runtimeType.hashCode; +} + +extension $LayoutBasePropertyInfoExtension on LayoutBasePropertyInfo { + LayoutBasePropertyInfo copyWith( + {final String? name, + final int? order, + final TextSettings? textStyle, + final PropertyEditInformation? editInfo, + final int? rowNr, + final String? unitOfMeasurement, + final String? format, + final bool? showOnlyInDeveloperMode, + final int? deviceId, + final bool? expanded, + final int? precision, + final Map? extensionData, + final String? displayName}) { + return LayoutBasePropertyInfo( + name: name ?? this.name, + order: order ?? this.order, + textStyle: textStyle ?? this.textStyle, + editInfo: editInfo ?? this.editInfo, + rowNr: rowNr ?? this.rowNr, + unitOfMeasurement: unitOfMeasurement ?? this.unitOfMeasurement, + format: format ?? this.format, + showOnlyInDeveloperMode: + showOnlyInDeveloperMode ?? this.showOnlyInDeveloperMode, + deviceId: deviceId ?? this.deviceId, + expanded: expanded ?? this.expanded, + precision: precision ?? this.precision, + extensionData: extensionData ?? this.extensionData, + displayName: displayName ?? this.displayName); + } + + LayoutBasePropertyInfo copyWithWrapped( + {final Wrapped? name, + final Wrapped? order, + final Wrapped? textStyle, + final Wrapped? editInfo, + final Wrapped? rowNr, + final Wrapped? unitOfMeasurement, + final Wrapped? format, + final Wrapped? showOnlyInDeveloperMode, + final Wrapped? deviceId, + final Wrapped? expanded, + final Wrapped? precision, + final Wrapped?>? extensionData, + final Wrapped? displayName}) { + return LayoutBasePropertyInfo( + name: (name != null ? name.value : this.name), + order: (order != null ? order.value : this.order), + textStyle: (textStyle != null ? textStyle.value : this.textStyle), + editInfo: (editInfo != null ? editInfo.value : this.editInfo), + rowNr: (rowNr != null ? rowNr.value : this.rowNr), + unitOfMeasurement: (unitOfMeasurement != null + ? unitOfMeasurement.value + : this.unitOfMeasurement), + format: (format != null ? format.value : this.format), + showOnlyInDeveloperMode: (showOnlyInDeveloperMode != null + ? showOnlyInDeveloperMode.value + : this.showOnlyInDeveloperMode), + deviceId: (deviceId != null ? deviceId.value : this.deviceId), + expanded: (expanded != null ? expanded.value : this.expanded), + precision: (precision != null ? precision.value : this.precision), + extensionData: + (extensionData != null ? extensionData.value : this.extensionData), + displayName: + (displayName != null ? displayName.value : this.displayName)); + } +} diff --git a/lib/devices/shared_controls/friendly_name_display.dart b/lib/devices/shared_controls/friendly_name_display.dart index 24fe12c..c765164 100644 --- a/lib/devices/shared_controls/friendly_name_display.dart +++ b/lib/devices/shared_controls/friendly_name_display.dart @@ -6,7 +6,7 @@ import 'package:smarthome/devices/base_model.dart'; class FriendlyNameDisplay extends ConsumerWidget { final int id; - const FriendlyNameDisplay(this.id, {final Key? key}) : super(key: key); + const FriendlyNameDisplay(this.id, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { diff --git a/lib/devices/shared_controls/rgb_slider.dart b/lib/devices/shared_controls/rgb_slider.dart index 6dff995..2d4f299 100644 --- a/lib/devices/shared_controls/rgb_slider.dart +++ b/lib/devices/shared_controls/rgb_slider.dart @@ -61,4 +61,3 @@ // ); // } // } - diff --git a/lib/devices/tradfricontroloutlet/tradfri_control_outlet.dart b/lib/devices/tradfricontroloutlet/tradfri_control_outlet.dart deleted file mode 100644 index 9738e2f..0000000 --- a/lib/devices/tradfricontroloutlet/tradfri_control_outlet.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:smarthome/devices/device_exporter.dart'; -import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/zigbee/zigbee_switch_model.dart'; -import 'package:smarthome/helper/connection_manager.dart'; -import 'package:smarthome/helper/theme_manager.dart'; -import 'package:smarthome/models/message.dart' as sm; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class TradfriControlOutlet extends Device { - TradfriControlOutlet(final int id, final String typeName, final IconData icon) : super(id, typeName, iconData: icon); - - @override - void navigateToDevice(final BuildContext context) { - Navigator.push( - context, MaterialPageRoute(builder: (final BuildContext context) => TradfriControlOutletScreen(this))); - } - - @override - Widget dashboardCardBody() { - return Wrap( - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.spaceEvenly, - children: [ - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(ZigbeeSwitchModel.stateProvider(id)); - return MaterialButton( - child: Text( - "An", - style: (state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => - sendToServer(sm.MessageType.Update, sm.Command.SingleColor, [], ref.read(hubConnectionProvider)), - ); - }, - ), - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(ZigbeeSwitchModel.stateProvider(id)); - return MaterialButton( - child: Text( - "Aus", - style: !(state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => sendToServer(sm.MessageType.Update, sm.Command.Off, [], ref.read(hubConnectionProvider)), - ); - }, - ), - ], - ); - } - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.TradfriControlOutlet; - } -} - -class TradfriControlOutletScreen extends ConsumerStatefulWidget { - final TradfriControlOutlet device; - const TradfriControlOutletScreen(this.device, {final Key? key}) : super(key: key); - - @override - _TradfriControlOutletScreenState createState() => _TradfriControlOutletScreenState(); -} - -class _TradfriControlOutletScreenState extends ConsumerState { - DateTime dateTime = DateTime.now(); - - @override - Widget build(final BuildContext context) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(widget.device.id)); - return Scaffold( - appBar: AppBar( - title: Text(friendlyName), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: buildBody(), - ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.power_settings_new), - onPressed: () { - final state = ref.watch(ZigbeeSwitchModel.stateProvider(widget.device.id)); - widget.device.sendToServer( - sm.MessageType.Update, state ? sm.Command.Off : sm.Command.On, [], ref.read(hubConnectionProvider)); - }), - ); - } - - Widget buildBody() { - final model = ref.watch(widget.device.baseModelTProvider(widget.device.id)); - if (model is! ZigbeeSwitchModel) return Container(); - - return ListView( - children: [ - ListTile( - title: Text("Angeschaltet: ${model.state ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verfügbar: ${model.available ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verbindungsqualität: ${model.linkQuality}"), - ), - ], - ); - } -} diff --git a/lib/devices/tradfriledbulb/tradfri_led_bulb.dart b/lib/devices/tradfriledbulb/tradfri_led_bulb.dart deleted file mode 100644 index 4aee54f..0000000 --- a/lib/devices/tradfriledbulb/tradfri_led_bulb.dart +++ /dev/null @@ -1,275 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:smarthome/controls/gradient_rounded_rect_slider_track_shape.dart'; -import 'package:smarthome/devices/device_exporter.dart'; -import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/shared_controls/shared_controls_exporter.dart'; -import 'package:smarthome/helper/connection_manager.dart'; -import 'package:smarthome/helper/theme_manager.dart'; -import 'package:smarthome/models/message.dart' as sm; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class TradfriLedBulb extends Device { - TradfriLedBulb(final int id, final String typeName, final IconData icon) : super(id, typeName, iconData: icon); - - @override - void navigateToDevice(final BuildContext context) { - Navigator.push(context, MaterialPageRoute(builder: (final BuildContext context) => TradfriLedBulbScreen(this))); - } - - @override - Widget getRightWidgets() { - return Consumer( - builder: (final context, final ref, final child) { - final color = ref.watch(TradfriLedBulbModel.colorProvider(id)); - - final colorNum = int.parse(color.replaceFirst('#', ''), radix: 16); - final r = (colorNum & 0xFF0000) >> 16; - final g = (colorNum & 0xFF00) >> 8; - final b = (colorNum & 0xFF) >> 0; - return Icon(Icons.bubble_chart, color: Color.fromRGBO(r, g, b, 1), size: 24.0); - }, - ); - } - - @override - Widget dashboardCardBody() { - return Wrap( - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.spaceEvenly, - children: [ - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(TradfriLedBulbModel.stateProvider(id)); - return MaterialButton( - child: Text( - "An", - style: (state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => sendToServer(sm.MessageType.Update, sm.Command.On, [], ref.read(hubConnectionProvider)), - ); - }, - ), - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(TradfriLedBulbModel.stateProvider(id)); - return MaterialButton( - child: Text( - "Aus", - style: !(state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => sendToServer(sm.MessageType.Update, sm.Command.Off, [], ref.read(hubConnectionProvider)), - ); - }, - ), - ], - ); - } - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.TradfriLedBulb; - } -} - -@immutable -class TradfriLedBulbScreen extends ConsumerWidget { - final TradfriLedBulb device; - TradfriLedBulbScreen(this.device, {final Key? key}) : super(key: key); - - static final _rgbProvider = StateProvider.family((final ref, final device) { - final color = ref.watch(TradfriLedBulbModel.colorProvider(device.id)); - - final rgb = RGB(); - - final colorNum = int.parse(color.replaceFirst('#', ''), radix: 16); - - rgb.r = (colorNum & 0xFF) >> 0; - rgb.g = (colorNum & 0xFF00) >> 8; - rgb.b = (colorNum & 0xFF0000) >> 16; - return rgb; - }); - - final _brightnessProvider = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - - return model?.brightness ?? 0; - }); - void changeBrightness(final double brightness, final WidgetRef ref) { - device.sendToServer( - sm.MessageType.Update, sm.Command.Brightness, [brightness.round().toString()], ref.read(hubConnectionProvider)); - } - - void changeColor(final RGB rgb, final WidgetRef ref) { - device.sendToServer( - sm.MessageType.Update, sm.Command.Color, ["#${rgb.hr + rgb.hg + rgb.hb}"], ref.read(hubConnectionProvider)); - } - - @override - Widget build(final BuildContext context, final WidgetRef ref) { - return Scaffold( - appBar: AppBar( - title: FriendlyNameDisplay(device.id), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: buildBody(context, ref), - ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.power_settings_new), - onPressed: () { - final state = ref.read(TradfriLedBulbModel.stateProvider(device.id)); - - device.sendToServer( - sm.MessageType.Update, state ? sm.Command.Off : sm.Command.On, [], ref.read(hubConnectionProvider)); - }, - ), - ); - } - - Widget buildBody(final BuildContext context, final WidgetRef ref) { - final model = ref.watch(device.baseModelTProvider(device.id)); - if (model is! TradfriLedBulbModel) return Container(); - - return ListView( - children: [ - ListTile( - title: Text("Angeschaltet: ${model.state ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verfügbar: ${model.available ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verbindungsqualität: ${model.linkQuality}"), - ), - ListTile( - title: Text("Helligkeit aktuell ${model.brightness.toStringAsFixed(0)}"), - subtitle: GestureDetector( - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - trackShape: GradientRoundedRectSliderTrackShape( - LinearGradient(colors: [Colors.grey.shade800, Colors.white]))), - child: Consumer(builder: (final context, final ref, final child) { - final brightness = ref.watch(_brightnessProvider(device)); - return Slider( - value: brightness.toDouble(), - onChanged: (final d) { - ref.read(_brightnessProvider(device).notifier).state = d.round(); - }, - max: 100.0, - divisions: 100, - label: '$brightness', - onChangeEnd: (final c) => changeBrightness(c, ref), - ); - }), - ), - ), - ), - Consumer( - builder: (final context, final ref, final child) { - final rgb = ref.watch(_rgbProvider(device)); - return ListTile( - subtitle: Text("Red: ${rgb.hr} | ${rgb.r}"), - title: Slider( - value: rgb.dr, - onChanged: (final d) { - ref.read(_rgbProvider(device).notifier).state = rgb.cloneWith(red: d.round()); - }, - onChangeEnd: (final a) => changeColor(rgb, ref), - max: 255.0, - label: 'R', - )); - }, - ), - Consumer( - builder: (final context, final ref, final child) { - final rgb = ref.watch(_rgbProvider(device)); - return ListTile( - subtitle: Text("Green: ${rgb.hg} | ${rgb.g}"), - title: Slider( - value: rgb.dg, - onChanged: (final d) { - ref.read(_rgbProvider(device).notifier).state = rgb.cloneWith(green: d.round()); - }, - onChangeEnd: (final a) => changeColor(rgb, ref), - max: 255.0, - label: 'G', - )); - }, - ), - Consumer( - builder: (final context, final ref, final child) { - final rgb = ref.watch(_rgbProvider(device)); - return ListTile( - subtitle: Text("Blue: ${rgb.hb} | ${rgb.b}"), - title: Slider( - value: rgb.db, - onChanged: (final d) { - ref.read(_rgbProvider(device).notifier).state = rgb.cloneWith(blue: d.round()); - }, - onChangeEnd: (final a) => changeColor(rgb, ref), - max: 255.0, - label: 'B', - )); - }, - ), - ], - ); - } -} - -class RGB { - int r = 0; - int g = 0; - int b = 0; - - double get dr => r.toDouble(); - double get dg => g.toDouble(); - double get db => b.toDouble(); - String get hr => _toRadixBase16(r); - String get hg => _toRadixBase16(g); - String get hb => _toRadixBase16(b); - - set dr(final double val) { - r = val.toInt(); - } - - set dg(final double val) { - g = val.toInt(); - } - - set db(final double val) { - b = val.toInt(); - } - - String _toRadixBase16(final int val) { - String ret = val.toRadixString(16); - if (ret.length == 1) ret = "0$ret"; - return ret; - } - - RGB(); - - RGB.rgb(final int red, final int green, final int blue) { - r = red; - g = green; - b = blue; - } - - RGB.color(final Color c) { - RGB.rgb(c.red, c.green, c.blue); - } - - RGB cloneWith({final int red = -1, final int green = -1, final int blue = -1}) { - final rgb = RGB(); - if (red > -1) rgb.r = red; - if (green > -1) rgb.g = green; - if (blue > -1) rgb.b = blue; - return rgb; - } - - @override - bool operator ==(final Object other) => other is RGB && r == other.r && g == other.g && b == other.b; - - @override - int get hashCode => Object.hash(r, g, b); -} diff --git a/lib/devices/tradfriledbulb/tradfri_led_bulb_model.dart b/lib/devices/tradfriledbulb/tradfri_led_bulb_model.dart deleted file mode 100644 index 4a2647c..0000000 --- a/lib/devices/tradfriledbulb/tradfri_led_bulb_model.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/device_exporter.dart'; -import 'package:riverpod/riverpod.dart'; - -part 'tradfri_led_bulb_model.g.dart'; - -@JsonSerializable() -@immutable -class TradfriLedBulbModel extends ZigbeeModel { - final int brightness; - final String color; - final bool state; - - static final brightnessProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TradfriLedBulbModel).brightness; - }); - static final colorProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TradfriLedBulbModel).color; - }); - static final stateProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TradfriLedBulbModel).state; - }); - - @override - bool get isConnected => available; - - const TradfriLedBulbModel(final int id, final String friendlyName, final String typeName, final bool isConnected, - final bool available, final DateTime lastReceived, final int linkQuality, this.brightness, this.color, this.state) - : super(id, friendlyName, typeName, isConnected, available, lastReceived, linkQuality); - factory TradfriLedBulbModel.fromJson(final Map json) => _$TradfriLedBulbModelFromJson(json); - - @override - Map toJson() => _$TradfriLedBulbModelToJson(this); - - @override - BaseModel getModelFromJson(final Map json) { - return TradfriLedBulbModel.fromJson(json); - } - - @override - bool operator ==(final Object other) => - other is TradfriLedBulbModel && - super == other && - brightness == other.brightness && - color == other.color && - state == other.state; - - @override - int get hashCode => Object.hash(super.hashCode, brightness, color, state); -} diff --git a/lib/devices/tradfrimotionsensor/tradfri_motion_sensor.dart b/lib/devices/tradfrimotionsensor/tradfri_motion_sensor.dart deleted file mode 100644 index e2b0fb5..0000000 --- a/lib/devices/tradfrimotionsensor/tradfri_motion_sensor.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:smarthome/devices/device_exporter.dart'; -import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/helper/theme_manager.dart'; -import 'package:smarthome/icons/icons.dart'; -import '../../helper/datetime_helper.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class TradfriMotionSensor extends Device { - TradfriMotionSensor(final int id, final String typeName, final IconData icon) : super(id, typeName, iconData: icon); - - @override - void navigateToDevice(final BuildContext context) { - Navigator.push( - context, MaterialPageRoute(builder: (final BuildContext context) => TradfriMotionSensorScreen(this))); - } - - @override - Widget getRightWidgets() { - return Consumer( - builder: (final context, final ref, final child) { - final battery = ref.watch(TradfriMotionSensorModel.batteryProvider(id)); - return Icon( - (battery > 80 - ? SmarthomeIcons.bat4 - : (battery > 60 - ? SmarthomeIcons.bat3 - : (battery > 40 - ? SmarthomeIcons.bat2 - : (battery > 20 ? SmarthomeIcons.bat1 : SmarthomeIcons.bat_charge)))), - size: 20, - ); - }, - ); - } - - @override - Widget dashboardCardBody() { - return Column( - children: ([ - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Consumer( - builder: (final context, final ref, final child) { - return Text((ref.watch(TradfriMotionSensorModel.occupancyProvider(id)) ? "Blockiert" : "Frei"), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)); - }, - ) - ]), - Container( - height: 2, - ), - Wrap( - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.spaceEvenly, - children: [ - Consumer( - builder: (final context, final ref, final child) { - final lastReceived = ref.watch(ZigbeeModel.lastReceivedProvider(id)); - - return Text( - (lastReceived.millisecondsSinceEpoch == -62135600400000 - ? "" - : lastReceived - .subtract(Duration(seconds: ref.watch(TradfriMotionSensorModel.noMotionProvider(id)))) - .toDate()), - style: const TextStyle()); - }, - ), - ], - ), - ] + - (DeviceManager.showDebugInformation - ? [ - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Consumer( - builder: (final context, final ref, final child) { - return Text(ref.watch(ZigbeeModel.lastReceivedProvider(id)).toDate()); - }, - ), - ]), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(id.toRadixString(16))]), - ] - : [])), - ); - } - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.TradfriMotionSensor; - } -} - -class TradfriMotionSensorScreen extends ConsumerStatefulWidget { - final TradfriMotionSensor device; - const TradfriMotionSensorScreen(this.device, {final Key? key}) : super(key: key); - - @override - TradfriMotionSensorScreenState createState() => TradfriMotionSensorScreenState(); -} - -class TradfriMotionSensorScreenState extends ConsumerState { - DateTime dateTime = DateTime.now(); - - @override - Widget build(final BuildContext context) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(widget.device.id)); - return Scaffold( - appBar: AppBar( - title: Text(friendlyName), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: buildBody(), - ), - ); - } - - Widget buildBody() { - final model = ref.watch(widget.device.baseModelTProvider(widget.device.id)); - - if (model is! TradfriMotionSensorModel) return Container(); - - return ListView( - children: [ - ListTile( - title: Text("Blockiert: ${model.occupancy ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Letzte Bewegung: ${model.lastReceived.subtract(Duration(seconds: model.noMotion)).toDate()}"), - ), - ListTile( - title: Text("Battery: ${model.battery.toStringAsFixed(0)} %"), - ), - ListTile( - title: Text("Verfügbar: ${model.available ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verbindungsqualität: ${model.linkQuality}"), - ), - ListTile( - title: Text("Zuletzt empfangen: ${model.lastReceived.toDate()}"), - ), - ], - ); - } -} diff --git a/lib/devices/tradfrimotionsensor/tradfri_motion_sensor_model.dart b/lib/devices/tradfrimotionsensor/tradfri_motion_sensor_model.dart deleted file mode 100644 index 784657a..0000000 --- a/lib/devices/tradfrimotionsensor/tradfri_motion_sensor_model.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/device_exporter.dart'; -import 'package:riverpod/riverpod.dart'; - -part 'tradfri_motion_sensor_model.g.dart'; - -@JsonSerializable() -class TradfriMotionSensorModel extends ZigbeeModel { - final int battery; - final int noMotion; - final bool occupancy; - - static final batteryProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TradfriMotionSensorModel).battery; - }); - - static final noMotionProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TradfriMotionSensorModel).noMotion; - }); - - static final occupancyProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TradfriMotionSensorModel).occupancy; - }); - - const TradfriMotionSensorModel( - final int id, - final String friendlyName, - final String typeName, - final bool isConnected, - final bool available, - final DateTime lastReceived, - final int linkQuality, - this.battery, - this.noMotion, - this.occupancy) - : super(id, friendlyName, typeName, isConnected, available, lastReceived, linkQuality); - factory TradfriMotionSensorModel.fromJson(final Map json) => - _$TradfriMotionSensorModelFromJson(json); - - @override - Map toJson() => _$TradfriMotionSensorModelToJson(this); - - @override - BaseModel getModelFromJson(final Map json) { - return TradfriMotionSensorModel.fromJson(json); - } - - @override - bool operator ==(final Object other) => - other is TradfriMotionSensorModel && - super == other && - battery == other.battery && - noMotion == other.noMotion && - occupancy == other.occupancy; - - @override - int get hashCode => Object.hash(super.hashCode, battery, noMotion, occupancy); -} diff --git a/lib/devices/xiaomi/temp_sensor.dart b/lib/devices/xiaomi/temp_sensor.dart deleted file mode 100644 index a8083b7..0000000 --- a/lib/devices/xiaomi/temp_sensor.dart +++ /dev/null @@ -1,443 +0,0 @@ -import 'dart:typed_data'; - -import 'package:adaptive_theme/adaptive_theme.dart'; -import 'package:collection/collection.dart' show IterableExtension; -import 'package:flutter/material.dart'; -import 'package:smarthome/devices/device_exporter.dart'; -import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/zigbee/iobroker_history_model.dart'; -import 'package:smarthome/helper/connection_manager.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; -import 'package:smarthome/models/message.dart' as sm; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:intl/intl.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'package:smarthome/icons/icons.dart'; -import 'package:smarthome/helper/theme_manager.dart'; -import '../../helper/datetime_helper.dart'; - -class XiaomiTempSensor extends Device { - XiaomiTempSensor(final int id, final String typeName, {final IconData? icon, final Uint8List? iconBytes}) - : super(id, typeName, iconData: icon, iconBytes: iconBytes); - - @override - void navigateToDevice(final BuildContext context) { - Navigator.push(context, MaterialPageRoute(builder: (final BuildContext context) => XiaomiTempSensorScreen(this))); - } - - @override - Widget getRightWidgets() { - return Consumer( - builder: (final context, final ref, final child) { - final battery = ref.watch(TempSensorModel.batteryProvider(id)); - return Icon( - (battery > 80 - ? SmarthomeIcons.bat4 - : (battery > 60 - ? SmarthomeIcons.bat3 - : (battery > 40 - ? SmarthomeIcons.bat2 - : (battery > 20 ? SmarthomeIcons.bat1 : SmarthomeIcons.bat_charge)))), - size: 20, - ); - }, - ); - } - - @override - Widget dashboardCardBody() { - return Consumer( - builder: (final context, final ref, final child) { - return Column( - children: ([ - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Consumer( - builder: (final context, final ref, final child) { - return Text((ref.watch(TempSensorModel.temperatureProvider(id)).toStringAsFixed(1)), - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)); - }, - ), - const Text(" °C", style: TextStyle(fontSize: 18)) - ]), - Container( - height: 2, - ), - Wrap( - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.spaceEvenly, - children: [ - Consumer( - builder: (final context, final ref, final child) { - return Text(("${ref.watch(TempSensorModel.humidityProvider(id)).toStringAsFixed(0)} %"), - style: const TextStyle()); - }, - ), - Container( - width: 8, - ), - Consumer( - builder: (final context, final ref, final child) { - return Text(("${ref.watch(TempSensorModel.pressureProvider(id)).toStringAsFixed(0)} hPa"), - style: const TextStyle()); - }, - ), - ], - ), - ] + - (DeviceManager.showDebugInformation - ? [ - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Consumer( - builder: (final context, final ref, final child) { - return Text(ref.watch(ZigbeeModel.lastReceivedProvider(id)).toDate()); - }, - ) - ]), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(id.toRadixString(16))]), - ] - : []))); - }, - ); - } - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.XiaomiTempSensor; - } -} - -class XiaomiTempSensorScreen extends ConsumerStatefulWidget { - final XiaomiTempSensor device; - final bool showAppBar; - const XiaomiTempSensorScreen(this.device, {this.showAppBar = true, final Key? key}) : super(key: key); - - @override - _XiaomiTempSensorScreenState createState() => _XiaomiTempSensorScreenState(); -} - -class _XiaomiTempSensorScreenState extends ConsumerState with SingleTickerProviderStateMixin { - late List histories; - late DateTime currentShownTime; - - @override - void initState() { - super.initState(); - currentShownTime = DateTime.now(); - histories = []; - widget.device - .getFromServer( - "GetIoBrokerHistories", [widget.device.id, currentShownTime.toString()], ref.read(hubConnectionProvider)) - .then((final x) { - for (final hist in x) { - histories.add(HistoryModel.fromJson(hist)); - } - setState(() {}); - }); - } - - void changeColor() {} - - void changeDelay(final int delay) { - widget.device - .sendToServer(sm.MessageType.Options, sm.Command.Delay, ["delay=$delay"], ref.read(hubConnectionProvider)); - } - - @override - Widget build(final BuildContext context) { - return DefaultTabController( - length: 4, - child: Scaffold( - appBar: AppBar( - automaticallyImplyLeading: widget.showAppBar, - - // flexibleSpace: Text("Xiaomi Sensor " + this.widget.tempSensor.baseModel.friendlyName), - title: const TabBar( - tabs: [ - Tab(icon: Icon(Icons.home)), - Tab(icon: Icon(SmarthomeIcons.temperature)), - Tab(icon: Icon(Icons.cloud)), - Tab( - icon: Icon( - SmarthomeIcons.wi_barometer, - size: 38.0, - ), - ) - ], - ), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: TabBarView( - children: [ - buildListView(), - buildGraphViewTemp(), - buildGraphViewHumidity(), - buildGraphViewPressure(), - ], - ), - ), - ), - ); - } - - Widget buildListView() { - final model = ref.watch(widget.device.baseModelTProvider(widget.device.id)); - if (model is! TempSensorModel) { - return Container(); - } - return OrientationBuilder( - builder: (final context, final orientation) { - return ListView( - // crossAxisCount: orientation == Orientation.portrait ? 1 : 2, - shrinkWrap: true, - children: (DeviceManager.showDebugInformation - ? [ - ListTile( - title: Text("ID: ${model.id.toRadixString(16)}"), - ), - ] - : []) + - [ - ListTile( - title: Text(model.friendlyName), - ), - ListTile( - title: Text("Temperature: ${model.temperature.toStringAsFixed(2)} °C"), - ), - ListTile( - title: Text("Luftdruck: ${model.pressure.toStringAsFixed(1)} kPa"), - ), - ListTile( - title: Text("Luftfeuchtigkeit: ${model.humidity.toStringAsFixed(2)} %"), - ), - ListTile( - title: Text("Battery: ${model.battery.toStringAsFixed(0)} %"), - ), - ListTile( - title: Text("Verfügbar: ${model.available ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Zuletzt empfangen: ${model.lastReceived.toDate()}"), - ), - ], - ); - }, - ); - } - - Widget buildGraphViewHumidity() { - final h = histories.firstWhereOrNull((final x) => x.propertyName == "humidity"); - if (h != null) { - return buildTimeSeriesRangeAnnotationChart( - h, - " %", - "rel. Luftfeuchtigkeit", - AdaptiveTheme.of(context).brightness == Brightness.light - ? Colors.blueAccent.shade700 - : Colors.blueAccent.shade100); - } - return buildDataMissing(); - } - - Widget buildGraphViewTemp() { - final h = histories.firstWhereOrNull((final x) => x.propertyName == "temperature"); - if (h != null) { - return buildTimeSeriesRangeAnnotationChart(h, " °C", "Temperatur", - AdaptiveTheme.of(context).brightness == Brightness.light ? Colors.redAccent.shade700 : Colors.redAccent); - } - return buildDataMissing(); - } - - Widget buildGraphViewPressure() { - final h = histories.firstWhereOrNull((final x) => x.propertyName == "pressure"); - if (h != null) { - return buildTimeSeriesRangeAnnotationChart( - h, - " hPA", - "Luftdruck", - AdaptiveTheme.of(context).brightness == Brightness.light - ? Colors.greenAccent.shade700 - : Colors.greenAccent.shade400); - } - return buildDataMissing(); - } - - Widget buildDataMissing() { - return Flex( - direction: Axis.vertical, - children: [ - Expanded( - child: Text("Daten werden geladen oder sind nicht vorhanden für ${currentShownTime.day}.${currentShownTime.month}.${currentShownTime.year}")), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: MaterialButton( - child: const Text("Früher"), - onPressed: () { - getNewData(currentShownTime.subtract(const Duration(days: 1))); - }, - )), - Expanded( - child: MaterialButton( - onPressed: () => _openDatePicker(currentShownTime), child: const Text('Datum auswählen')), - ), - Expanded( - child: MaterialButton( - child: const Text("Später"), - onPressed: () { - getNewData(currentShownTime.add(const Duration(days: 1))); - }, - )), - ], - ) - ], - ); - } - - Widget buildTimeSeriesRangeAnnotationChart( - final HistoryModel h, final String unit, final String valueName, final Color lineColor) { - h.historyRecords = h.historyRecords.where((final x) => x.value != null).toList(growable: false); - return Flex( - direction: Axis.vertical, - children: [ - Expanded( - child: TimeSeriesRangeAnnotationChart( - [ - LineSeries( - enableTooltip: true, - animationDuration: 500, - // markerSettings: MarkerSettings(shape: DataMarkerType.circle, color: Colors.green, width: 5, height: 5, isVisible: true), - markerSettings: const MarkerSettings( - isVisible: true, - shape: DataMarkerType.circle, - ), - dataSource: h.historyRecords - .map((final x) => - TimeSeriesValue(DateTime(1970).add(Duration(milliseconds: x.timeStamp)), x.value, lineColor)) - .toList(), - xValueMapper: (final TimeSeriesValue value, final _) => value.time, - yValueMapper: (final TimeSeriesValue value, final _) => value.value, - pointColorMapper: (final TimeSeriesValue value, final _) => value.lineColor, - width: 2) - ], - h.historyRecords - .where((final x) => x.value != null) - .map((final x) => x.value!) - .minBy(10000, (final e) => e) - .toDouble(), - h.historyRecords - .where((final x) => x.value != null) - .map((final x) => x.value!) - .maxBy(0, (final e) => e) - .toDouble(), - unit, - valueName, - currentShownTime, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: MaterialButton( - child: const Text("Früher"), - onPressed: () { - getNewData(currentShownTime.subtract(const Duration(days: 1))); - }, - )), - Expanded( - child: MaterialButton( - onPressed: () => _openDatePicker(currentShownTime), child: const Text('Datum auswählen')), - ), - Expanded( - child: MaterialButton( - child: const Text("Später"), - onPressed: () { - getNewData(currentShownTime.add(const Duration(days: 1))); - }, - )), - ], - ) - ], - ); - } - - void _openDatePicker(final DateTime initial) { - // showDatePicker is a pre-made funtion of Flutter - showDatePicker(context: context, initialDate: initial, firstDate: DateTime(2018), lastDate: DateTime.now()) - .then((final pickedDate) { - // Check if no date is selected - if (pickedDate == null) { - return; - } - getNewData(pickedDate); - }); - } - - getNewData(final DateTime dt) { - if (dt.millisecondsSinceEpoch > DateTime.now().millisecondsSinceEpoch) return; - widget.device - .getFromServer("GetIoBrokerHistories", [widget.device.id, dt.toString()], ref.read(hubConnectionProvider)) - .then((final x) { - currentShownTime = dt; - histories.clear(); - for (final hist in x) { - final histo = HistoryModel.fromJson(hist); - histo.historyRecords = histo.historyRecords.where((final x) => x.value != null).toList(); - histories.add(histo); - } - setState(() {}); - }); - } -} - -class TimeSeriesRangeAnnotationChart extends StatelessWidget { - final List seriesList; - final double min; - final double max; - final String unit; - final String valueName; - final DateTime shownDate; - - const TimeSeriesRangeAnnotationChart(this.seriesList, this.min, this.max, this.unit, this.valueName, this.shownDate, - {final Key? key}) - : super(key: key); - - @override - Widget build(final BuildContext context) { - return OrientationBuilder(builder: (final context, final orientation) { - return SfCartesianChart( - primaryXAxis: DateTimeAxis( - interval: orientation == Orientation.landscape ? 2 : 4, - intervalType: DateTimeIntervalType.hours, - dateFormat: DateFormat("HH:mm"), - majorGridLines: const MajorGridLines(width: 0), - title: AxisTitle(text: DateFormat("dd.MM.yyyy").format(shownDate))), - primaryYAxis: NumericAxis( - minimum: (min - (((max - min) < 10 ? 10 : (max - min)) / 10)).roundToDouble(), - maximum: (max + (((max - min) < 10 ? 10 : (max - min)) / 10)).roundToDouble(), - interval: (((max - min) < 10 ? 10 : (max - min)) / 10).roundToDouble(), - axisLine: const AxisLine(width: 0), - labelFormat: '{value}$unit', - majorTickLines: const MajorTickLines(size: 0), - title: AxisTitle(text: valueName)), - series: seriesList, - trackballBehavior: TrackballBehavior( - enable: true, - activationMode: ActivationMode.singleTap, - tooltipSettings: const InteractiveTooltip(format: '{point.x} : {point.y}')), - ); - }); - } -} - -/// Sample time series data type. -class TimeSeriesValue { - final DateTime time; - final num? value; - final Color lineColor; - - TimeSeriesValue(this.time, this.value, this.lineColor); -} diff --git a/lib/devices/xiaomi/temp_sensor_model.dart b/lib/devices/xiaomi/temp_sensor_model.dart deleted file mode 100644 index 93d13e3..0000000 --- a/lib/devices/xiaomi/temp_sensor_model.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -import 'package:riverpod/riverpod.dart'; -import '../device_exporter.dart'; - -part 'temp_sensor_model.g.dart'; - -@JsonSerializable() -class TempSensorModel extends ZigbeeModel { - final double temperature; - final double humidity; - final double pressure; - final int battery; - - static final temperatureProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TempSensorModel).temperature; - }); - static final humidityProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TempSensorModel).humidity; - }); - static final pressureProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TempSensorModel).pressure; - }); - static final batteryProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as TempSensorModel).battery; - }); - - const TempSensorModel( - final int id, - final String friendlyName, - final String typeName, - final bool isConnected, - final bool available, - final DateTime lastReceived, - final int linkQuality, - this.temperature, - this.humidity, - this.pressure, - this.battery) - : super(id, friendlyName, typeName, isConnected, available, lastReceived, linkQuality); - - factory TempSensorModel.fromJson(final Map json) => _$TempSensorModelFromJson(json); - - @override - BaseModel getModelFromJson(final Map json) { - return TempSensorModel.fromJson(json); - } - - @override - Map toJson() => _$TempSensorModelToJson(this); - - @override - bool operator ==(final Object other) => - other is TempSensorModel && - super == other && - other.temperature == temperature && - other.humidity == humidity && - other.pressure == pressure && - other.battery == battery; - - @override - int get hashCode => Object.hash( - id, friendlyName, isConnected, available, lastReceived, linkQuality, temperature, humidity, pressure, battery); -} diff --git a/lib/devices/zigbee/iobroker_history_model.dart b/lib/devices/zigbee/iobroker_history_model.dart deleted file mode 100644 index b2b1300..0000000 --- a/lib/devices/zigbee/iobroker_history_model.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'iobroker_history_model.g.dart'; - -@JsonSerializable() -class HistoryModel { - late List historyRecords; - String? propertyName; - - HistoryModel(); - factory HistoryModel.fromJson(final Map json) => _$HistoryModelFromJson(json); - - Map toJson() => _$HistoryModelToJson(this); -} - -@JsonSerializable() -class HistoryRecord { - @JsonKey(name: 'val') - num? value; - @JsonKey(name: 'ts') - late int timeStamp; - - HistoryRecord(); - factory HistoryRecord.fromJson(final Map json) => _$HistoryRecordFromJson(json); - - Map toJson() => _$HistoryRecordToJson(this); -} diff --git a/lib/devices/zigbee/zigbee_model.dart b/lib/devices/zigbee/zigbee_model.dart deleted file mode 100644 index e976d0c..0000000 --- a/lib/devices/zigbee/zigbee_model.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/device_exporter.dart'; - -import 'package:riverpod/riverpod.dart'; - -part 'zigbee_model.g.dart'; - -@JsonSerializable() -class ZigbeeModel extends ConnectionBaseModel { - final bool available; - final DateTime lastReceived; - final int linkQuality; - - static final availableProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeModel).available; - }); - static final lastReceivedProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeModel).lastReceived; - }); - static final linkQualityProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeModel).linkQuality; - }); - - @override - bool get isConnected => available; - - const ZigbeeModel(super.id, super.friendlyName, super.typeName, super.isConnected, this.available, this.lastReceived, - this.linkQuality); - factory ZigbeeModel.fromJson(final Map json) => _$ZigbeeModelFromJson(json); - - @override - Map toJson() => _$ZigbeeModelToJson(this); - - @override - BaseModel updateFromJson(final Map json) { - final updatedModel = getModelFromJson(json); - bool updated = false; - if (updatedModel != this) { - updated = true; - } - return updated ? updatedModel : this; - } - - @override - BaseModel getModelFromJson(final Map json) { - return ZigbeeModel.fromJson(json); - } - - @override - bool operator ==(final Object other) => - other is ZigbeeModel && - super == other && - available == other.available && - lastReceived == other.lastReceived && - linkQuality == other.linkQuality; - - @override - int get hashCode => Object.hash(super.hashCode, available, lastReceived, linkQuality); -} diff --git a/lib/devices/zigbee/zigbee_switch_model.dart b/lib/devices/zigbee/zigbee_switch_model.dart deleted file mode 100644 index 5fc0d59..0000000 --- a/lib/devices/zigbee/zigbee_switch_model.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:smarthome/devices/base_model.dart'; -import 'package:smarthome/devices/zigbee/zigbee_model.dart'; -import 'package:riverpod/riverpod.dart'; - -part 'zigbee_switch_model.g.dart'; - -@JsonSerializable() -@immutable -class ZigbeeSwitchModel extends ZigbeeModel { - final bool state; - - static final stateProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeSwitchModel).state; - }); - - const ZigbeeSwitchModel(final int id, final String friendlyName, final String typeName, final bool isConnected, - final bool available, final DateTime lastReceived, final int linkQuality, this.state) - : super(id, friendlyName, typeName, isConnected, available, lastReceived, linkQuality); - - factory ZigbeeSwitchModel.fromJson(final Map json) => _$ZigbeeSwitchModelFromJson(json); - - @override - BaseModel getModelFromJson(final Map json) { - return ZigbeeSwitchModel.fromJson(json); - } - - @override - Map toJson() => _$ZigbeeSwitchModelToJson(this); - - @override - bool operator ==(final Object other) => other is ZigbeeSwitchModel && super == other && state == other.state; - - @override - int get hashCode => Object.hash(super.hashCode, state); -} diff --git a/lib/devices/zigbee/zigbeelamp/zigbee_lamp.dart b/lib/devices/zigbee/zigbeelamp/zigbee_lamp.dart deleted file mode 100644 index d44f961..0000000 --- a/lib/devices/zigbee/zigbeelamp/zigbee_lamp.dart +++ /dev/null @@ -1,238 +0,0 @@ -// ignore_for_file: unnecessary_null_comparison - -import 'package:flutter/material.dart'; -// import 'package:signalr_client/signalr_client.dart'; -import 'package:smarthome/controls/gradient_rounded_rect_slider_track_shape.dart'; -import 'package:smarthome/devices/base_model.dart'; -import 'package:smarthome/devices/device.dart'; -import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/zigbee/zigbee_model.dart'; -import 'package:smarthome/helper/connection_manager.dart'; -import 'package:smarthome/helper/theme_manager.dart'; -import 'package:smarthome/models/message.dart' as sm; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'zigbee_lamp_model.dart'; - -class ZigbeeLamp extends Device { - ZigbeeLamp(final int id, final String typeName, final IconData icon) : super(id, typeName, iconData: icon); - - final stateProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - if (baseModel is ZigbeeLampModel) return baseModel.state; - return false; - }); - - @override - void navigateToDevice(final BuildContext context) { - Navigator.push(context, MaterialPageRoute(builder: (final BuildContext context) => ZigbeeLampScreen(this))); - } - - @override - Widget dashboardCardBody() { - return Wrap( - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.spaceEvenly, - children: [ - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(stateProvider(id)); - return MaterialButton( - child: Text( - "An", - style: (state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => - sendToServer(sm.MessageType.Update, sm.Command.SingleColor, [], ref.read(hubConnectionProvider)), - ); - }, - ), - Consumer( - builder: (final context, final ref, final child) { - final state = ref.watch(stateProvider(id)); - return MaterialButton( - child: Text( - "Aus", - style: !(state) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 20) : const TextStyle(), - ), - onPressed: () => sendToServer(sm.MessageType.Update, sm.Command.Off, [], ref.read(hubConnectionProvider)), - ); - }, - ), - ], - ); - } - - @override - DeviceTypes getDeviceType() { - return DeviceTypes.ZigbeeLamp; - } -} - -class ZigbeeLampScreen extends ConsumerStatefulWidget { - final ZigbeeLamp device; - const ZigbeeLampScreen(this.device, {final Key? key}) : super(key: key); - - @override - _ZigbeeLampScreenState createState() => _ZigbeeLampScreenState(); -} - -class _ZigbeeLampScreenState extends ConsumerState { - DateTime dateTime = DateTime.now(); - - final _brightnessProvider = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - - return model?.brightness ?? 0; - }); - final _colorTemp = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - - return model?.colortemp ?? 0; - }); - final _transitionTime = StateProvider.family>((final ref, final device) { - final model = ref.watch(device.baseModelTProvider(device.id)); - - return model?.transitionTime ?? 0; - }); - - void sliderChange(final Function f, final int dateTimeMilliseconds, [final double? val]) { - if (DateTime.now().isAfter(dateTime.add(Duration(milliseconds: dateTimeMilliseconds)))) { - Function.apply(f, val == null ? [] : [val]); - dateTime = DateTime.now(); - } - } - - void changeDelay(final double? delay) { - widget.device - .sendToServer(sm.MessageType.Options, sm.Command.Delay, [delay.toString()], ref.read(hubConnectionProvider)); - } - - void changeBrightness(final double brightness) { - widget.device.sendToServer( - sm.MessageType.Update, sm.Command.Brightness, [brightness.round().toString()], ref.read(hubConnectionProvider)); - } - - void changeColorTemp(final double colorTemp) { - widget.device.sendToServer( - sm.MessageType.Update, sm.Command.Temp, [colorTemp.round().toString()], ref.read(hubConnectionProvider)); - } - - @override - Widget build(final BuildContext context) { - final friendlyName = ref.watch(BaseModel.friendlyNameProvider(widget.device.id)); - return Scaffold( - appBar: AppBar( - title: Text(friendlyName), - ), - body: Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: buildBody(), - ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.power_settings_new), - onPressed: () { - final state = ref.read(ZigbeeLampModel.stateProvider(widget.device.id)); - - widget.device.sendToServer(sm.MessageType.Update, state ? sm.Command.Off : sm.Command.SingleColor, [], - ref.read(hubConnectionProvider)); - }, - ), - ); - } - - Widget buildBody() { - return ListView( - children: [ - ListTile( - title: Text("Angeschaltet: ${ref.watch(ZigbeeLampModel.stateProvider(widget.device.id)) ? "Ja" : "Nein"}"), - ), - ListTile( - title: Text("Verfügbar: ${ref.watch(ZigbeeModel.availableProvider(widget.device.id)) ? "Ja" : "Nein"}"), - ), - ListTile( - title: - Text("Verbindungsqualität: ${ref.watch(ZigbeeModel.linkQualityProvider(widget.device.id))}"), - ), - ListTile( - title: - Text("Helligkeit ${ref.watch(ZigbeeLampModel.brightnessProvider(widget.device.id)).toStringAsFixed(0)}"), - subtitle: GestureDetector( - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - trackShape: GradientRoundedRectSliderTrackShape( - LinearGradient(colors: [Colors.grey.shade800, Colors.white]))), - child: Consumer( - builder: (final context, final ref, final child) { - final brightness = ref.watch(_brightnessProvider(widget.device)); - return Slider( - value: brightness.toDouble(), - onChanged: (final d) { - ref.read(_brightnessProvider(widget.device).notifier).state = d.round(); - sliderChange(changeBrightness, 500, d); - }, - max: 100.0, - divisions: 100, - label: '$brightness', - ); - }, - ), - ), - onTapCancel: () => - changeBrightness(ref.watch(ZigbeeLampModel.brightnessProvider(widget.device.id)).toDouble()), - ), - ), - ListTile( - title: Text("Farbtemparatur ${(ref.watch(ZigbeeLampModel.colorTempProvider(widget.device.id)) - 204).toStringAsFixed(0)}"), - subtitle: GestureDetector( - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - trackShape: const GradientRoundedRectSliderTrackShape( - LinearGradient(colors: [Color.fromARGB(255, 255, 209, 163), Color.fromARGB(255, 255, 147, 44)]))), - child: Consumer( - builder: (final context, final ref, final child) { - final colorTempClampledProvider = Provider((final ref) { - final colorTemp = ref.watch(_colorTemp(widget.device)); - return ((colorTemp - 204).clamp(0, 204)).toDouble(); - }); - final colorTemp = ref.watch(colorTempClampledProvider); - return Slider( - value: colorTemp, - onChanged: (final d) { - ref.read(_colorTemp(widget.device).notifier).state = d.round(); - sliderChange(changeColorTemp, 500, d + 204.0); - }, - max: 204.0, - divisions: 204, - label: '${colorTemp - 204}', - ); - }, - ), - ), - onTapCancel: () => - changeColorTemp(ref.watch(ZigbeeLampModel.colorTempProvider(widget.device.id)).toDouble()), - ), - ), - ListTile( - title: Text("Übergangszeit ${(ref.watch(ZigbeeLampModel.transitionTimeProvider(widget.device.id)) ?? 0).toStringAsFixed(1)} Sekunden"), - subtitle: GestureDetector( - child: Consumer(builder: (final context, final ref, final child) { - final transitionTime = ref.watch(_transitionTime(widget.device)); - return Slider( - value: transitionTime, - onChanged: (final d) { - ref.read(_transitionTime(widget.device).notifier).state = d; - sliderChange(changeDelay, 500, d); - }, - max: 10.0, - divisions: 100, - label: '$transitionTime', - ); - }), - onTapCancel: () => changeDelay(ref.watch(ZigbeeLampModel.transitionTimeProvider(widget.device.id))), - ), - ), - ], - ); - } -} diff --git a/lib/devices/zigbee/zigbeelamp/zigbee_lamp_model.dart b/lib/devices/zigbee/zigbeelamp/zigbee_lamp_model.dart deleted file mode 100644 index 88e324c..0000000 --- a/lib/devices/zigbee/zigbeelamp/zigbee_lamp_model.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:json_annotation/json_annotation.dart'; - -import '../../device_exporter.dart'; -import 'package:riverpod/riverpod.dart'; - -part 'zigbee_lamp_model.g.dart'; - -@JsonSerializable() -@immutable -class ZigbeeLampModel extends ZigbeeModel { - final int? brightness; - final bool state; - final int? colortemp; - final double? transitionTime; - - static final transitionTimeProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeLampModel).transitionTime; - }); - static final brightnessProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeLampModel).brightness ?? 0; - }); - static final colorTempProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeLampModel).colortemp ?? 0; - }); - static final stateProvider = Provider.family((final ref, final id) { - final baseModel = ref.watch(BaseModel.byIdProvider(id)); - return (baseModel as ZigbeeLampModel).state; - }); - - const ZigbeeLampModel( - final int id, - final String friendlyName, - final String typeName, - final bool isConnected, - final bool available, - final DateTime lastReceived, - final int linkQuality, - this.brightness, - this.state, - this.colortemp, - this.transitionTime) - : super(id, friendlyName, typeName, isConnected, available, lastReceived, linkQuality); - - factory ZigbeeLampModel.fromJson(final Map json) => _$ZigbeeLampModelFromJson(json); - - @override - BaseModel getModelFromJson(final Map json) { - return ZigbeeLampModel.fromJson(json); - } - - @override - Map toJson() => _$ZigbeeLampModelToJson(this); - - @override - int get hashCode => Object.hash( - super.hashCode, - brightness, - state, - colortemp, - transitionTime, - ); - - @override - bool operator ==(final Object other) => - other is ZigbeeLampModel && - super == other && - brightness == other.brightness && - state == other.state && - colortemp == other.colortemp && - transitionTime == other.transitionTime; -} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..ce793c2 --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,86 @@ +// 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: + return macos; + case TargetPlatform.windows: + return windows; + 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 web = FirebaseOptions( + apiKey: 'AIzaSyC-2VRykZ0hZVwh8-I7jqOVR_8tWwu0FIo', + appId: '1:676052672736:web:f2eeb331cf7b65b0d3f085', + messagingSenderId: '676052672736', + projectId: 'smarthome-8582c', + authDomain: 'smarthome-8582c.firebaseapp.com', + storageBucket: 'smarthome-8582c.appspot.com', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyDWDL8YkshmtuyecsKJ2_Q1eq3MBJGjIXg', + appId: '1:676052672736:android:3d2a3ddb0a86f6a6d3f085', + messagingSenderId: '676052672736', + projectId: 'smarthome-8582c', + storageBucket: 'smarthome-8582c.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyApL1meTPPvyFe5r_VUS1Mbr5JFNHIJJrg', + appId: '1:676052672736:ios:52d0eab5daa03da8d3f085', + messagingSenderId: '676052672736', + projectId: 'smarthome-8582c', + storageBucket: 'smarthome-8582c.appspot.com', + iosBundleId: 'de.susch19.smarthome', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyApL1meTPPvyFe5r_VUS1Mbr5JFNHIJJrg', + appId: '1:676052672736:ios:52d0eab5daa03da8d3f085', + messagingSenderId: '676052672736', + projectId: 'smarthome-8582c', + storageBucket: 'smarthome-8582c.appspot.com', + iosBundleId: 'de.susch19.smarthome', + ); + + static const FirebaseOptions windows = FirebaseOptions( + apiKey: 'AIzaSyC-2VRykZ0hZVwh8-I7jqOVR_8tWwu0FIo', + appId: '1:676052672736:web:e6b0a957bf4c91cbd3f085', + messagingSenderId: '676052672736', + projectId: 'smarthome-8582c', + authDomain: 'smarthome-8582c.firebaseapp.com', + storageBucket: 'smarthome-8582c.appspot.com', + ); +} diff --git a/lib/helper/cache_file_manager.dart b/lib/helper/cache_file_manager.dart index 20d36f3..f0702f2 100644 --- a/lib/helper/cache_file_manager.dart +++ b/lib/helper/cache_file_manager.dart @@ -21,7 +21,8 @@ class CacheFileManager { return file.readAsString(); } - Future writeHashCode(final String fileName, final String hashCode) async { + Future writeHashCode( + final String fileName, final String hashCode) async { final file = File(p.join(_path, "$fileName.$_fileExtension.md5")); await file.writeAsString(hashCode); } @@ -42,12 +43,14 @@ class CacheFileManager { return file.readAsBytes(); } - Future writeContentAsString(final String fileName, final String content) async { + Future writeContentAsString( + final String fileName, final String content) async { final file = File(p.join(_path, "$fileName.$_fileExtension")); await file.writeAsString(content); } - Future writeContentAsBytes(final String fileName, final Uint8List content) async { + Future writeContentAsBytes( + final String fileName, final Uint8List content) async { final file = File(p.join(_path, "$fileName.$_fileExtension")); await file.writeAsBytes(content); } diff --git a/lib/helper/connection_manager.dart b/lib/helper/connection_manager.dart index 90bab87..7b6f8e7 100644 --- a/lib/helper/connection_manager.dart +++ b/lib/helper/connection_manager.dart @@ -1,39 +1,35 @@ -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; // import 'package:signalr_core/signalr_core.dart'; // import 'package:signalr_core/signalr_core.dart'; import 'package:signalr_netcore/signalr_client.dart'; import 'package:logging/logging.dart'; -import 'package:smarthome/cloud/app_cloud_configuration.dart'; import 'package:smarthome/devices/base_model.dart'; +import 'package:smarthome/devices/device.dart'; import 'package:smarthome/devices/generic/device_layout_service.dart'; -import 'package:smarthome/helper/preference_manager.dart'; +import 'package:smarthome/devices/generic/stores/store_service.dart'; import 'package:smarthome/helper/settings_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:synchronized/synchronized.dart'; -import '../signalr/smarthome_protocol.dart'; -import 'package:http/http.dart' as http; +import 'package:smarthome/restapi/swagger.swagger.dart' as sw; import 'package:signalr_netcore/iretry_policy.dart'; +part 'connection_manager.g.dart'; + // final hubConnectionStateProvider = StateProvider((final ref) { // return null; // }); -final hubConnectionProvider = StateNotifierProvider((final ref) { - ref.onDispose(() => _emptyHubConnection.stop()); - return ConnectionManager(ref, _emptyHubConnection); -}); - // final hubConnectionProvider = Provider((ref) { // final cont = ref.watch(hubConnectionContainerProvider); // return cont.connection ?? _emptyHubConnection; // }); final hubConnectionConnectedProvider = Provider((final ref) { - final hubConnection = ref.watch(hubConnectionProvider); - + final val = ref.watch(connectionManagerProvider); + final hubConnection = val.value; + if (hubConnection == null) return null; // if (hubConnectionState == HubConnectionState.connected) return hubConnection; if (hubConnection.connectionState == HubConnectionState.Connected) { return hubConnection.connection; @@ -41,40 +37,26 @@ final hubConnectionConnectedProvider = Provider((final ref) { return null; }); -final _emptyHubConnection = HubConnectionBuilder().withUrl("http://localhost:5056/SmartHome").build(); - -final apiProvider = Provider((final ref) => ApiService()); - -final cloudConfigProvider = FutureProvider((final ref) async { - final api = ref.watch(apiProvider); - final urlString = ref.watch(serverUrlProvider); - final url = Uri.parse(urlString); - final res = await api.getSecurityConfig(url.host, url.port); - if (res != null) { - PreferencesManager.instance.setString("cloudConfig", jsonEncode(res)); - return res; - } else { - final cc = PreferencesManager.instance.getString("cloudConfig"); - if (cc != null && cc.isNotEmpty) { - return AppCloudConfiguration.fromJson(jsonDecode(cc))..loadedFromPersistentStorage = true; - } - } - return null; -}); +// final _emptyHubConnection = +// HubConnectionBuilder().withUrl("http://localhost:5056/SmartHome").build(); -class ApiService { - Future getSecurityConfig(final String host, final int port) async { - try { - final response = await http - .get(Uri(scheme: "http", host: host, port: port, path: "/Security")) - .timeout(const Duration(seconds: 10)); - if (response.statusCode == 200) { - return AppCloudConfiguration.fromJson(jsonDecode(response.body)); - } - } catch (ex) {} - return null; - } -} +// final apiProvider = Provider((final ref) => ApiService()); + +// class ApiService { +// Future getSecurityConfig( +// final String host, final int port) async { +// try { +// final uri = +// Uri(scheme: "http", host: host, port: port, path: "/Security"); +// print(uri); +// final response = await http.get(uri).timeout(const Duration(seconds: 10)); +// if (response.statusCode == 200) { +// return AppCloudConfiguration.fromJson(jsonDecode(response.body)); +// } +// } catch (ex) {} +// return null; +// } +// } @immutable class HubConnectionContainer { @@ -83,60 +65,61 @@ class HubConnectionContainer { const HubConnectionContainer(this.connection, this.connectionState); } -class ConnectionManager extends StateNotifier { - static ValueNotifier connectionIconChanged = ValueNotifier(Icons.error_outline); - static final lock = Lock(); +@Riverpod(keepAlive: true) +int _dummyApiRefresh(final Ref ref) => + DateTime.timestamp().millisecondsSinceEpoch; - Ref ref; +@Riverpod(keepAlive: true) +class ConnectionManager extends _$ConnectionManager { + static ValueNotifier connectionIconChanged = + ValueNotifier(Icons.error_outline); - ConnectionManager(this.ref, final HubConnection con) : super(HubConnectionContainer(con, con.state)) { - startConnection(); - } - Future startConnection() async { - if (state.connectionState == HubConnectionState.Connected) state.connection?.stop(); - final cloudConfig = await ref.watch(cloudConfigProvider.future); - SmarthomeProtocol.cloudConfig = cloudConfig; - final newConnectionState = createHubConnection(cloudConfig); + @override + FutureOr build() async { + final serverUrl = ref.watch(serverUrlProvider); + final newConnectionState = createHubConnection(serverUrl); final connection = newConnectionState.connection; - if (connection == null) return; + if (connection == null) return HubConnectionContainer(connection, null); + ref.onDispose(() { + state.value?.connection?.off("Update"); + state.value?.connection?.off("UpdateUi"); + state.value?.connection?.stop(); + }); connection.serverTimeoutInMilliseconds = 30000; - onReconnecting({final Exception? error}) { - connectionIconChanged.value = Icons.error_outline; - connection.off("Update"); - connection.off("UpdateUi"); - state = HubConnectionContainer(connection, connection.state); - // _instance.state = _emptyHubConnection; - } - connection.onreconnecting(onReconnecting); connection.onreconnected(({final String? connectionId}) { connectionIconChanged.value = Icons.check; - connection.on("Update", update); - connection.on("UpdateUi", DeviceLayoutService.updateFromServer); - state = HubConnectionContainer(connection, connection.state); + connection.on("Update", _updateState); + connection.on( + "UpdateUi", ref.read(layoutIconsProvider.notifier).updateFromServer); + ref.invalidate(_dummyApiRefreshProvider); + state = AsyncData(HubConnectionContainer(connection, connection.state)); }); connection.onclose(({final Exception? error}) async { final e = error; if (e == null) return; - state = HubConnectionContainer(connection, connection.state); + state = AsyncData(HubConnectionContainer(connection, connection.state)); while (true) { - if (newConnectionState.connectionState != HubConnectionState.Connected) { + if (newConnectionState.connectionState != + HubConnectionState.Connected) { connectionIconChanged.value = Icons.error_outline; try { connection.stop(); - final newConnection = createHubConnection(cloudConfig); + final newConnection = + createHubConnection(ref.read(serverUrlProvider)); await newConnection.connection?.start(); - state = HubConnectionContainer(connection, connection.state); + state = + AsyncData(HubConnectionContainer(connection, connection.state)); } catch (e) {} sleep(const Duration(seconds: 1)); } else { connectionIconChanged.value = Icons.check; } - // var currentDevices = _ref.read(deviceProvider); + // var currentDevices = _ref.read(deviceManagerProvider); // var dev = await DeviceManager.subscribeToDevice((currentDevices).map((x) => x.id).toList()); // for (var d in currentDevices) { @@ -146,69 +129,62 @@ class ConnectionManager extends StateNotifier { break; } }); - connection.on("Update", update); - connection.on("UpdateUi", DeviceLayoutService.updateFromServer); - final serverUrl = ref.read(serverUrlProvider); + connection.on("Update", _updateState); + connection.on( + "UpdateUi", ref.read(layoutIconsProvider.notifier).updateFromServer); if (serverUrl != "") { await connection.start(); + ref.invalidate(_dummyApiRefreshProvider); connectionIconChanged.value = Icons.check; - state = HubConnectionContainer(connection, connection.state); } + return HubConnectionContainer(connection, connection.state); + } + + void onReconnecting({final Exception? error}) { + connectionIconChanged.value = Icons.error_outline; + final connection = state.value?.connection; + if (connection == null) return; + connection.off("Update"); + connection.off("UpdateUi"); + state = AsyncData(HubConnectionContainer(connection, connection.state)); + // _instance.state = _emptyHubConnection; } Future newHubConnection() async { connectionIconChanged.value = Icons.refresh; + final current = state.value; + if (current != null) { + current.connection?.off("Update"); + current.connection?.off("UpdateUi"); + current.connection?.stop(); + } + final serverUrl = ref.read(serverUrlProvider); - state.connection?.off("Update"); - state.connection?.off("UpdateUi"); - state.connection?.stop(); - state = HubConnectionContainer(state.connection, state.connection?.state); - - final newState = createHubConnection(SmarthomeProtocol.cloudConfig); - newState.connection?.on("Update", update); - newState.connection?.on("UpdateUi", DeviceLayoutService.updateFromServer); + final newState = createHubConnection(serverUrl); + newState.connection?.on("Update", _updateState); + newState.connection?.on( + "UpdateUi", ref.read(layoutIconsProvider.notifier).updateFromServer); await newState.connection?.start(); connectionIconChanged.value = Icons.check; - state = HubConnectionContainer(newState.connection, newState.connection?.state); + state = AsyncData(HubConnectionContainer( + newState.connection, newState.connection?.state)); } - HubConnectionContainer createHubConnection(final AppCloudConfiguration? cloudConfig) { + HubConnectionContainer createHubConnection(final String serverUrl) { Logger.root.level = Level.ALL; // Writes the log messages to the console Logger.root.onRecord.listen((final LogRecord rec) { print('${rec.level.name}: ${rec.time}: ${rec.message}'); }); - final serverUrl = ref.watch(serverUrlProvider); - final serverUri = Uri.parse(serverUrl); return HubConnectionContainer( HubConnectionBuilder() - .withUrl( - cloudConfig?.loadedFromPersistentStorage ?? false - ? Uri( - host: cloudConfig!.host, - port: cloudConfig.port, - pathSegments: [ - ...serverUri.pathSegments, - ...(serverUri.pathSegments.any((final element) => element == cloudConfig.id) - ? [] - : [cloudConfig.id]) - ], - scheme: "http") - .toString() - : serverUrl, - // HttpConnectionOptions( - //accessTokenFactory: () async => await getAccessToken(PreferencesManager.instance), - // logging: (final level, final message) => print('$level: $message')), + .withUrl(serverUrl, options: HttpConnectionOptions( logger: Logger("SignalR - transport"), requestTimeout: 30000, transport: HttpTransportType.WebSockets)) - // .configureLogging(Logger("SignalR - hub")) - // .withHubProtocol(JsonHubProtocol()) - .withHubProtocol(SmarthomeProtocol()) - // .withHubProtocol(MessagePackHubProtocol()) - // .withAutomaticReconnect(PermanentRetryPolicy()) + // .withHubProtocol(SmarthomeProtocol()) .withAutomaticReconnect( reconnectPolicy: PermanentRetryPolicy(), ) @@ -216,29 +192,60 @@ class ConnectionManager extends StateNotifier { HubConnectionState.Disconnected); } - Future update(final List? arguments) async { - await lock.synchronized(() async { - final baseModels = ref.read(baseModelProvider.notifier); - final oldState = baseModels.state.toList(); - bool hasChanges = false; - for (final a in arguments!) { - final updateMap = a as Map; - for (var i = 0; i < oldState.length; i++) { - final oldModel = oldState[i]; - if (oldModel.id != updateMap["id"]) continue; - final newModel = oldModel.updateFromJson(updateMap as Map); - if (oldState[i] == newModel) continue; + Future _updateState(final List? arguments) async { + final oldState = ref.read(baseModelsProvider).toList(); + + bool hasChanges = false; + for (final a in arguments!) { + final updateMap = a as Map; + for (var i = 0; i < oldState.length; i++) { + final oldModel = oldState[i]; + if (oldModel.id != updateMap["id"]) continue; + final typesUntypes = updateMap["typeNames"] as List? ?? []; + final types = typesUntypes.map((final x) => x.toString()).toList(); + ref + .read(stateServiceProvider.notifier) + .updateAndGetStores(oldModel.id, updateMap as Map); + + final newModel = + oldModel.mergeWith(BaseModel.fromJson(updateMap, types)); + if (oldState[i] != newModel) { oldState[i] = newModel; hasChanges = true; } + for (final element in updateMap.keys) { + final now = DateTime.now(); + final from = DateTime.utc(now.year, now.month, now.day) + .subtract(now.timeZoneOffset); + if (ref.exists(historyPropertyNameProvider( + newModel.id, from, from.add(Duration(days: 1)), element))) { + ref.invalidate(historyPropertyNameProvider( + newModel.id, from, from.add(Duration(days: 1)), element)); + } + } } - if (hasChanges) baseModels.state = oldState; - }); + } + if (hasChanges) { + ref.read(baseModelsProvider.notifier).storeModels(oldState); + } } } class PermanentRetryPolicy extends IRetryPolicy { - static const List retryTimes = [100, 200, 500, 1000, 2500, 5000, 7500, 10000, 15000, 20000, 25000, 30000]; + static const List retryTimes = [ + 100, + 200, + 500, + 1000, + 2500, + 5000, + 7500, + 10000, + 15000, + 20000, + 25000, + 30000 + ]; @override int? nextRetryDelayInMilliseconds(final RetryContext retryContext) { @@ -247,3 +254,17 @@ class PermanentRetryPolicy extends IRetryPolicy { return retryTimes[retryCount]; } } + +@Riverpod(keepAlive: true) +class Api extends _$Api { + @override + sw.Swagger build() { + ref.watch(_dummyApiRefreshProvider); + final serverUrl = ref.watch(serverUrlProvider); + final parsed = Uri.parse(serverUrl); + final uri = + Uri(scheme: parsed.scheme, host: parsed.host, port: parsed.port); + + return sw.Swagger.create(baseUrl: uri); + } +} diff --git a/lib/helper/extension_export.dart b/lib/helper/extension_export.dart new file mode 100644 index 0000000..fda62bf --- /dev/null +++ b/lib/helper/extension_export.dart @@ -0,0 +1,2 @@ +export "iterable_extensions.dart"; +export "number_extensions.dart"; diff --git a/lib/helper/firebase_manager.dart b/lib/helper/firebase_manager.dart new file mode 100644 index 0000000..70df4bd --- /dev/null +++ b/lib/helper/firebase_manager.dart @@ -0,0 +1,9 @@ +class FirebaseManager { + // FirebaseManager(){ + // Firebase.initializeApp(options: FirebaseOptions( + // apiKey: apiKey, + // appId: appId, + // messagingSenderId: messagingSenderId, + // projectId: projectId)) + // } +} diff --git a/lib/helper/iterable_extensions.dart b/lib/helper/iterable_extensions.dart index e6c029f..46256ed 100644 --- a/lib/helper/iterable_extensions.dart +++ b/lib/helper/iterable_extensions.dart @@ -76,10 +76,12 @@ extension Iterables on Iterable { return list; } - Map> groupBy(final K Function(E) keyFunction) => fold(>{}, - (final Map> map, final E element) => map..putIfAbsent(keyFunction(element), () => []).add(element)); + Map> groupBy(final K Function(E) keyFunction) => fold( + >{}, + (final Map> map, final E element) => + map..putIfAbsent(keyFunction(element), () => []).add(element)); - Map> groupManyBy(final List Function(E) keyFunction) => + Map> groupManyBy(final Iterable Function(E) keyFunction) => fold(>{}, (final Map> map, final E element) { for (final r in keyFunction(element)) { map.putIfAbsent(r, () => []).add(element); @@ -87,7 +89,7 @@ extension Iterables on Iterable { return map; }); - E? firstOrNull(final bool Function(E element) keyFunction) { + E? firstOrDefault(final bool Function(E element) keyFunction) { for (final item in this) { if (keyFunction(item)) return item; } @@ -137,7 +139,8 @@ extension Iterables on Iterable { } Map toMap( - final TKey Function(E element) keyFunction, final TValue Function(E element) valueFunction) { + final TKey Function(E element) keyFunction, + final TValue Function(E element) valueFunction) { final map = {}; for (final item in [...this]) { map[keyFunction(item)] = valueFunction(item); diff --git a/lib/helper/mdns_manager.dart b/lib/helper/mdns_manager.dart index 866d7d4..dff610c 100644 --- a/lib/helper/mdns_manager.dart +++ b/lib/helper/mdns_manager.dart @@ -11,9 +11,11 @@ import 'package:signalr_netcore/signalr_client.dart'; class MdnsManager { static const String name = '_smarthome._tcp'; - static final MDnsClient _client = MDnsClient(rawDatagramSocketFactory: (final dynamic host, final int port, - {final bool? reuseAddress, final bool? reusePort, final int? ttl}) { - return RawDatagramSocket.bind(host, port, reuseAddress: reuseAddress ?? true, ttl: ttl ?? 255); + static final MDnsClient _client = MDnsClient(rawDatagramSocketFactory: + (final dynamic host, final int port, + {final bool? reuseAddress, final bool? reusePort, final int? ttl}) { + return RawDatagramSocket.bind(host, port, + reuseAddress: reuseAddress ?? true, ttl: ttl ?? 255); }); static bool get initialized => _isInitialized; static bool _isInitialized = false; @@ -32,25 +34,30 @@ class MdnsManager { _isInitialized = false; } - static Future> getInterfaces(final InternetAddressType type) async { + static Future> getInterfaces( + final InternetAddressType type) async { _interfaces = await NetworkInterface.list( type: type, ); return _interfaces; } - static Stream getRecords({final Duration timeToLive = const Duration(minutes: 5)}) async* { + static Stream getRecords( + {final Duration timeToLive = const Duration(minutes: 5)}) async* { if (_lastSearched.difference(DateTime.now()).abs() < timeToLive) { for (final cached in _founded.values) { - if (cached.lastChecked.add(timeToLive).isAfter(DateTime.now())) yield cached; + if (cached.lastChecked.add(timeToLive).isAfter(DateTime.now())) { + yield cached; + } } return; } - await for (final PtrResourceRecord ptr - in _client.lookup(ResourceRecordQuery.serverPointer(name))) { + await for (final PtrResourceRecord ptr in _client + .lookup(ResourceRecordQuery.serverPointer(name))) { await for (final SrvResourceRecord srv - in _client.lookup(ResourceRecordQuery.service(ptr.domainName))) { + in _client.lookup( + ResourceRecordQuery.service(ptr.domainName))) { final instanceNameEndIndex = srv.name.indexOf(name); final instanceName = srv.name.substring(0, instanceNameEndIndex - 1); final List ipPorts = []; @@ -59,21 +66,25 @@ class MdnsManager { String clusterId = instanceName; await for (final IPAddressResourceRecord ipAddr - in _client.lookup(ResourceRecordQuery.addressIPv4(srv.target))) { + in _client.lookup( + ResourceRecordQuery.addressIPv4(srv.target))) { if (await checkConnection(ipAddr, srv)) { - ipPorts.add(IpPort(ipAddr.address.address, srv.port, ipAddr.address.type)); + ipPorts.add( + IpPort(ipAddr.address.address, srv.port, ipAddr.address.type)); } } await for (final IPAddressResourceRecord ipAddr - in _client.lookup(ResourceRecordQuery.addressIPv6(srv.target))) { + in _client.lookup( + ResourceRecordQuery.addressIPv6(srv.target))) { if (await checkConnection(ipAddr, srv)) { - ipPorts.add(IpPort(ipAddr.address.address, srv.port, ipAddr.address.type)); + ipPorts.add( + IpPort(ipAddr.address.address, srv.port, ipAddr.address.type)); } } - await for (final TxtResourceRecord txtRecord - in _client.lookup(ResourceRecordQuery.text(srv.name))) { + await for (final TxtResourceRecord txtRecord in _client + .lookup(ResourceRecordQuery.text(srv.name))) { final keyValues = txtRecord.text.split("\n"); for (final kv in keyValues) { final keyValue = kv.split("="); @@ -92,7 +103,8 @@ class MdnsManager { } if (_founded.containsKey(srv.name)) { final sr = _founded[srv.name]!; - if (sr.lastChecked.difference(DateTime.now()).abs() > const Duration(seconds: 2)) { + if (sr.lastChecked.difference(DateTime.now()).abs() > + const Duration(seconds: 2)) { sr.debug = debug; sr.reachableAddresses = ipPorts; sr.minAppVersion = ver; @@ -102,7 +114,8 @@ class MdnsManager { yield sr; } } else { - final sr = ServerRecord(instanceName, clusterId, srv.name, ipPorts, ver, debug, DateTime.now()); + final sr = ServerRecord(instanceName, clusterId, srv.name, ipPorts, + ver, debug, DateTime.now()); _founded[srv.name] = sr; yield sr; } @@ -118,7 +131,8 @@ class MdnsManager { // } } - static Future checkConnection(final IPAddressResourceRecord ipAddr, final SrvResourceRecord srv) async { + static Future checkConnection( + final IPAddressResourceRecord ipAddr, final SrvResourceRecord srv) async { try { final isIpv6 = ipAddr.address.type.name == "IPv6"; // if (isIpv6) { diff --git a/lib/helper/notification_service.dart b/lib/helper/notification_service.dart new file mode 100644 index 0000000..7094a9c --- /dev/null +++ b/lib/helper/notification_service.dart @@ -0,0 +1,227 @@ +import 'dart:io'; + +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:smarthome/devices/generic/generic_device_exporter.dart'; +import 'package:smarthome/helper/connection_manager.dart'; +import 'package:smarthome/helper/iterable_extensions.dart'; +import 'package:smarthome/helper/number_extensions.dart'; +import 'package:smarthome/helper/preference_manager.dart'; +import 'package:smarthome/models/notification_topic.dart'; +import 'package:smarthome/notifications/app_notification.dart'; + +part 'notification_service.g.dart'; + +FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + +class Callback { + Type get type => T; + + final String id; + final Function(T parameter) func; + + const Callback(this.id, this.func); +} + +@Riverpod(keepAlive: true) +class NotificationService extends _$NotificationService { + static int notificationId = 0; + + static final Map> + _callbacks = {}; + + @override + Future build() async { + final connection = ref.watch(hubConnectionConnectedProvider); + if (connection == null) return; + connection.invoke("activate"); + connection.on("Notify", processNotification); + final topics = PreferencesManager.instance.getNotificationTopics(); + final origLength = topics.length; + final api = ref.read(apiProvider); + final res = await api.notificationAllOneTimeNotificationsGet(); + final allIds = res.bodyOrThrow.topics; + topics.removeWhere((final x) => x.oneTime && !allIds.contains(x.topic)); + if (origLength != topics.length) { + PreferencesManager.instance.setNotificationTopics(topics); + } + } + + void processNotification(final List? arguments) { + final notification = AppNotification.fromJson(arguments!.first as dynamic); + final topics = PreferencesManager.instance.getNotificationTopics(); + final topic = + topics.firstOrDefault((final x) => x.topic == notification.topic); + if (topic == null) return; + if (notification.wasOneTime) { + final idx = topics.indexOf(topic); + topics[idx] = topic.copyWith(enabled: false, topic: ""); + PreferencesManager.instance.setNotificationTopics(topics); + } + + if (notification is VisibleAppNotification) { + flutterLocalNotificationsPlugin.show( + notificationId++, notification.title, notification.body, null); + } + + final callbacks = _callbacks[notification.runtimeType]; + if (callbacks == null || callbacks.isEmpty) return; + + for (final element in callbacks.values) { + element(notification); + } + } + + static String registerCallback( + final String id, final Function(T parameter) func) { + return _registerCallback(id, T, (final p) => func(p)); + } + + static String _registerCallback(final String id, final Type type, + final Function(dynamic parameter) func) { + if (_callbacks.containsKey(type)) { + _callbacks[type]!.addAll({id: func}); + } else { + _callbacks.addAll({ + type: {id: func} + }); + } + + return id; + } + + void unregisterCallback(final String id, final Type type) { + if (_callbacks.containsKey(type)) { + _callbacks[type]!.remove(id); + } + } + + Future showNotificationDialog( + final BuildContext context, + final List<(String, int?, NotificationSetup)> notifications, + ) async { + List topics; + try { + topics = PreferencesManager.instance.getNotificationTopics(); + } catch (e) { + topics = []; + PreferencesManager.instance.setNotificationTopics(topics); + } + final api = ref.read(apiProvider); + final grouped = notifications.groupBy((final x) => x.$1); + + final hookedBuilder = HookBuilder( + builder: (final context) { + final expansionTiles = []; + + for (final grp in grouped.entries) { + final widgets = []; + for (final (_, id, element) in grp.value) { + final topic = useState(topics.firstWhere( + (final x) => + x.uniqueName == element.uniqueName && x.deviceId == id, + orElse: () { + final oneTime = element.times == 1; + final String topic; + if (oneTime) { + topic = ""; + } else if (id == null) { + topic = element.uniqueName; + } else { + topic = "${element.uniqueName}_${id.toHex()}"; + } + return NotificationTopic( + enabled: false, + deviceId: id, + oneTime: oneTime, + topic: topic, + uniqueName: element.uniqueName); + }, + )); + if (!topics.contains(topic.value)) topics.add(topic.value); + + final checked = useState(topic.value.enabled); + widgets.add(CheckboxListTile( + value: checked.value, + title: Text(element.translatableName), + onChanged: (final value) async { + checked.value = value ?? false; + if (topic.value.topic == "") { + final res = await api.notificationNextNotificationIdGet( + uniqueName: topic.value.uniqueName, + deviceId: id, + ); + final notificationTopic = res.bodyOrThrow; + print(notificationTopic); + final idx = topics.indexOf(topic.value); + topic.value = topic.value.copyWith( + topic: notificationTopic, enabled: checked.value); + topics[idx] = topic.value; + } else if (checked.value) { + final idx = topics.indexOf(topic.value); + topic.value = topic.value.copyWith(enabled: checked.value); + topics[idx] = topic.value; + } else { + final idx = topics.indexOf(topic.value); + topic.value = topic.value.copyWith(enabled: checked.value); + topics[idx] = topic.value; + } + }, + )); + } + expansionTiles.add(ExpansionTile( + title: Text(grp.key), + initiallyExpanded: grouped.length == 1, + children: widgets, + )); + } + + return SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: expansionTiles, + ), + ); + }, + ); + + final ad = AlertDialog( + title: Text("Benachrichtigungen"), + content: hookedBuilder, + actions: [ + TextButton( + child: Text("Abbrechen"), + onPressed: () { + Navigator.pop(context, false); + }), + TextButton( + child: Text("Speichern"), + onPressed: () { + Navigator.pop(context, true); + }) + ], + ); + final dialogRes = + await showDialog(context: context, builder: (final ctx) => ad); + if (dialogRes == true) { + final validTopics = topics + .where((final x) => x.topic.isNotEmpty && (!x.oneTime || x.enabled)) + .toList(); + + PreferencesManager.instance.setNotificationTopics(validTopics); + if (Platform.isAndroid || Platform.isIOS) { + for (final topic in validTopics) { + if (topic.enabled) { + FirebaseMessaging.instance.subscribeToTopic(topic.topic); + } else { + FirebaseMessaging.instance.unsubscribeFromTopic(topic.topic); + } + } + } + } + } +} diff --git a/lib/helper/number_extensions.dart b/lib/helper/number_extensions.dart new file mode 100644 index 0000000..45e0f34 --- /dev/null +++ b/lib/helper/number_extensions.dart @@ -0,0 +1,6 @@ +extension BigIntConvert on num { + BigInt toBigUnsigned() => BigInt.from(this).toUnsigned(64); + BigInt toBigSigned() => BigInt.from(this).toSigned(64); + + String toHex() => BigInt.from(this).toUnsigned(64).toRadixString(16); +} diff --git a/lib/helper/preference_manager.dart b/lib/helper/preference_manager.dart index a8309ac..b2bcc9d 100644 --- a/lib/helper/preference_manager.dart +++ b/lib/helper/preference_manager.dart @@ -1,4 +1,7 @@ +import 'dart:convert'; + import 'package:shared_preferences/shared_preferences.dart'; +import 'package:smarthome/models/notification_topic.dart'; class PreferencesManager { static late PreferencesManager instance; @@ -25,21 +28,37 @@ class PreferencesManager { List? getStringList(final String key) => _prefs.getStringList(key); - DateTime? getDateTime(final String key) => DateTime.tryParse(_prefs.getString(key) ?? ''); + DateTime? getDateTime(final String key) => + DateTime.tryParse(_prefs.getString(key) ?? ''); Future reload() => _prefs.reload(); Future remove(final String key) => _prefs.remove(key); - Future setBool(final String key, final bool value) => _prefs.setBool(key, value); + Future setBool(final String key, final bool value) => + _prefs.setBool(key, value); + + Future setDouble(final String key, final double value) => + _prefs.setDouble(key, value); + + Future setInt(final String key, final int value) => + _prefs.setInt(key, value); - Future setDouble(final String key, final double value) => _prefs.setDouble(key, value); + Future setString(final String key, final String value) => + _prefs.setString(key, value); - Future setInt(final String key, final int value) => _prefs.setInt(key, value); + Future setStringList(final String key, final List value) => + _prefs.setStringList(key, value); - Future setString(final String key, final String value) => _prefs.setString(key, value); + Future setDateTime(final String key, final DateTime value) => + _prefs.setString(key, value.toIso8601String()); - Future setStringList(final String key, final List value) => _prefs.setStringList(key, value); + List getNotificationTopics() => + ((_prefs.getStringList("notification_topics")) ?? []) + .map((final x) => NotificationTopic.fromJson(jsonDecode(x))) + .toList(); - Future setDateTime(final String key, final DateTime value) => _prefs.setString(key, value.toIso8601String()); + Future setNotificationTopics(final List topics) => + _prefs.setStringList( + "notification_topics", topics.map((final x) => jsonEncode(x)).toList()); } diff --git a/lib/helper/settings_manager.dart b/lib/helper/settings_manager.dart index e4bf34b..7d4d1ae 100644 --- a/lib/helper/settings_manager.dart +++ b/lib/helper/settings_manager.dart @@ -1,69 +1,76 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:smarthome/helper/preference_manager.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +part 'settings_manager.g.dart'; const fallbackServerUrl = "http://localhost:5056/SmartHome"; -final settingsProvider = StateNotifierProvider((final ref) { - print("Creating new settingsmanager"); - return SettingsManager(); -}); - final debugInformationEnabledProvider = Provider((final ref) { - return ref.watch(settingsProvider).showDebugInformation; + return ref + .watch(settingsManagerProvider.select((final x) => x.showDebugInformation)); }); final serverUrlProvider = Provider((final ref) { - return ref.watch(settingsProvider).serverUrl; + return ref.watch(settingsManagerProvider.select((final x) => x.serverUrl)); }); final groupingEnabledProvider = Provider((final ref) { - return ref.watch(settingsProvider).groupingEnabled; + return ref.watch(settingsManagerProvider.select((final x) => x.groupingEnabled)); }); -class Settings { - String serverUrl = ""; - bool groupingEnabled = true; - bool showDebugInformation = false; - - Settings() { - groupingEnabled = PreferencesManager.instance.getBool("Groupings") ?? true; - serverUrl = PreferencesManager.instance.getString("mainserverurl") ?? fallbackServerUrl; - showDebugInformation = PreferencesManager.instance.getBool("ShowDebugInformation") ?? false; +@Riverpod(keepAlive: true) +class SettingsManager extends _$SettingsManager { + @override + Settings build() { + return Settings(); } - Settings copyWith({final bool? groupingEnabled, final String? serverUrl, final bool? showDebugInformation}) { - return Settings() - ..groupingEnabled = groupingEnabled ?? this.groupingEnabled - ..serverUrl = serverUrl ?? this.serverUrl - ..showDebugInformation = showDebugInformation ?? this.showDebugInformation; + void setGroupingEnabled(final bool newState) { + state = state.copyWith(groupingEnabled: newState); + PreferencesManager.instance.setBool("Groupings", newState); } -} -class SettingsManager extends StateNotifier { - static late SettingsManager _instance; - SettingsManager() : super(Settings()) { - _instance = this; + void setShowDebugInformation(final bool showDebugInformation) { + state = state.copyWith(showDebugInformation: showDebugInformation); + PreferencesManager.instance + .setBool("ShowDebugInformation", showDebugInformation); } - static void setGroupingEnabled(final bool newState) { - final state = _instance; + void setServerUrl(final String newUrl) { + state = state.copyWith(serverUrl: newUrl); + PreferencesManager.instance.setString("mainserverurl", newUrl); + } - state.state = state.state.copyWith(groupingEnabled: newState); - PreferencesManager.instance.setBool("Groupings", newState); + void setName(final String name) { + state = state.copyWith(name: name); + PreferencesManager.instance.setString("Name", name); } +} - static void setShowDebugInformation(final bool showDebugInformation) { - final state = _instance; +class Settings { + String serverUrl = ""; + bool groupingEnabled = true; + bool showDebugInformation = false; + String name = ""; - state.state = state.state.copyWith(showDebugInformation: showDebugInformation); - PreferencesManager.instance.setBool("ShowDebugInformation", showDebugInformation); + Settings() { + groupingEnabled = PreferencesManager.instance.getBool("Groupings") ?? true; + serverUrl = PreferencesManager.instance.getString("mainserverurl") ?? ""; + showDebugInformation = + PreferencesManager.instance.getBool("ShowDebugInformation") ?? false; + name = PreferencesManager.instance.getString("Name") ?? ""; } - static void setServerUrl(final String newUrl) { - final state = _instance; - - state.state = state.state.copyWith(serverUrl: newUrl); - PreferencesManager.instance.setString("mainserverurl", newUrl); + Settings copyWith( + {final bool? groupingEnabled, + final String? serverUrl, + final bool? showDebugInformation, + final String? name}) { + return Settings() + ..groupingEnabled = groupingEnabled ?? this.groupingEnabled + ..serverUrl = serverUrl ?? this.serverUrl + ..showDebugInformation = showDebugInformation ?? this.showDebugInformation + ..name = name ?? this.name; } } diff --git a/lib/helper/simple_dialog.dart b/lib/helper/simple_dialog.dart index 82888eb..c6c2336 100644 --- a/lib/helper/simple_dialog.dart +++ b/lib/helper/simple_dialog.dart @@ -21,13 +21,13 @@ class SimpleDialog { TextButton( child: Text(cancelButtonText), onPressed: () { - Navigator.pop(context, ""); + Navigator.pop(context, false); onCancel?.call(); }), TextButton( child: Text(okButtonText), onPressed: () { - Navigator.pop(context, ""); + Navigator.pop(context, true); onSubmitted?.call(); }) ]); diff --git a/lib/helper/simple_dialog_single_input.dart b/lib/helper/simple_dialog_single_input.dart index e25bc51..f456aab 100644 --- a/lib/helper/simple_dialog_single_input.dart +++ b/lib/helper/simple_dialog_single_input.dart @@ -20,7 +20,8 @@ class SimpleDialogSingleInput { child: ListBody( children: [ TextField( - decoration: InputDecoration(hintText: hintText, labelText: labelText), + decoration: + InputDecoration(hintText: hintText, labelText: labelText), controller: tec, maxLines: maxLines, autofocus: true, @@ -32,7 +33,9 @@ class SimpleDialogSingleInput { ), ), actions: [ - TextButton(child: Text(cancelButtonText), onPressed: () => Navigator.pop(context!, "")), + TextButton( + child: Text(cancelButtonText), + onPressed: () => Navigator.pop(context!, "")), TextButton( child: Text(acceptButtonText), onPressed: () { diff --git a/lib/helper/theme_manager.dart b/lib/helper/theme_manager.dart index 18ccfcf..967dffe 100644 --- a/lib/helper/theme_manager.dart +++ b/lib/helper/theme_manager.dart @@ -9,35 +9,39 @@ class ThemeManager { // colorSchemeSeed: Colors.blue, brightness: Brightness.dark, secondaryHeaderColor: Colors.indigo, - dialogBackgroundColor: Colors.indigo.shade900.withOpacity(0.95), - cardTheme: CardTheme(color: Colors.indigo.shade800.withOpacity(0.95), shadowColor: Colors.black38), - // backgroundColor: Colors.indigo.shade800.withOpacity(0.95), - sliderTheme: const SliderThemeData(thumbColor: Colors.tealAccent, activeTrackColor: Colors.tealAccent), - canvasColor: Colors.indigo.shade800.withOpacity(0.95), + dialogTheme: DialogThemeData( + backgroundColor: Colors.indigo.shade900.withValues(alpha: 0.95)), + cardTheme: CardThemeData( + color: Colors.indigo.shade800.withValues(alpha: 0.95), + shadowColor: Colors.black38), + // backgroundColor: Colors.indigo.shade800.withValues(alpha: 0.95), + sliderTheme: const SliderThemeData( + thumbColor: Colors.tealAccent, activeTrackColor: Colors.tealAccent), + canvasColor: Colors.indigo.shade800.withValues(alpha: 0.95), popupMenuTheme: PopupMenuThemeData( - color: Colors.indigo.shade800.withOpacity(0.95), + color: Colors.indigo.shade800.withValues(alpha: 0.95), ), textButtonTheme: TextButtonThemeData( style: ButtonStyle( - foregroundColor: MaterialStateProperty.resolveWith(_getColor), + foregroundColor: WidgetStateProperty.resolveWith(_getColor), )), primarySwatch: Colors.blue, - textTheme: const TextTheme(titleMedium: TextStyle(decorationColor: Colors.teal)), + textTheme: + const TextTheme(titleMedium: TextStyle(decorationColor: Colors.teal)), colorScheme: ColorScheme.dark( primary: Colors.blue, secondary: Colors.teal, - background: Colors.indigo.shade700, surface: Colors.blue.shade900, onPrimary: Colors.white, onSecondary: Colors.white), chipTheme: ChipThemeData( - backgroundColor: Colors.indigo.shade600.withOpacity(0.95), + backgroundColor: Colors.indigo.shade600.withValues(alpha: 0.95), brightness: Brightness.dark, disabledColor: Colors.indigo.shade900, labelStyle: const TextStyle(), padding: const EdgeInsetsDirectional.all(4), secondaryLabelStyle: const TextStyle(), - secondarySelectedColor: Colors.indigo.withOpacity(0.95), + secondarySelectedColor: Colors.indigo.withValues(alpha: 0.95), selectedColor: Colors.indigo), ); } @@ -47,39 +51,46 @@ class ThemeManager { return ThemeData( useMaterial3: useMaterial3, brightness: Brightness.light, - dialogBackgroundColor: indigoColor.withOpacity(0.95), - cardTheme: CardTheme(color: indigoColor.withOpacity(0.95), shadowColor: Colors.white54), - sliderTheme: - SliderThemeData(thumbColor: Colors.tealAccent.shade100, activeTrackColor: Colors.tealAccent.shade100), + dialogTheme: + DialogThemeData(backgroundColor: indigoColor.withValues(alpha: 0.95)), + cardTheme: CardThemeData( + color: indigoColor.withValues(alpha: 0.95), + shadowColor: Colors.white54), + sliderTheme: SliderThemeData( + thumbColor: Colors.tealAccent.shade100, + activeTrackColor: Colors.tealAccent.shade100), textButtonTheme: TextButtonThemeData( style: ButtonStyle( - foregroundColor: MaterialStateProperty.resolveWith(_getLightColor), + foregroundColor: WidgetStateProperty.resolveWith(_getLightColor), )), popupMenuTheme: PopupMenuThemeData( - color: Colors.indigo.shade100.withOpacity(0.95), + color: Colors.indigo.shade100.withValues(alpha: 0.95), ), - textTheme: TextTheme(titleMedium: TextStyle(decorationColor: Colors.tealAccent.shade100)), + textTheme: TextTheme( + titleMedium: TextStyle(decorationColor: Colors.tealAccent.shade100)), chipTheme: ChipThemeData( - backgroundColor: indigoColor.withOpacity(0.95), + backgroundColor: indigoColor.withValues(alpha: 0.95), brightness: Brightness.light, disabledColor: Colors.indigo.shade400, labelStyle: const TextStyle(), padding: const EdgeInsetsDirectional.all(4), secondaryLabelStyle: const TextStyle(color: Colors.green), - secondarySelectedColor: Colors.indigo.withOpacity(0.95), + secondarySelectedColor: Colors.indigo.withValues(alpha: 0.95), selectedColor: Colors.indigo), colorScheme: ColorScheme.light( primary: Colors.lightBlue.shade200, secondary: Colors.tealAccent.shade100, - background: Colors.indigo.shade200, surface: indigoColor, onPrimary: Colors.black) - .copyWith(primary: Colors.lightBlue, background: indigoColor.withOpacity(0.95)), + .copyWith( + primary: Colors.lightBlue, + surface: indigoColor.withValues(alpha: 0.95)), ); } static BoxDecoration getBackgroundDecoration(final BuildContext context) { - var fract = MediaQuery.of(context).size.width / MediaQuery.of(context).size.height; + var fract = + MediaQuery.of(context).size.width / MediaQuery.of(context).size.height; fract = pow(fract, 2).toDouble(); return BoxDecoration( gradient: LinearGradient( @@ -95,11 +106,11 @@ class ThemeManager { ); } - static Color _getColor(final Set states) { - const Set interactiveStates = { - MaterialState.scrolledUnder, - MaterialState.error, - MaterialState.disabled + static Color _getColor(final Set states) { + const Set interactiveStates = { + WidgetState.scrolledUnder, + WidgetState.error, + WidgetState.disabled }; if (states.any(interactiveStates.contains)) { return Colors.teal.shade900; @@ -107,11 +118,11 @@ class ThemeManager { return Colors.tealAccent; } - static Color _getLightColor(final Set states) { - const Set interactiveStates = { - MaterialState.scrolledUnder, - MaterialState.error, - MaterialState.disabled + static Color _getLightColor(final Set states) { + const Set interactiveStates = { + WidgetState.scrolledUnder, + WidgetState.error, + WidgetState.disabled }; if (states.any(interactiveStates.contains)) { return Colors.teal.shade200; diff --git a/lib/helper/update_manager.dart b/lib/helper/update_manager.dart index 01c6dfb..ab3231f 100644 --- a/lib/helper/update_manager.dart +++ b/lib/helper/update_manager.dart @@ -2,74 +2,83 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:github/github.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:smarthome/helper/helper_methods.dart'; +import 'package:smarthome/helper/notification_service.dart'; import 'package:smarthome/helper/preference_manager.dart'; import 'package:smarthome/helper/simple_dialog.dart' as simple_dialog; +import 'package:smarthome/main.dart'; import 'package:smarthome/models/version_and_url.dart'; import 'package:version/version.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; -final versionAndUrlProvider = StateNotifierProvider((final ref) { +final versionAndUrlProvider = + StateNotifierProvider((final ref) { final um = UpdateManager(); UpdateManager.checkForNewestVersion(); return um; }); class UpdateManager extends StateNotifier { - static final Version version = Version(1, 2, 3); static const int checkEveryHours = 16; static final GitHub gitHub = GitHub(); - static final RepositorySlug repositorySlug = RepositorySlug("susch19", "SmartHome"); + static final RepositorySlug repositorySlug = + RepositorySlug("susch19", "SmartHome"); static final RegExp versionRegExp = RegExp(r'v|V'); static DateTime? lastChecked; static late UpdateManager _instance; + static late PackageInfo _packageInfo; - static final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + static final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); - static const String updateNotificationTitle = "Neue Version verfügbar"; // Update available + static const String updateNotificationTitle = + "Neue Version verfügbar"; // Update available static const String updateNotificationBody = "Bitte aktualisiere auf "; // A new update is available. Please update to version - static int notificationId = 0; - UpdateManager() : super(null) { _instance = this; } static Future initialize() async { - lastChecked = PreferencesManager.instance.getDateTime("lastChecked"); - - const AndroidInitializationSettings initializationSettingsAndroid = - AndroidInitializationSettings('@mipmap/ic_launcher'); - - const InitializationSettings initializationSettings = - InitializationSettings(android: initializationSettingsAndroid); - - await flutterLocalNotificationsPlugin.initialize(initializationSettings, - onDidReceiveNotificationResponse: (final details) { - if (details.payload != null) { - HelperMethods.openUrl(details.payload!); + _packageInfo = await PackageInfo.fromPlatform(); + lastChecked = + (PreferencesManager.instance.getDateTime("lastChecked") ?? + DateTime.timestamp()) + .add(Duration(days: -1)); + + onDidReceiveNotificationResponse.stream.listen((final details) { + if (details.payload != null && + details.payload!.startsWith("UpdateManager")) { + HelperMethods.openUrl( + details.payload!.substring("UpdateManager".length)); } }); } static Future checkForNewestVersion() async { - print("showUpdateNotification called ${lastChecked?.toString() ?? "lastChecked is null"}"); + print( + "showUpdateNotification called ${lastChecked?.toString() ?? "lastChecked is null"}"); // TODO: at a later point maybe schedule this notification to be shown every x hours // TODO: maybe also put the check every x hours in the settings - if (lastChecked == null || DateTime.now().difference(lastChecked!).inHours > checkEveryHours) { + if (lastChecked == null || + DateTime.now().difference(lastChecked!).inHours > checkEveryHours) { lastChecked = DateTime.now(); PreferencesManager.instance.setDateTime("lastChecked", lastChecked!); final versionAndUrl = await getVersionAndUrl(); - if (versionAndUrl != null && !versionAndUrl.upToDate && versionAndUrl.url != null) { + if (versionAndUrl != null && + !versionAndUrl.upToDate && + versionAndUrl.url != null) { _instance.state = versionAndUrl; } } } - static Future displayNotificationDialog(final BuildContext context, final VersionAndUrl versionAndUrl) async { + static Future displayNotificationDialog( + final BuildContext context, final VersionAndUrl versionAndUrl) async { _instance.state = null; if (Platform.isAndroid) { // Show notification @@ -81,28 +90,35 @@ class UpdateManager extends StateNotifier { builder: (final BuildContext c) => simple_dialog.SimpleDialog.create( context: c, title: updateNotificationTitle, - content: updateNotificationBody + versionAndUrl.version.toString(), + content: + updateNotificationBody + versionAndUrl.version.toString(), okButtonText: "Aktualisieren", cancelButtonText: "Später", onSubmitted: () => HelperMethods.openUrl(versionAndUrl.url!))); } } - static Future _showNotification(final VersionAndUrl versionAndUrl) async { - const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( - 'updateNotifications', 'Update Benachrichtigungen', - channelDescription: 'Benachrichtigungen über neue Updates', - importance: Importance.low, - priority: Priority.low, - ticker: 'SmartHome'); - const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics); - await flutterLocalNotificationsPlugin.show(notificationId++, updateNotificationTitle, - updateNotificationBody + versionAndUrl.version.toString(), platformChannelSpecifics, - payload: versionAndUrl.url); + static Future _showNotification( + final VersionAndUrl versionAndUrl) async { + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails( + 'updateNotifications', 'Update Benachrichtigungen', + channelDescription: 'Benachrichtigungen über neue Updates', + importance: Importance.low, + priority: Priority.low, + ticker: 'SmartHome'); + const NotificationDetails platformChannelSpecifics = + NotificationDetails(android: androidPlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show( + NotificationService.notificationId++, + updateNotificationTitle, + updateNotificationBody + versionAndUrl.version.toString(), + platformChannelSpecifics, + payload: "UpdateManager${versionAndUrl.url}"); } static String getVersionString(final VersionAndUrl? newVersion) { - var v = version.toString(); + var v = _packageInfo.version.toString(); if (newVersion == null) { v += " - konnte neue Version nicht überprüfen"; @@ -119,14 +135,15 @@ class UpdateManager extends StateNotifier { final release = await gitHub.repositories.getLatestRelease(repositorySlug); Version latestVersion; try { - latestVersion = Version.parse(release.tagName!.replaceFirst(versionRegExp, '')); + latestVersion = + Version.parse(release.tagName!.replaceFirst(versionRegExp, '')); } catch (e) { return null; } final asset = _extractUrl(release); - if (asset != null && latestVersion > version) { + if (asset != null && latestVersion > Version.parse(_packageInfo.version)) { return VersionAndUrl(latestVersion, false, asset.browserDownloadUrl); } else { return VersionAndUrl(latestVersion, true, null); diff --git a/lib/icons/icons.dart b/lib/icons/icons.dart index ea70631..e6cd86f 100644 --- a/lib/icons/icons.dart +++ b/lib/icons/icons.dart @@ -1 +1 @@ -export 'smarthome_icons.dart'; \ No newline at end of file +export 'smarthome_icons.dart'; diff --git a/lib/icons/smarthome_icons.dart b/lib/icons/smarthome_icons.dart index 57cd1cc..2e50265 100644 --- a/lib/icons/smarthome_icons.dart +++ b/lib/icons/smarthome_icons.dart @@ -31,6 +31,7 @@ /// License: SIL (http://scripts.sil.org/OFL) /// Homepage: http://www.mfglabs.com/ /// +library; // ignore_for_file: constant_identifier_names import 'package:flutter/widgets.dart'; @@ -41,8 +42,10 @@ class SmarthomeIcons { static const _kFontFam = 'Smarthome'; static const IconData temperature = IconData(0xe800, fontFamily: _kFontFam); - static const IconData wi_thermometer = IconData(0xe801, fontFamily: _kFontFam); - static const IconData xiaomiTempSensor = IconData(0xe802, fontFamily: _kFontFam); + static const IconData wi_thermometer = + IconData(0xe801, fontFamily: _kFontFam); + static const IconData xiaomiTempSensor = + IconData(0xe802, fontFamily: _kFontFam); static const IconData wi_hot = IconData(0xe803, fontFamily: _kFontFam); static const IconData wi_barometer = IconData(0xe804, fontFamily: _kFontFam); static const IconData fire = IconData(0xe805, fontFamily: _kFontFam); @@ -86,12 +89,16 @@ class SmarthomeIcons { static const IconData power_swtich = IconData(0xe83e, fontFamily: _kFontFam); static const IconData paw = IconData(0xe84a, fontFamily: _kFontFam); static const IconData paper_plane_1 = IconData(0xf01d, fontFamily: _kFontFam); - static const IconData paper_plane_alt2 = IconData(0xf01e, fontFamily: _kFontFam); - static const IconData paper_plane_alt = IconData(0xf01f, fontFamily: _kFontFam); + static const IconData paper_plane_alt2 = + IconData(0xf01e, fontFamily: _kFontFam); + static const IconData paper_plane_alt = + IconData(0xf01f, fontFamily: _kFontFam); static const IconData gauge = IconData(0xf0e4, fontFamily: _kFontFam); static const IconData paper_plane = IconData(0xf1d8, fontFamily: _kFontFam); - static const IconData paper_plane_empty = IconData(0xf1d9, fontFamily: _kFontFam); - static const IconData connectdevelop = IconData(0xf20e, fontFamily: _kFontFam); + static const IconData paper_plane_empty = + IconData(0xf1d9, fontFamily: _kFontFam); + static const IconData connectdevelop = + IconData(0xf20e, fontFamily: _kFontFam); static const IconData battery_4 = IconData(0xf240, fontFamily: _kFontFam); static const IconData battery_3 = IconData(0xf241, fontFamily: _kFontFam); static const IconData battery_2 = IconData(0xf242, fontFamily: _kFontFam); diff --git a/lib/info_icon_provider.dart b/lib/info_icon_provider.dart new file mode 100644 index 0000000..ff4d42d --- /dev/null +++ b/lib/info_icon_provider.dart @@ -0,0 +1,59 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:signalr_netcore/signalr_client.dart'; +import 'package:smarthome/helper/connection_manager.dart'; +import 'package:flutter_riverpod/legacy.dart'; + +class InfoIconProvider extends StateNotifier + with WidgetsBindingObserver { + final Ref ref; + InfoIconProvider(this.ref) : super(Icons.refresh) { + final connectionListen = ref.watch(connectionManagerProvider); + final connection = connectionListen.value; + + if (connection == null || + connection.connectionState == HubConnectionState.Disconnected) { + state = Icons.warning; + } else if (connection.connectionState == HubConnectionState.Connected) { + state = Icons.check; + } + + WidgetsBinding.instance.addObserver(this); + ConnectionManager.connectionIconChanged.addListener(() { + if (!mounted) return; + state = ConnectionManager.connectionIconChanged.value; + }); + } + + @override + void didChangeAppLifecycleState(final AppLifecycleState state) { + if (kIsWeb || + !(defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS)) { + return; + } + + if (state == AppLifecycleState.resumed) { + // ref.read(hubConnectionProvider.notifier).newHubConnection(); + } else if (state == AppLifecycleState.paused) { + this.state = Icons.error_outline; + final connection = ref.watch(connectionManagerProvider); + // if (connectionState == HubConnectionState.connected) ConnectionManager.hubConnection.stop(); + + switch (connection) { + case AsyncData(:final value): + value.connection?.stop(); + break; + default: + break; + } + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } +} diff --git a/lib/main.dart b/lib/main.dart index b54f167..1a6c2ab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,59 +1,93 @@ +// ignore_for_file: depend_on_referenced_packages + import 'dart:io'; import 'dart:ui'; // import 'package:file_picker/file_picker.dart'; -import 'package:collection/collection.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:flutter_riverpod/legacy.dart'; import 'package:oktoast/oktoast.dart'; import 'package:shared_preferences/shared_preferences.dart'; // // import 'package:signalr_client/signalr_client.dart'; // import 'package:signalr_core/signalr_core.dart'; -import 'package:signalr_netcore/signalr_client.dart'; -import 'package:smarthome/dashboard/group_devices.dart'; import 'package:smarthome/devices/device_exporter.dart'; import 'dart:async'; import 'package:smarthome/devices/device_manager.dart'; -import 'package:smarthome/devices/device_overview_model.dart'; -import 'package:smarthome/devices/generic/device_layout_service.dart'; -import 'package:smarthome/devices/generic/stores/store_service.dart'; -import 'package:smarthome/helper/iterable_extensions.dart'; +import 'package:smarthome/helper/connection_manager.dart'; +import 'package:smarthome/helper/notification_service.dart'; import 'package:smarthome/helper/preference_manager.dart'; -import 'package:smarthome/helper/settings_manager.dart'; -import 'package:smarthome/helper/simple_dialog_single_input.dart'; -import 'package:flutter/foundation.dart'; -import 'package:smarthome/helper/theme_manager.dart'; import 'package:smarthome/helper/update_manager.dart'; -import 'package:smarthome/screens/screen_export.dart'; -import 'package:smarthome/screens/settings_page.dart'; import 'package:adaptive_theme/adaptive_theme.dart'; -import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:intl/intl.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tuple/tuple.dart'; - -import 'controls/expandable_fab.dart'; -import 'helper/connection_manager.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'info_icon_provider.dart'; +import 'my_app.dart'; +import 'package:firebase_core/firebase_core.dart' + show Firebase, FirebaseOptions; +import 'package:flutter/foundation.dart' + show TargetPlatform, defaultTargetPlatform, kDebugMode, kIsWeb; part 'main.g.dart'; +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler( + final RemoteMessage message) async { + print("Handling a background message: ${message.messageId}"); + print(message.toMap().entries.join(',')); + print(message.data); + print(message.messageId); + print(message.sentTime?.toIso8601String()); + print(message.notification?.android?.priority); +} + //Implement https://developer.android.com/develop/ui/views/device-control as a seperate build //Because having support for android kitkat is still wanted -final _brightnessChangeProvider = - ChangeNotifierProvider.family((final ref, final b) { +final _brightnessChangeProvider = ChangeNotifierProvider.family< + AdaptiveThemeModeWatcher, AdaptiveThemeManager>((final ref, final b) { return AdaptiveThemeModeWatcher(b); }); -final _addItemSelectorProvider = StateProvider.family((final ref, final arg) => false); - @riverpod -Brightness brightness(final BrightnessRef ref, final AdaptiveThemeManager b) { +Brightness brightness(final Ref ref, final AdaptiveThemeManager b) { final brightness = ref.watch(_brightnessChangeProvider(b)).brightness; return brightness; } +// @riverpod +// Future> showPin(final ShowPinRef ref) async { +// final watched = ref.watch(deviceNameProvider); +// final kioskMode = await ref.watch(inKioskModeProvider.future); +// final falseTuple = Tuple2(false, kioskMode); + +// return watched.when( +// data: (final data) => Tuple2(data == "SM-T560", falseTuple.item2), +// error: ((final _, final __) => falseTuple), +// loading: () => falseTuple); +// } + +@riverpod +Future deviceName(final Ref ref) async { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) return ""; + final pluginInfo = DeviceInfoPlugin(); + return (await pluginInfo.androidInfo).model; +} + +// @riverpod +// Stream inKioskMode(final InKioskModeRef ref) { +// if (!kIsWeb || defaultTargetPlatform != TargetPlatform.android) +// return Stream.value(false); + +// return watchKioskMode(androidQueryPeriod: const Duration(seconds: 1)) +// .map((final o) => KioskMode.enabled == o); +// } + final _searchProvider = StateProvider(((final ref) => "")); final searchTextProvider = StateProvider(((final ref) { @@ -65,7 +99,7 @@ final searchTextProvider = StateProvider(((final ref) { final searchEnabledProvider = StateProvider(((final ref) => false)); @riverpod -Widget getTitleWidget(final GetTitleWidgetRef ref) { +Widget getTitleWidget(final Ref ref) { final enabled = ref.watch(searchEnabledProvider); if (!enabled) return const Text("Smart Home App"); @@ -78,7 +112,7 @@ Widget getTitleWidget(final GetTitleWidgetRef ref) { } @riverpod -List filteredDevices(final FilteredDevicesRef ref) { +List filteredDevices(final Ref ref) { final searchText = ref.watch(searchTextProvider).toLowerCase(); final devices = ref.watch(sortedDeviceProvider); @@ -87,7 +121,12 @@ List filteredDevices(final FilteredDevicesRef ref) { return devices .where((final element) => element.typeName.toLowerCase().contains(searchText) || - (ref.read(element.baseModelTProvider(element.id))?.friendlyName.toLowerCase().contains(searchText) ?? false)) + (ref + .read(element.baseModelTProvider(element.id)) + ?.friendlyName + .toLowerCase() + .contains(searchText) ?? + false)) .toList(); } @@ -100,15 +139,69 @@ class CustomScrollBehavior extends MaterialScrollBehavior { }; } +class DevHttpOverrides extends HttpOverrides { + final String certificate; + DevHttpOverrides(this.certificate); + + @override + HttpClient createHttpClient(SecurityContext? context) { + context ??= SecurityContext(withTrustedRoots: true); + + context.setTrustedCertificatesBytes(certificate.codeUnits); + return super.createHttpClient(context); + } +} + +StreamController onDidReceiveNotificationResponse = + StreamController.broadcast(); + void main() async { WidgetsFlutterBinding.ensureInitialized(); + if (kDebugMode) { + SharedPreferences.setPrefix("debug."); + } else { + SharedPreferences.setPrefix("test."); + } final prefs = await SharedPreferences.getInstance(); // prefs.clear(); + final String certificate = + await rootBundle.loadString('assets/certs/Smarthome.pem'); + + HttpOverrides.global = DevHttpOverrides(certificate); PreferencesManager.instance = PreferencesManager(prefs); - final savedThemeMode = await AdaptiveTheme.getThemeMode() ?? AdaptiveThemeMode.system; +// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + final DarwinInitializationSettings initializationSettingsDarwin = + DarwinInitializationSettings(); + final LinuxInitializationSettings initializationSettingsLinux = + LinuxInitializationSettings(defaultActionName: 'Open notification'); + const WindowsInitializationSettings initSettings = + WindowsInitializationSettings( + appName: 'Smarthome', + appUserModelId: 'de.susch19.Smarthome', + guid: '07DCF00D-5E87-4882-9F37-A5F3242BF1A1', + ); + + final InitializationSettings initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsDarwin, + macOS: initializationSettingsDarwin, + linux: initializationSettingsLinux, + windows: initSettings, + ); + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: (final details) => + onDidReceiveNotificationResponse.add(details), + ); + + final savedThemeMode = + await AdaptiveTheme.getThemeMode() ?? AdaptiveThemeMode.system; DeviceManager.init(); + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); Intl.defaultLocale = "de-DE"; initializeDateFormatting("de-DE").then((final _) { // ConnectionManager.startConnection(); @@ -116,7 +209,9 @@ void main() async { child: OKToast( backgroundColor: Colors.grey.withOpacity(0.3), position: ToastPosition.bottom, - child: MyApp(savedThemeMode), + child: _EagerInitialization( + child: MyApp(savedThemeMode), + ), ), )); }); @@ -147,542 +242,63 @@ class AdaptiveThemeModeWatcher extends ChangeNotifier { } } -class MyApp extends StatelessWidget { - const MyApp(this.savedThemeMode, {final Key? key}) : super(key: key); - - static late PreferencesManager prefManager; - final AdaptiveThemeMode savedThemeMode; - - @override - Widget build(final BuildContext context) { - return AdaptiveTheme( - light: ThemeManager.getLightTheme(), - dark: ThemeManager.getDarkTheme(), - initial: savedThemeMode, - builder: (final theme, final darkTheme) => MaterialApp( - scrollBehavior: CustomScrollBehavior(), - title: 'Smarthome', - theme: theme, - darkTheme: darkTheme, - home: const MyHomePage(title: 'Smarthome Home Page'), - )); - } -} - -class InfoIconProvider extends StateNotifier with WidgetsBindingObserver { - final Ref ref; - InfoIconProvider(this.ref) : super(Icons.refresh) { - final connection = ref.watch(hubConnectionProvider); - - // if (connection == HubConnectionState.disconnected) { - if (connection.connectionState == HubConnectionState.Disconnected) { - state = Icons.warning; - // } else if (connection == HubConnectionState.connected) { - } else if (connection.connectionState == HubConnectionState.Connected) { - state = Icons.check; - } - - WidgetsBinding.instance.addObserver(this); - ConnectionManager.connectionIconChanged.addListener(() { - if (!mounted) return; - state = ConnectionManager.connectionIconChanged.value; - }); - } - - @override - void didChangeAppLifecycleState(final AppLifecycleState state) { - if (kIsWeb || !(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS)) { - return; - } - - if (state == AppLifecycleState.resumed) { - ref.read(hubConnectionProvider.notifier).newHubConnection(); - } else if (state == AppLifecycleState.paused) { - this.state = Icons.error_outline; - final connection = ref.watch(hubConnectionProvider); - // if (connectionState == HubConnectionState.connected) ConnectionManager.hubConnection.stop(); - connection.connection?.stop(); - } - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } -} - final infoIconProvider = StateNotifierProvider( (final ref) => InfoIconProvider(ref), ); final maxCrossAxisExtentProvider = StateProvider((final _) => - PreferencesManager.instance.getDouble("DashboardCardSize") ?? (!kIsWeb && Platform.isAndroid ? 370 : 300)); - -final _groupCollapsedProvider = StateProvider.family((final _, final __) => false); - -class MyHomePage extends ConsumerWidget { - const MyHomePage({final Key? key, this.title}) : super(key: key); - final String? title; - - Widget buildBodyGrouped(final BuildContext context, final WidgetRef ref) { - final deviceGroupsRaw = ref.watch(filteredDevicesProvider); - final deviceGroupsMap = deviceGroupsRaw.groupManyBy((final x) => ref.watch(Device.groupsByIdProvider(x.id))); - final deviceGroups = deviceGroupsMap.entries.sorted((final a, final b) => b.value.length.compareTo(a.value.length)); + PreferencesManager.instance.getDouble("DashboardCardSize") ?? + (!kIsWeb && Platform.isAndroid ? 370 : 300)); - final versionAndUrl = ref.watch(versionAndUrlProvider); - if (versionAndUrl != null) { - WidgetsBinding.instance.addPostFrameCallback((final _) async { - await UpdateManager.displayNotificationDialog(context, versionAndUrl); - }); - } - - return Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: RefreshIndicator( - child: ConstrainedBox( - constraints: BoxConstraints.tight(Size.infinite), - child: OrientationBuilder( - builder: (final context, final orientation) { - return Consumer( - builder: (final context, final ref, final child) { - return MasonryGridView.extent( - maxCrossAxisExtent: ref.watch(maxCrossAxisExtentProvider), - mainAxisSpacing: 8, - crossAxisSpacing: 8, - itemCount: deviceGroups.length, - itemBuilder: (final context, final i) { - final deviceGroup = deviceGroups.elementAt(i); - //if (deviceGroup == null) return const Text("Empty Entry"); - return Container( - margin: const EdgeInsets.only(left: 2, top: 4, right: 2, bottom: 2), - child: getDashboardCard(deviceGroup, ref), - ); - }, - ); - }, - ); - }, - ), - ), - onRefresh: () async => refresh(ref), - ), - ); - } - - Widget buildBody(final BuildContext context, final WidgetRef ref) { - ref.watch(valueStoreProvider); - final devices = ref.watch(filteredDevicesProvider); - return Container( - decoration: ThemeManager.getBackgroundDecoration(context), - child: RefreshIndicator( - child: ConstrainedBox( - constraints: BoxConstraints.tight(Size.infinite), - child: OrientationBuilder( - builder: (final context, final orientation) { - return Consumer( - builder: (final context, final ref, final child) { - return MasonryGridView.extent( - maxCrossAxisExtent: ref.watch(maxCrossAxisExtentProvider), - itemCount: devices.length, - itemBuilder: (final context, final i) { - final device = devices[i]; - if (device is GenericDevice) { - final developerMode = ref.watch(debugInformationEnabledProvider); - final layout = ref.watch(deviceLayoutProvider(Tuple2(device.id, device.typeName))); - if (layout == null || (!developerMode && layout.showOnlyInDeveloperMode)) return Container(); - } - return Container( - margin: const EdgeInsets.only(left: 2, top: 4, right: 2, bottom: 2), - child: device.dashboardView( - () { - deviceAction(context, ref, device); - }, - ), - ); - }, - // staggeredTileBuilder: (final int index) => const StaggeredTile.fit(1) - ); - }, - ); - }, - ), - ), - onRefresh: () async => refresh(ref), - ), - ); - } - - Widget getDashboardCard( - final MapEntry>> deviceGroup, - final WidgetRef ref, - ) { - final collapsed = ref.watch(_groupCollapsedProvider(deviceGroup.key)); - - return Column( - children: [ - Container( - margin: const EdgeInsets.only(left: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible(child: Consumer( - builder: (final context, final ref, final child) { - final groupName = ref.watch(DeviceManager.customGroupNameProvider(deviceGroup.key)); - return MaterialButton( - onPressed: (() { - final oldCollapsed = ref.read(_groupCollapsedProvider(deviceGroup.key).notifier); - oldCollapsed.state = !oldCollapsed.state; - }), - child: Text( - groupName, - style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ); - }, - )), - Consumer( - builder: (final context, final ref, final child) { - return PopupMenuButton( - onSelected: (final o) => groupOption(context, ref, o, deviceGroup), - itemBuilder: (final BuildContext context) => >[ - const PopupMenuItem(value: 'Rename', child: Text("Umbenennen")), - const PopupMenuItem(value: 'Delete', child: Text("Entfernen")), - const PopupMenuItem(value: 'Edit', child: Text("Geräte zuordnen")), - ], - ); - }, - ) - ], - ), - ), - ] + - (collapsed - ? [] - : deviceGroup.value - .map((final e) => Consumer( - builder: (final context, final ref, final child) { - if (e is GenericDevice) { - final developerMode = ref.watch(debugInformationEnabledProvider); - final layout = ref.watch(deviceLayoutProvider(Tuple2(e.id, e.typeName))); - if (layout == null || (!developerMode && layout.showOnlyInDeveloperMode)) { - return Container(); - } - } - - return Container( - margin: const EdgeInsets.only(), - child: e.dashboardView( - () { - deviceAction(context, ref, e); - }, - ), - ); - }, - )) - .toList())); - } - - void groupOption(final BuildContext context, final WidgetRef ref, final String value, - final MapEntry>> deviceGroup) { - switch (value) { - case 'Rename': - final sd = SimpleDialogSingleInput.create( - hintText: "Name der Gruppe", - labelText: "Name", - acceptButtonText: "Umbenennen", - cancelButtonText: "Abbrechen", - defaultText: ref.read(DeviceManager.customGroupNameProvider(deviceGroup.key)), - onSubmitted: (final s) { - ref.read(deviceProvider.notifier).changeGroupName(deviceGroup.key, s); - }, - title: "Gruppenname ändern", - context: context); - showDialog(builder: (final BuildContext context) => sd, context: context); - break; - case 'Delete': - for (final element in deviceGroup.value) { - final groupsState = ref.read(Device.groupsByIdProvider(element.id).notifier); - final groups = groupsState.state.toList(); - - groups.remove(deviceGroup.key); - if (groups.isEmpty) removeDevice(context, ref, element.id, pop: false); - groupsState.state = groups; - } - ref.read(deviceProvider.notifier).saveDeviceGroups(); - - break; - case 'Edit': - Navigator.push(context, MaterialPageRoute(builder: (final c) => GroupDevices(deviceGroup.key, false))); - - break; - - default: - break; - } - } - - Future addDevice(final DeviceOverviewModel device, final WidgetRef ref) async { - for (final item in device.typeNames) { - if (await tryCreateDevice(device, item, ref)) return; - } - } - - Future tryCreateDevice(final DeviceOverviewModel device, final String item, final WidgetRef ref) async { - if (!DeviceManager.ctorFactory.containsKey(item) || !DeviceManager.stringNameJsonFactory.containsKey(item)) { - return false; - } - - ref.read(deviceProvider.notifier).subscribeToDevice(device.id); - return true; - } +class _EagerInitialization extends ConsumerWidget { + const _EagerInitialization({required this.child}); + final Widget child; @override Widget build(final BuildContext context, final WidgetRef ref) { - final _ = ref.watch(brightnessProvider(AdaptiveTheme.of(context))); - final settings = ref.watch(groupingEnabledProvider); - - return Scaffold( - appBar: AppBar( - title: ref.watch(getTitleWidgetProvider), - leading: IconButton(icon: Icon(ref.watch(infoIconProvider)), onPressed: () => refresh(ref)), - actions: [ - IconButton( - onPressed: () { - final notifier = ref.watch(searchEnabledProvider.notifier); - notifier.state = !notifier.state; - }, - icon: ref.watch(searchEnabledProvider) ? const Icon(Icons.cancel_outlined) : const Icon(Icons.search)), - PopupMenuButton( - child: const Icon(Icons.sort), - onSelected: (final v) => selectedSort(ref, v), - itemBuilder: (final BuildContext context) => >[ - const PopupMenuItem(value: 'Name', child: Text("Name")), - const PopupMenuItem(value: 'Typ', child: Text("Gerätetyp")), - const PopupMenuItem(value: 'Id', child: Text("Id")), - ], - ), - PopupMenuButton( - onSelected: (final v) => selectedOption(context, v, ref), - itemBuilder: (final BuildContext context) => >[ - const PopupMenuItem(value: 'RemoveAll', child: Text("Entferne alle Geräte")), - const PopupMenuItem(value: 'Settings', child: Text("Einstellungen")), - ], - ), - ], - ), - body: settings ? buildBodyGrouped(context, ref) : buildBody(context, ref), - floatingActionButton: ExpandableFab( - distance: 64.0, - children: [ - ActionButton( - onPressed: () => addNewDevice(context, ref), - icon: const Icon(Icons.add), - ), - ActionButton( - onPressed: () => addGroup(context), - icon: const Icon(Icons.group_rounded), - ), - ], - ), - ); - } + ref.read(notificationServiceProvider); - void selectedOption(final BuildContext context, final String value, final WidgetRef ref) { - switch (value) { - case "Debug": - DeviceManager.showDebugInformation = !DeviceManager.showDebugInformation; - break; - case "Theme": - AdaptiveTheme.of(context).toggleThemeMode(); - break; - case "RemoveAll": - ref.read(deviceProvider.notifier).removeAllDevices(); - break; - case "Info": - Navigator.push(context, MaterialPageRoute(builder: (final c) => const AboutPage())); - break; - case 'Settings': - Navigator.push(context, MaterialPageRoute(builder: (final c) => const SettingsPage())); - break; - } - } - - void deviceAction(final BuildContext context, final WidgetRef ref, final Device d) { - final actions = []; - - actions.add( - SimpleDialogOption( - child: const Text("Umbenennen"), - onPressed: () => renameDevice(context, ref, d), - ), - ); - actions.add( - SimpleDialogOption( - child: const Text("Entfernen"), - onPressed: () => removeDevice(context, ref, d.id), - ), - ); - - final dialog = SimpleDialog( - title: Consumer( - builder: (final context, final ref, final child) { - return Text("Gerät ${ref.watch(BaseModel.friendlyNameProvider(d.id))}"); - }, - ), - children: actions, - ); - showDialog(context: context, builder: (final b) => dialog); - } - - void removeDevice(final BuildContext context, final WidgetRef ref, final int id, {final bool pop = true}) { - if (pop) Navigator.pop(context); - - final devices = ref.read(deviceProvider.notifier); - devices.removeDevice(id); - } - - renameDevice(final BuildContext context, final WidgetRef ref, final Device x) { - showDialog( - context: context, - builder: (final BuildContext context) => SimpleDialogSingleInput.create( - context: context, - title: "Gerät benennen", - hintText: "Name für das Gerät", - labelText: "Name", - defaultText: ref.read(BaseModel.friendlyNameProvider(x.id)), - maxLines: 2, - onSubmitted: (final s) async { - x.updateDeviceOnServer(x.id, s, ref.read(hubConnectionProvider)); - })).then((final x) => Navigator.of(context).pop()); - } - - void selectedSort(final WidgetRef ref, final String value) { - final currentSortState = ref.watch(deviceSortProvider.notifier); - SortTypes newSort = currentSortState.state; - switch (value) { - case "Name": - newSort = newSort == SortTypes.NameAsc ? SortTypes.NameDesc : SortTypes.NameAsc; - break; - case "Typ": - newSort = newSort == SortTypes.TypeAsc ? SortTypes.TypeDesc : SortTypes.TypeAsc; - break; - case "Id": - newSort = newSort == SortTypes.IdAsd ? SortTypes.IdDesc : SortTypes.IdAsd; - break; - } - currentSortState.state = newSort; - PreferencesManager.instance.setInt("SortOrder", newSort.index); - } - - Future addNewDevice(final BuildContext context, final WidgetRef ref) async { - // if (ConnectionManager.hubConnection.state != HubConnectionState.connected) { - final connection = ref.watch(hubConnectionConnectedProvider); - if (connection == null) { - return; - } - - final serverDevices = (await connection.invoke("GetDeviceOverview", args: [])) as List; - final serverDevicesList = serverDevices.map((final e) => DeviceOverviewModel.fromJson(e)).toList(); - - final devices = ref.read(deviceProvider); - final devicesToSelect = []; - for (final dev in serverDevicesList) { - if (!devices.any((final x) => x.id == dev.id)) { - devicesToSelect.add( - Consumer( - builder: (final context, final ref, final child) { - final selected = ref.watch(_addItemSelectorProvider(dev.id)); - return SimpleDialogOption( - child: Row( - children: [ - Checkbox( - value: selected, - onChanged: (c) { - ref.watch(_addItemSelectorProvider(dev.id).notifier).state = !selected; - }), - Text(dev.friendlyName ?? dev.id.toString(), style: const TextStyle(fontWeight: FontWeight.bold)), - Text(": ${dev.typeName}") - ], - ), - onPressed: () async { - ref.watch(_addItemSelectorProvider(dev.id).notifier).state = !selected; - }, - ); - }, - ), - ); + ref.watch(_firebaseConfigProvider).whenData((final value) async { + if (value == null) { + return; } - } - if (devicesToSelect.length > 1) { - devicesToSelect.insert( - 0, - SimpleDialogOption( - child: const Text("Select all"), - onPressed: () async { - for (final dev in serverDevicesList) { - ref.watch(_addItemSelectorProvider(dev.id).notifier).state = true; - } - }, - ), - ); - devicesToSelect.add( - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SimpleDialogOption( - child: MaterialButton( - onPressed: () => subscribeTo(context, ref, serverDevicesList), - child: const Text("Subscribe to selected"), - ), - onPressed: () { - subscribeTo(context, ref, serverDevicesList); - }, - ), - ], - ), + await Firebase.initializeApp( + options: value, ); - } + await FirebaseMessaging.instance.requestPermission(); - final dialog = SimpleDialog( - title: Text((devicesToSelect.isEmpty ? "No new Devices found" : "Add new Smarthome Device")), - children: devicesToSelect, - ); - showDialog(context: context, builder: (final b) => dialog); - } - - void subscribeTo(final BuildContext context, final WidgetRef ref, final List serverDevicesList) { - final List selectedIds = []; - for (final dev in serverDevicesList) { - final notifier = ref.read(_addItemSelectorProvider(dev.id).notifier); - if (notifier.state) { - selectedIds.add(dev.id); - notifier.state = false; + FirebaseMessaging.onMessage.listen((final element) { + print(element.data); + print(element.messageId); + print(element.sentTime?.toIso8601String()); + print(element.notification?.android?.priority); + }); + if (Platform.isAndroid) { + FirebaseMessaging.instance.subscribeToTopic("app"); } - } - ref.read(deviceProvider.notifier).subscribeToDevices(selectedIds); - Navigator.pop(context); - } - - void refresh(final WidgetRef ref) { - ref.read(hubConnectionProvider.notifier).newHubConnection().then((final value) async { - await ref.read(deviceProvider.notifier).reloadCurrentDevices(); }); - } - void addGroup(final BuildContext context) { - final sd = SimpleDialogSingleInput.create( - hintText: "Name der neuen Gruppe", - labelText: "Name", - acceptButtonText: "Erstellen", - cancelButtonText: "Abbrechen", - onSubmitted: (final s) { - Navigator.push(context, MaterialPageRoute(builder: (final c) => GroupDevices(s, true))); - }, - title: "Neue Gruppe", - context: context); - showDialog(builder: (final BuildContext context) => sd, context: context); + return child; } } + +final _firebaseConfigProvider = + FutureProvider((final ref) async { + final connection = ref.watch(apiProvider); + + final apiRes = await connection.notificationFirebaseOptionsGet(); + final res = apiRes.bodyOrThrow as Map; + final plattformName = + kIsWeb ? "web" : defaultTargetPlatform.name.toLowerCase(); + if (!res.containsKey(plattformName)) return null; + final subMap = res[plattformName] as Map; + + return FirebaseOptions( + apiKey: subMap["apiKey"]!, + appId: subMap["appId"]!, + messagingSenderId: subMap["messagingSenderId"]!, + projectId: subMap["projectId"]!, + storageBucket: subMap["storageBucket"], + iosBundleId: subMap["iosBundleId"], + authDomain: subMap["authDomain"]); +}); diff --git a/lib/models/command.dart b/lib/models/command.dart new file mode 100644 index 0000000..7e1f3b4 --- /dev/null +++ b/lib/models/command.dart @@ -0,0 +1,65 @@ + +import 'package:json_annotation/json_annotation.dart'; + +enum Command { + @JsonValue(0) + none(0), + @JsonValue(1) + off(1), + @JsonValue(2) + on(2), + @JsonValue(3) + whoiam(3), + @JsonValue(4) + ip(4), + @JsonValue(5) + time(5), + @JsonValue(6) + temp(6), + @JsonValue(7) + brightness(7), + @JsonValue(8) + relativebrightness(8), + @JsonValue(9) + color(9), + @JsonValue(10) + mode(10), + @JsonValue(11) + onchangedconnections(11), + @JsonValue(12) + onnewconnection(12), + @JsonValue(13) + mesh(13), + @JsonValue(14) + delay(14), + @JsonValue(15) + rgb(15), + @JsonValue(16) + strobo(16), + @JsonValue(17) + rgbcycle(17), + @JsonValue(18) + lightwander(18), + @JsonValue(19) + rgbwander(19), + @JsonValue(20) + reverse(20), + @JsonValue(21) + singlecolor(21), + @JsonValue(22) + devicemapping(22), + @JsonValue(23) + calibration(23), + @JsonValue(24) + ota(24), + @JsonValue(25) + otapart(25), + @JsonValue(26) + log(26), + @JsonValue(100) + zigbee(100); + + final int? value; + + const Command(this.value); +} \ No newline at end of file diff --git a/lib/models/ipport.dart b/lib/models/ipport.dart index 6fb7dac..8692221 100644 --- a/lib/models/ipport.dart +++ b/lib/models/ipport.dart @@ -5,4 +5,4 @@ class IpPort { int port; InternetAddressType type; IpPort(this.ipAddress, this.port, this.type); -} \ No newline at end of file +} diff --git a/lib/models/message.dart b/lib/models/message.dart index 36a188a..ebc99b1 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -1,37 +1,13 @@ // ignore_for_file: constant_identifier_names import 'package:json_annotation/json_annotation.dart'; +import 'package:smarthome/restapi/swagger.enums.swagger.dart'; part 'message.g.dart'; -enum MessageType { Get, Update, Options } +// enum MessageType { Get, Update, Options } + -enum Command { - None, - Off, - On, - WhoIAm, - IP, - Time, - Temp, - Brightness, - RelativeBrightness, - Color, - Mode, - OnChangedConnections, - OnNewConnection, - Mesh, - Delay, - RGB, - Strobo, - RGBCycle, - LightWander, - RGBWander, - Reverse, - SingleColor, - DeviceMapping, - Calibration, -} @JsonSerializable() class Message { @@ -45,7 +21,8 @@ class Message { Message(this.id, this.messageType, this.command, [this.parameters]); - factory Message.fromJson(final Map json) => _$MessageFromJson(json); + factory Message.fromJson(final Map json) => + _$MessageFromJson(json); Map toJson() => _$MessageToJson(this); diff --git a/lib/models/notification_topic.dart b/lib/models/notification_topic.dart new file mode 100644 index 0000000..2523d77 --- /dev/null +++ b/lib/models/notification_topic.dart @@ -0,0 +1,44 @@ +import 'package:collection/collection.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'notification_topic.g.dart'; + +@JsonSerializable() +class NotificationTopic extends EqualityBy { + final bool enabled; + final String topic; + final String uniqueName; + final bool oneTime; + final int? deviceId; + + NotificationTopic({ + required this.enabled, + required this.topic, + required this.uniqueName, + required this.oneTime, + this.deviceId, + }) : super( + (final t) => (t.uniqueName, deviceId), + ); + + factory NotificationTopic.fromJson(final Map json) => + _$NotificationTopicFromJson(json); + + Map toJson() => _$NotificationTopicToJson(this); + + NotificationTopic copyWith({ + final bool? enabled, + final String? topic, + final String? uniqueName, + final bool? oneTime, + final int? deviceId, + }) { + return NotificationTopic( + enabled: enabled ?? this.enabled, + topic: topic ?? this.topic, + uniqueName: uniqueName ?? this.uniqueName, + oneTime: oneTime ?? this.oneTime, + deviceId: deviceId ?? this.deviceId, + ); + } +} diff --git a/lib/models/server_record.dart b/lib/models/server_record.dart index 3488dba..b23eb90 100644 --- a/lib/models/server_record.dart +++ b/lib/models/server_record.dart @@ -12,5 +12,12 @@ class ServerRecord { DateTime lastChecked; ServerRecord( - this.name, this.clusterId, this.fqdn, this.reachableAddresses, this.minAppVersion, this.debug, this.lastChecked); + this.name, + this.clusterId, + this.fqdn, + this.reachableAddresses, + this.minAppVersion, + this.debug, + this.lastChecked, + ); } diff --git a/lib/models/version_and_url.dart b/lib/models/version_and_url.dart index bb45676..de06e6a 100644 --- a/lib/models/version_and_url.dart +++ b/lib/models/version_and_url.dart @@ -9,7 +9,10 @@ class VersionAndUrl { @override bool operator ==(final Object other) => - other is VersionAndUrl && version == other.version && upToDate == other.upToDate && url == other.url; + other is VersionAndUrl && + version == other.version && + upToDate == other.upToDate && + url == other.url; @override int get hashCode => Object.hash(version.hashCode, upToDate, url); diff --git a/lib/my_app.dart b/lib/my_app.dart new file mode 100644 index 0000000..e4bbbed --- /dev/null +++ b/lib/my_app.dart @@ -0,0 +1,47 @@ +import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:smarthome/helper/preference_manager.dart'; +import 'package:smarthome/helper/settings_manager.dart'; +import 'package:smarthome/helper/theme_manager.dart'; +import 'package:smarthome/main.dart'; +import 'package:smarthome/screens/setup_page.dart'; + +import 'my_home_page.dart'; + +class MyApp extends StatelessWidget { + const MyApp(this.savedThemeMode, {super.key}); + + static late PreferencesManager prefManager; + final AdaptiveThemeMode savedThemeMode; + + @override + Widget build(final BuildContext context) { + return AdaptiveTheme( + light: ThemeManager.getLightTheme(), + dark: ThemeManager.getDarkTheme(), + initial: savedThemeMode, + builder: (final theme, final darkTheme) => MaterialApp( + scrollBehavior: CustomScrollBehavior(), + title: 'Smarthome', + theme: theme, + darkTheme: darkTheme, + home: const StartingPage(), + )); + } +} + +class StartingPage extends ConsumerWidget { + const StartingPage({super.key}); + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final settings = ref.watch(settingsManagerProvider); + if (settings.serverUrl.isEmpty || settings.name.isEmpty) { + return const SetupPage(); + } + return const MyHomePage( + title: "Smarthome Home Page", + ); + } +} diff --git a/lib/my_home_page.dart b/lib/my_home_page.dart new file mode 100644 index 0000000..aeecf49 --- /dev/null +++ b/lib/my_home_page.dart @@ -0,0 +1,648 @@ +import 'dart:async'; +import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:smarthome/controls/dashboard_card.dart'; +import 'package:smarthome/controls/expandable_fab.dart'; +import 'package:smarthome/dashboard/group_devices.dart'; +import 'package:smarthome/devices/device_exporter.dart'; +import 'package:smarthome/devices/device_manager.dart'; +import 'package:smarthome/devices/device_overview_model.dart'; +import 'package:smarthome/devices/generic/device_layout_service.dart'; +import 'package:smarthome/helper/connection_manager.dart'; +import 'package:smarthome/helper/iterable_extensions.dart'; +import 'package:smarthome/helper/notification_service.dart'; +import 'package:smarthome/helper/preference_manager.dart'; +import 'package:smarthome/helper/settings_manager.dart'; +import 'package:smarthome/helper/simple_dialog_single_input.dart'; +import 'package:smarthome/helper/theme_manager.dart'; +import 'package:smarthome/helper/update_manager.dart'; +import 'package:smarthome/main.dart'; +import 'package:smarthome/screens/screen_export.dart'; +import 'package:smarthome/screens/settings_page.dart'; +import 'package:flutter_riverpod/legacy.dart'; + +final _groupCollapsedProvider = StateProvider.family( + (final _, final __) => false, +); + +class DashboardGroup extends ConsumerWidget { + const DashboardGroup({super.key, required this.deviceGroup}); + + final MapEntry>> deviceGroup; + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final collapsed = ref.watch(_groupCollapsedProvider(deviceGroup.key)); + + return Column( + children: [ + Container( + margin: const EdgeInsets.only(left: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Consumer( + builder: (final context, final ref, final child) { + final groupName = ref.watch( + DeviceManager.customGroupNameProvider(deviceGroup.key), + ); + return MaterialButton( + onPressed: (() { + final oldCollapsed = ref.read( + _groupCollapsedProvider(deviceGroup.key).notifier, + ); + oldCollapsed.state = !oldCollapsed.state; + }), + child: Text( + groupName, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ); + }, + ), + ), + Consumer( + builder: (final context, final ref, final child) { + return PopupMenuButton( + onSelected: (final o) => + groupOption(context, ref, o, deviceGroup), + itemBuilder: (final BuildContext context) => + >[ + const PopupMenuItem( + value: 'Rename', + child: Text("Umbenennen"), + ), + const PopupMenuItem( + value: 'Delete', + child: Text("Entfernen"), + ), + const PopupMenuItem( + value: 'Edit', + child: Text("Geräte zuordnen"), + ), + ], + ); + }, + ), + ], + ), + ), + if (!collapsed) + ...deviceGroup.value.map( + (final e) => Consumer( + builder: (final context, final ref, final child) { + return Container( + margin: const EdgeInsets.only(), + child: DashboardCard( + device: e, + onLongPress: () { + MyHomePage.deviceAction(context, ref, e); + }, + ), + ); + }, + ), + ), + ], + ); + } + + void groupOption( + final BuildContext context, + final WidgetRef ref, + final String value, + final MapEntry>> deviceGroup, + ) { + switch (value) { + case 'Rename': + final sd = SimpleDialogSingleInput.create( + hintText: "Name der Gruppe", + labelText: "Name", + acceptButtonText: "Umbenennen", + cancelButtonText: "Abbrechen", + defaultText: ref.read( + DeviceManager.customGroupNameProvider(deviceGroup.key), + ), + onSubmitted: (final s) { + ref + .read(deviceManagerProvider.notifier) + .changeGroupName(deviceGroup.key, s); + }, + title: "Gruppenname ändern", + context: context, + ); + showDialog( + builder: (final BuildContext context) => sd, + context: context, + ); + break; + case 'Delete': + for (final element in deviceGroup.value) { + final groupsState = ref.read( + Device.groupsByIdProvider(element.id).notifier, + ); + final groups = groupsState.state.toList(); + + groups.remove(deviceGroup.key); + if (groups.isEmpty) { + MyHomePage.removeDevice(context, ref, element.id, pop: false); + } + groupsState.state = groups; + } + ref.read(deviceManagerProvider.notifier).saveDeviceGroups(); + + break; + case 'Edit': + Navigator.push( + context, + MaterialPageRoute( + builder: (final c) => GroupDevices(deviceGroup.key, false), + ), + ); + + break; + + default: + break; + } + } +} + +class MyHomePage extends ConsumerWidget { + const MyHomePage({super.key, this.title}); + final String? title; + + Widget buildBodyGrouped(final BuildContext context, final WidgetRef ref) { + final deviceGroupsRaw = ref.watch(filteredDevicesProvider); + final deviceGroupsMap = deviceGroupsRaw.groupManyBy( + (final x) => ref.watch(Device.groupsByIdProvider(x.id)), + ); + final deviceGroups = deviceGroupsMap.entries.sorted( + (final a, final b) => b.value.length.compareTo(a.value.length), + ); + + final versionAndUrl = ref.watch(versionAndUrlProvider); + if (versionAndUrl != null) { + WidgetsBinding.instance.addPostFrameCallback((final _) async { + await UpdateManager.displayNotificationDialog(context, versionAndUrl); + }); + } + + return Consumer( + builder: (final context, final ref, final child) { + return MasonryGridView.extent( + maxCrossAxisExtent: ref.watch(maxCrossAxisExtentProvider), + mainAxisSpacing: 8, + crossAxisSpacing: 8, + itemCount: deviceGroups.length, + itemBuilder: (final context, final i) { + final deviceGroup = deviceGroups.elementAt(i); + //if (deviceGroup == null) return const Text("Empty Entry"); + return Container( + margin: const EdgeInsets.only( + left: 2, + top: 4, + right: 2, + bottom: 2, + ), + child: DashboardGroup(deviceGroup: deviceGroup), + ); + }, + ); + }, + ); + } + + Widget buildBody(final BuildContext context, final WidgetRef ref) { + final devices = ref.watch(filteredDevicesProvider); + + return Consumer( + builder: (final context, final ref, final child) { + return MasonryGridView.extent( + maxCrossAxisExtent: ref.watch(maxCrossAxisExtentProvider), + itemCount: devices.length, + itemBuilder: (final context, final i) { + final device = devices[i]; + return Container( + margin: const EdgeInsets.only( + left: 2, + top: 4, + right: 2, + bottom: 2, + ), + child: DashboardCard( + device: device, + onLongPress: () { + deviceAction(context, ref, device); + }, + ), + ); + }, + // staggeredTileBuilder: (final int index) => const StaggeredTile.fit(1) + ); + }, + ); + } + + static void deviceAction( + final BuildContext context, + final WidgetRef ref, + final Device d, + ) { + final actions = []; + + actions.add( + SimpleDialogOption( + child: const Text("Umbenennen"), + onPressed: () => renameDevice(context, ref, d), + ), + ); + actions.add( + SimpleDialogOption( + child: const Text("Entfernen"), + onPressed: () => removeDevice(context, ref, d.id), + ), + ); + + final dialog = SimpleDialog( + title: Consumer( + builder: (final context, final ref, final child) { + return Text( + "Gerät ${ref.watch(BaseModel.friendlyNameProvider(d.id))}", + ); + }, + ), + children: actions, + ); + showDialog(context: context, builder: (final b) => dialog); + } + + static Future addDevice( + final DeviceOverviewModel device, + final WidgetRef ref, + ) async { + for (final item in device.typeNames) { + if (await tryCreateDevice(device, item, ref)) return; + } + } + + static Future tryCreateDevice( + final DeviceOverviewModel device, + final String item, + final WidgetRef ref, + ) async { + if (!deviceCtorFactory.containsKey(item) || + !stringNameJsonFactory.containsKey(item)) { + return false; + } + + ref.read(deviceManagerProvider.notifier).subscribeToDevice(device.id); + return true; + } + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final _ = ref.watch(brightnessProvider(AdaptiveTheme.of(context))); + final settings = ref.watch(groupingEnabledProvider); + + return Scaffold( + appBar: AppBar( + title: ref.watch(getTitleWidgetProvider), + leading: IconButton( + icon: Icon(ref.watch(infoIconProvider)), + onPressed: () => ref.invalidate(connectionManagerProvider), + ), + actions: [ + // ref.watch(showPinProvider).when( + // data: (final data) { + // final showPin = data.item1; + // if (!showPin) return const SizedBox(); + + // final pinned = data.item2; + // return IconButton( + // onPressed: () => pinned ? stopKioskMode() : startKioskMode(), + // icon: const Icon(Icons.pin_drop_outlined)); + // }, + // error: (final error, final stackTrace) => const SizedBox(), + // loading: () => const SizedBox(), + // ), + IconButton( + onPressed: () { + final notifier = ref.watch(searchEnabledProvider.notifier); + notifier.state = !notifier.state; + }, + icon: ref.watch(searchEnabledProvider) + ? const Icon(Icons.cancel_outlined) + : const Icon(Icons.search), + ), + PopupMenuButton( + icon: const Icon(Icons.sort), + onSelected: (final v) => selectedSort(ref, v), + itemBuilder: (final BuildContext context) => + >[ + const PopupMenuItem( + value: 'Name', + child: Text("Name"), + ), + const PopupMenuItem( + value: 'Typ', + child: Text("Gerätetyp"), + ), + const PopupMenuItem(value: 'Id', child: Text("Id")), + ], + ), + PopupMenuButton( + onSelected: (final v) => selectedOption(context, v, ref), + itemBuilder: (final BuildContext context) => + >[ + const PopupMenuItem( + value: 'RemoveAll', + child: Text("Entferne alle Geräte"), + ), + const PopupMenuItem( + value: 'Notifications', + child: Text("Alle Benachrichtigungen"), + ), + const PopupMenuItem( + value: 'Settings', + child: Text("Einstellungen"), + ), + ], + ), + ], + ), + body: Container( + decoration: ThemeManager.getBackgroundDecoration(context), + child: RefreshIndicator( + child: ConstrainedBox( + constraints: BoxConstraints.tight(Size.infinite), + child: OrientationBuilder( + builder: (final context, final orientation) => settings + ? buildBodyGrouped(context, ref) + : buildBody(context, ref), + ), + ), + onRefresh: () async => ref.invalidate(connectionManagerProvider), + ), + ), + floatingActionButton: ExpandableFab( + distance: 64.0, + children: [ + ActionButton( + onPressed: () => addNewDevice(context, ref), + icon: const Icon(Icons.add), + ), + ActionButton( + onPressed: () => addGroup(context), + icon: const Icon(Icons.group_rounded), + ), + ], + ), + ); + } + + static Future selectedOption( + final BuildContext context, + final String value, + final WidgetRef ref, + ) async { + switch (value) { + case "Debug": + DeviceManager.showDebugInformation = + !DeviceManager.showDebugInformation; + break; + case "Theme": + AdaptiveTheme.of(context).toggleThemeMode(); + break; + case "RemoveAll": + ref.read(deviceManagerProvider.notifier).removeAllDevices(); + break; + case "Info": + Navigator.push( + context, + MaterialPageRoute(builder: (final c) => const AboutPage()), + ); + break; + case "Notifications": + final layoutNotifier = ref.read(deviceLayoutsProvider.notifier); + final connection = ref.read(apiProvider); + + final serverDevices = await connection.appDeviceOverviewGet( + onlyShowInApp: false, + ); + final devices = serverDevices.bodyOrThrow; + + final globalsServer = await connection + .notificationAllGlobalNotificationsGet(); + final globals = globalsServer.bodyOrThrow + .map((final x) => ("Global", null, x)) + .toList(); + + final perDevice = devices + .map((final x) => (x, layoutNotifier.getLayout(x.id, x.typeName))) + .where((final x) => x.$2?.notificationSetup?.isNotEmpty ?? false) + .mapMany( + (final x) => x.$2!.notificationSetup!.map((final y) => (x.$1, y)), + ) + .where( + (final x) => + !x.$2.global && + (x.$2.deviceIds == null || + x.$2.deviceIds!.isEmpty || + x.$2.deviceIds!.contains(x.$1.id)), + ) + .map((final x) => (x.$1.friendlyName, x.$1.id, x.$2)) + .toList(); + + ref.read(notificationServiceProvider.notifier).showNotificationDialog( + context, + [...globals, ...perDevice], + ); + break; + case 'Settings': + Navigator.push( + context, + MaterialPageRoute(builder: (final c) => const SettingsPage()), + ); + break; + } + } + + static Future removeDevice( + final BuildContext context, + final WidgetRef ref, + final int id, { + final bool pop = true, + }) async { + if (pop) Navigator.pop(context); + + final devices = ref.read(deviceManagerProvider.notifier); + await devices.removeDevice(id); + ref.invalidate(sortedDeviceProvider); + } + + static void renameDevice( + final BuildContext context, + final WidgetRef ref, + final Device x, + ) { + showDialog( + context: context, + builder: (final BuildContext context) => SimpleDialogSingleInput.create( + context: context, + title: "Gerät benennen", + hintText: "Name für das Gerät", + labelText: "Name", + defaultText: ref.read(BaseModel.friendlyNameProvider(x.id)), + maxLines: 2, + onSubmitted: (final s) async { + x.updateDeviceOnServer(x.id, s, ref.read(apiProvider)); + }, + ), + ).then((final x) => Navigator.of(context).pop()); + } + + void selectedSort(final WidgetRef ref, final String value) { + final currentSortState = ref.watch(deviceSortProvider.notifier); + SortTypes newSort = currentSortState.state; + switch (value) { + case "Name": + newSort = newSort == SortTypes.NameAsc + ? SortTypes.NameDesc + : SortTypes.NameAsc; + break; + case "Typ": + newSort = newSort == SortTypes.TypeAsc + ? SortTypes.TypeDesc + : SortTypes.TypeAsc; + break; + case "Id": + newSort = newSort == SortTypes.IdAsd + ? SortTypes.IdDesc + : SortTypes.IdAsd; + break; + } + currentSortState.state = newSort; + PreferencesManager.instance.setInt("SortOrder", newSort.index); + } + + static Future addNewDevice( + final BuildContext context, + final WidgetRef ref, + ) async { + final connection = ref.read(apiProvider); + + final serverDevices = await connection.appDeviceOverviewGet(); + + final serverDevicesList = serverDevices.bodyOrThrow; + + final devicesFuture = ref.read(deviceManagerProvider); + if (!devicesFuture.hasValue) return; + + final dialog = HookConsumer( + builder: (final context, final ref, final child) { + final devices = devicesFuture.requireValue; + final dialogRows = []; + final selecteds = useState>([]); + final allSelected = selecteds.value.length == serverDevicesList.length; + return AlertDialog( + title: Text("Add new Smarthome Device"), + + content: Builder( + builder: (final context) { + for (final dev in serverDevicesList) { + if (!devices.any((final x) => x.id == dev.id)) { + final selected = selecteds.value.contains(dev.id); + dialogRows.add( + CheckboxListTile( + value: selected, + onChanged: (final c) { + if (selected) { + selecteds.value = [ + ...selecteds.value.where((final x) => x != dev.id), + ]; + } else { + selecteds.value = [...selecteds.value, dev.id]; + } + }, + title: Text( + dev.friendlyName, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text(dev.typeName), + ), + ); + } + } + + return SingleChildScrollView(child: Column(children: dialogRows)); + }, + ), + actions: [ + SimpleDialogOption( + child: allSelected + ? const Text("Deselect all") + : const Text("Select all"), + onPressed: () async { + selecteds.value = allSelected + ? [] + : serverDevicesList.map((final x) => x.id).toList(); + }, + ), + + MaterialButton( + onPressed: () => subscribeTo(context, ref, selecteds.value), + child: const Text("Subscribe to selected"), + ), + ], + ); + }, + ); + await showDialog(context: context, builder: (final b) => dialog); + } + + static void subscribeTo( + final BuildContext context, + final WidgetRef ref, + final List devices, + ) { + ref.read(deviceManagerProvider.notifier).subscribeToDevices(devices); + Navigator.pop(context); + } + + static void refresh(final WidgetRef ref) { + ref.read(connectionManagerProvider.notifier).newHubConnection().then(( + final value, + ) async { + await ref.read(deviceManagerProvider.notifier).reloadCurrentDevices(); + }); + } + + static void addGroup(final BuildContext context) { + final sd = SimpleDialogSingleInput.create( + hintText: "Name der neuen Gruppe", + labelText: "Name", + acceptButtonText: "Erstellen", + cancelButtonText: "Abbrechen", + onSubmitted: (final s) { + Navigator.push( + context, + MaterialPageRoute(builder: (final c) => GroupDevices(s, true)), + ); + }, + title: "Neue Gruppe", + context: context, + ); + showDialog(builder: (final BuildContext context) => sd, context: context); + } +} diff --git a/lib/notifications/app_notification.dart b/lib/notifications/app_notification.dart new file mode 100644 index 0000000..f341b36 --- /dev/null +++ b/lib/notifications/app_notification.dart @@ -0,0 +1,83 @@ +import 'dart:convert'; + +import 'package:json_annotation/json_annotation.dart'; + +part 'app_notification.g.dart'; + +@JsonSerializable(explicitToJson: true) +class AppNotification { + const AppNotification({ + required this.topic, + required this.wasOneTime, + }); + static const fromJsonFactory = AppNotification.fromJson; + + factory AppNotification.fromJson(final Map json) { + switch (json["typeName"]) { + case "VisibleAppNotification": + return VisibleAppNotification.fromJson(json); + case "AppNotification": + default: + return _$AppNotificationFromJson(json); + } + } + + static const toJsonFactory = _$AppNotificationToJson; + Map toJson() => _$AppNotificationToJson(this); + + @JsonKey(name: 'topic') + final String topic; + @JsonKey(name: 'wasOneTime') + final bool wasOneTime; + + @override + String toString() => jsonEncode(this); + + AppNotification copyWith({final String? topic, final bool? wasOneTime}) { + return AppNotification( + topic: topic ?? this.topic, wasOneTime: wasOneTime ?? this.wasOneTime); + } +} + +@JsonSerializable(explicitToJson: true) +class VisibleAppNotification extends AppNotification { + VisibleAppNotification({ + required super.topic, + required super.wasOneTime, + required this.title, + this.body, + this.ttl, + }); + + @JsonKey(name: 'title') + final String title; + @JsonKey(name: 'body') + final String? body; + @JsonKey(name: 'ttl') + final int? ttl; + static const fromJsonFactory = AppNotification.fromJson; + + factory VisibleAppNotification.fromJson(final Map json) => + _$VisibleAppNotificationFromJson(json); + + static const toJsonFactory = _$VisibleAppNotificationToJson; + @override + Map toJson() => _$VisibleAppNotificationToJson(this); + + @override + AppNotification copyWith({ + final String? title, + final String? body, + final String? topic, + final int? ttl, + final bool? wasOneTime, + }) { + return VisibleAppNotification( + title: title ?? this.title, + body: body ?? this.body, + topic: topic ?? this.topic, + ttl: ttl ?? this.ttl, + wasOneTime: wasOneTime ?? this.wasOneTime, + ); + } +} diff --git a/lib/restapi/client_index.dart b/lib/restapi/client_index.dart new file mode 100644 index 0000000..1f599c0 --- /dev/null +++ b/lib/restapi/client_index.dart @@ -0,0 +1 @@ +export 'swagger.swagger.dart' show Swagger; diff --git a/lib/restapi/client_mapping.dart b/lib/restapi/client_mapping.dart new file mode 100644 index 0000000..1a331a3 --- /dev/null +++ b/lib/restapi/client_mapping.dart @@ -0,0 +1,4 @@ +// coverage:ignore-file +// ignore_for_file: type=lint + +final Map)> generatedMapping = {}; diff --git a/lib/restapi/swagger.enums.swagger.dart b/lib/restapi/swagger.enums.swagger.dart new file mode 100644 index 0000000..97451b1 --- /dev/null +++ b/lib/restapi/swagger.enums.swagger.dart @@ -0,0 +1,92 @@ +// coverage:ignore-file +// ignore_for_file: type=lint + +import 'package:json_annotation/json_annotation.dart'; + +enum AppLogLevel { + @JsonValue(null) + swaggerGeneratedUnknown(null), + + @JsonValue(0) + fatal(0), + @JsonValue(1) + error(1), + @JsonValue(2) + warning(2), + @JsonValue(3) + info(3), + @JsonValue(4) + debug(4); + + final int? value; + + const AppLogLevel(this.value); +} + +enum DasboardSpecialType { + @JsonValue(null) + swaggerGeneratedUnknown(null), + + @JsonValue('None') + none('None'), + @JsonValue('Right') + right('Right'), + @JsonValue('Availability') + availability('Availability'), + @JsonValue('Color') + color('Color'), + @JsonValue('Disabled') + disabled('Disabled'), + @JsonValue('Battery') + battery('Battery'); + + final String? value; + + const DasboardSpecialType(this.value); +} + +enum FontStyleSetting { + @JsonValue(null) + swaggerGeneratedUnknown(null), + + @JsonValue('Normal') + normal('Normal'), + @JsonValue('Italic') + italic('Italic'); + + final String? value; + + const FontStyleSetting(this.value); +} + +enum FontWeightSetting { + @JsonValue(null) + swaggerGeneratedUnknown(null), + + @JsonValue('Normal') + normal('Normal'), + @JsonValue('Bold') + bold('Bold'); + + final String? value; + + const FontWeightSetting(this.value); +} + +enum MessageType { + @JsonValue(null) + swaggerGeneratedUnknown(null), + + @JsonValue('Get') + $get('Get'), + @JsonValue('Update') + update('Update'), + @JsonValue('Options') + options('Options'), + @JsonValue('Relay') + relay('Relay'); + + final String? value; + + const MessageType(this.value); +} diff --git a/lib/restapi/swagger.metadata.swagger.dart b/lib/restapi/swagger.metadata.swagger.dart new file mode 100644 index 0000000..b22e1de --- /dev/null +++ b/lib/restapi/swagger.metadata.swagger.dart @@ -0,0 +1,31 @@ +// ignore_for_file: type=lint + +/// Metadata class containing information from SwaggerRequest +class SwaggerMetaData { + const SwaggerMetaData({ + required this.summary, + required this.description, + required this.operationId, + required this.consumes, + required this.produces, + required this.security, + required this.tags, + required this.deprecated, + }); + + final String summary; + + final String description; + + final String operationId; + + final List consumes; + + final List produces; + + final List security; + + final List tags; + + final bool deprecated; +} diff --git a/lib/restapi/swagger.swagger.chopper.dart b/lib/restapi/swagger.swagger.chopper.dart new file mode 100644 index 0000000..ce25f9d --- /dev/null +++ b/lib/restapi/swagger.swagger.chopper.dart @@ -0,0 +1,972 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format width=80 + +part of 'swagger.swagger.dart'; + +// ************************************************************************** +// ChopperGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +final class _$Swagger extends Swagger { + _$Swagger([ChopperClient? client]) { + if (client == null) return; + this.client = client; + } + + @override + final Type definitionType = Swagger; + + @override + Future> _appGet({ + String? name, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app'); + final Map $params = {'name': name}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appPatch({ + String? id, + String? newName, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app'); + final Map $params = { + 'id': id, + 'newName': newName, + }; + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appSettingsGet({ + String? id, + String? key, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/settings'); + final Map $params = { + 'id': id, + 'key': key, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appSettingsPost({ + String? id, + String? key, + String? $value, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/settings'); + final Map $params = { + 'id': id, + 'key': key, + 'value': $value, + }; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _deviceRebuildIdPatch({ + required int? id, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device/rebuild/${id}'); + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _devicePatch({ + bool? onlyNew, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device'); + final Map $params = {'onlyNew': onlyNew}; + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _devicePut({ + required RestCreatedDevice? body, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device'); + final $body = body; + final Request $request = Request( + 'PUT', + $url, + client.baseUrl, + body: $body, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _deviceGet({ + bool? includeState, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device'); + final Map $params = { + 'includeState': includeState, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _deviceStatesIdGet({ + required int? id, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device/states/${id}'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _deviceStatesIdPost({ + required int? id, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device/states/${id}'); + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _deviceHistoryIdGet({ + required int? id, + DateTime? from, + DateTime? to, + String? propName, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device/history/${id}'); + final Map $params = { + 'from': from, + 'to': to, + 'propName': propName, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _deviceStateIdNameGet({ + required int? id, + required String? name, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device/state/${id}/${name}'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _deviceStateIdPost({ + required int? id, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/device/state/${id}'); + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future>>> _appDeviceGet({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/device'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send>, Device>($request); + } + + @override + Future> _appDevicePatch({ + required DeviceRenameRequest? body, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/device'); + final $body = body; + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + body: $body, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future>> _appDeviceOverviewGet({ + bool? onlyShowInApp, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/device/overview'); + final Map $params = { + 'onlyShowInApp': onlyShowInApp, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send, DeviceOverview>($request); + } + + @override + Future>> _appHistorySettingsGet({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/history/settings'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send, HistoryPropertyState>( + $request, + ); + } + + @override + Future> _appHistoryPatch({ + required SetHistoryRequest? body, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/history'); + final $body = body; + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + body: $body, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future>> _appHistoryGet({ + int? id, + DateTime? dt, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/history'); + final Map $params = {'id': id, 'dt': dt}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send, History>($request); + } + + @override + Future> _appHistoryRangeGet({ + int? id, + DateTime? from, + DateTime? to, + String? propertyName, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/history/range'); + final Map $params = { + 'id': id, + 'from': from, + 'to': to, + 'propertyName': propertyName, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appLayoutSingleGet({ + String? typeName, + String? iconName, + int? deviceId, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/layout/single'); + final Map $params = { + 'TypeName': typeName, + 'IconName': iconName, + 'DeviceId': deviceId, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future>> _appLayoutMultiGet({ + List? request, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/layout/multi'); + final Map $params = {'request': request}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send, LayoutResponse>($request); + } + + @override + Future>> _appLayoutAllGet({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/layout/all'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send, LayoutResponse>($request); + } + + @override + Future> _appLayoutIconByNameGet({ + String? name, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/layout/iconByName'); + final Map $params = {'name': name}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appLayoutPatch({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/layout'); + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _notificationSendNotificationPost({ + required AppNotification? body, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/notification/sendNotification'); + final $body = body; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _notificationFirebaseOptionsGet({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/notification/firebaseOptions'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _notificationNextNotificationIdGet({ + String? uniqueName, + int? deviceId, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/notification/nextNotificationId'); + final Map $params = { + 'uniqueName': uniqueName, + 'deviceId': deviceId, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> + _notificationAllOneTimeNotificationsGet({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/notification/allOneTimeNotifications'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send( + $request, + ); + } + + @override + Future>> + _notificationAllGlobalNotificationsGet({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/notification/allGlobalNotifications'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send, NotificationSetup>($request); + } + + @override + Future> _painlessTimePut({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Painless"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/painless/time'); + final Request $request = Request( + 'PUT', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _securityGet({ + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Security"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/security'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appSmarthomePost({ + required JsonApiSmarthomeMessage? body, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Smarthome"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/smarthome'); + final $body = body; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appSmarthomeGet({ + int? deviceId, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Smarthome"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/smarthome'); + final Map $params = { + 'deviceId': deviceId, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _appSmarthomeLogPost({ + required List? body, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Smarthome"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/app/smarthome/log'); + final $body = body; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + tag: swaggerMetaData, + ); + return client.send($request); + } + + @override + Future> _windmillPost({ + required WindmillSmarthomeMessage? body, + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Windmill"], + deprecated: false, + ), + }) { + final Uri $url = Uri.parse('/windmill'); + final $body = body; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + tag: swaggerMetaData, + ); + return client.send($request); + } +} diff --git a/lib/restapi/swagger.swagger.dart b/lib/restapi/swagger.swagger.dart new file mode 100644 index 0000000..83ccaf0 --- /dev/null +++ b/lib/restapi/swagger.swagger.dart @@ -0,0 +1,3894 @@ +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element_parameter + +import 'package:json_annotation/json_annotation.dart'; +import 'package:collection/collection.dart'; +import 'dart:convert'; + +import '../devices/device_exporter.dart'; +import 'package:chopper/chopper.dart'; + +import 'client_mapping.dart'; +import 'dart:async'; +import 'package:http/http.dart' as http; +import 'package:chopper/chopper.dart' as chopper; +import 'swagger.enums.swagger.dart' as enums; +import 'swagger.metadata.swagger.dart'; +export 'swagger.enums.swagger.dart'; + +part 'swagger.swagger.chopper.dart'; +part 'swagger.swagger.g.dart'; + +// ************************************************************************** +// SwaggerChopperGenerator +// ************************************************************************** + +@ChopperApi() +abstract class Swagger extends ChopperService { + static Swagger create({ + ChopperClient? client, + http.Client? httpClient, + Authenticator? authenticator, + ErrorConverter? errorConverter, + Converter? converter, + Uri? baseUrl, + List? interceptors, + }) { + if (client != null) { + return _$Swagger(client); + } + + final newClient = ChopperClient( + services: [_$Swagger()], + converter: converter ?? $JsonSerializableConverter(), + interceptors: interceptors ?? [], + client: httpClient, + authenticator: authenticator, + errorConverter: errorConverter, + baseUrl: baseUrl ?? Uri.parse('http://'), + ); + return _$Swagger(newClient); + } + + /// + ///@param name + Future> appGet({String? name}) { + return _appGet(name: name); + } + + /// + ///@param name + @GET(path: '/app') + Future> _appGet({ + @Query('name') String? name, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }); + + /// + ///@param id + ///@param newName + Future appPatch({String? id, String? newName}) { + return _appPatch(id: id, newName: newName); + } + + /// + ///@param id + ///@param newName + @PATCH(path: '/app', optionalBody: true) + Future _appPatch({ + @Query('id') String? id, + @Query('newName') String? newName, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }); + + /// + ///@param id + ///@param key + Future> appSettingsGet({String? id, String? key}) { + return _appSettingsGet(id: id, key: key); + } + + /// + ///@param id + ///@param key + @GET(path: '/app/settings') + Future> _appSettingsGet({ + @Query('id') String? id, + @Query('key') String? key, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }); + + /// + ///@param id + ///@param key + ///@param value + Future> appSettingsPost({ + String? id, + String? key, + String? $value, + }) { + return _appSettingsPost(id: id, key: key, $value: $value); + } + + /// + ///@param id + ///@param key + ///@param value + @POST(path: '/app/settings', optionalBody: true) + Future> _appSettingsPost({ + @Query('id') String? id, + @Query('key') String? key, + @Query('value') String? $value, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["App"], + deprecated: false, + ), + }); + + /// + ///@param id + Future deviceRebuildIdPatch({required int? id}) { + return _deviceRebuildIdPatch(id: id); + } + + /// + ///@param id + @PATCH(path: '/device/rebuild/{id}', optionalBody: true) + Future _deviceRebuildIdPatch({ + @Path('id') required int? id, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param onlyNew + Future devicePatch({bool? onlyNew}) { + return _devicePatch(onlyNew: onlyNew); + } + + /// + ///@param onlyNew + @PATCH(path: '/device', optionalBody: true) + Future _devicePatch({ + @Query('onlyNew') bool? onlyNew, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + Future devicePut({required RestCreatedDevice? body}) { + return _devicePut(body: body); + } + + /// + @PUT(path: '/device', optionalBody: true) + Future _devicePut({ + @Body() required RestCreatedDevice? body, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param includeState + Future deviceGet({bool? includeState}) { + return _deviceGet(includeState: includeState); + } + + /// + ///@param includeState + @GET(path: '/device') + Future _deviceGet({ + @Query('includeState') bool? includeState, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param id + Future deviceStatesIdGet({required int? id}) { + return _deviceStatesIdGet(id: id); + } + + /// + ///@param id + @GET(path: '/device/states/{id}') + Future _deviceStatesIdGet({ + @Path('id') required int? id, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param id + Future deviceStatesIdPost({required int? id}) { + return _deviceStatesIdPost(id: id); + } + + /// + ///@param id + @POST(path: '/device/states/{id}', optionalBody: true) + Future _deviceStatesIdPost({ + @Path('id') required int? id, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param id + ///@param from + ///@param to + ///@param propName + Future deviceHistoryIdGet({ + required int? id, + DateTime? from, + DateTime? to, + String? propName, + }) { + return _deviceHistoryIdGet(id: id, from: from, to: to, propName: propName); + } + + /// + ///@param id + ///@param from + ///@param to + ///@param propName + @GET(path: '/device/history/{id}') + Future _deviceHistoryIdGet({ + @Path('id') required int? id, + @Query('from') DateTime? from, + @Query('to') DateTime? to, + @Query('propName') String? propName, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param id + ///@param name + Future deviceStateIdNameGet({ + required int? id, + required String? name, + }) { + return _deviceStateIdNameGet(id: id, name: name); + } + + /// + ///@param id + ///@param name + @GET(path: '/device/state/{id}/{name}') + Future _deviceStateIdNameGet({ + @Path('id') required int? id, + @Path('name') required String? name, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param id + Future deviceStateIdPost({required int? id}) { + return _deviceStateIdPost(id: id); + } + + /// + ///@param id + @POST(path: '/device/state/{id}', optionalBody: true) + Future _deviceStateIdPost({ + @Path('id') required int? id, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + Future>> appDeviceGet() { + generatedMapping.putIfAbsent(Device, () => Device.fromJsonFactory); + + return _appDeviceGet(); + } + + /// + @GET(path: '/app/device') + Future>> _appDeviceGet({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + Future appDevicePatch({ + required DeviceRenameRequest? body, + }) { + return _appDevicePatch(body: body); + } + + /// + @PATCH(path: '/app/device', optionalBody: true) + Future _appDevicePatch({ + @Body() required DeviceRenameRequest? body, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + ///@param onlyShowInApp + Future>> appDeviceOverviewGet({ + bool? onlyShowInApp, + }) { + generatedMapping.putIfAbsent( + DeviceOverview, + () => DeviceOverview.fromJsonFactory, + ); + + return _appDeviceOverviewGet(onlyShowInApp: onlyShowInApp); + } + + /// + ///@param onlyShowInApp + @GET(path: '/app/device/overview') + Future>> _appDeviceOverviewGet({ + @Query('onlyShowInApp') bool? onlyShowInApp, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Device"], + deprecated: false, + ), + }); + + /// + Future>> appHistorySettingsGet() { + generatedMapping.putIfAbsent( + HistoryPropertyState, + () => HistoryPropertyState.fromJsonFactory, + ); + + return _appHistorySettingsGet(); + } + + /// + @GET(path: '/app/history/settings') + Future>> _appHistorySettingsGet({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }); + + /// + Future appHistoryPatch({required SetHistoryRequest? body}) { + return _appHistoryPatch(body: body); + } + + /// + @PATCH(path: '/app/history', optionalBody: true) + Future _appHistoryPatch({ + @Body() required SetHistoryRequest? body, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }); + + /// + ///@param id + ///@param dt + Future>> appHistoryGet({ + int? id, + DateTime? dt, + }) { + generatedMapping.putIfAbsent(History, () => History.fromJsonFactory); + + return _appHistoryGet(id: id, dt: dt); + } + + /// + ///@param id + ///@param dt + @GET(path: '/app/history') + Future>> _appHistoryGet({ + @Query('id') int? id, + @Query('dt') DateTime? dt, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }); + + /// + ///@param id + ///@param from + ///@param to + ///@param propertyName + Future> appHistoryRangeGet({ + int? id, + DateTime? from, + DateTime? to, + String? propertyName, + }) { + generatedMapping.putIfAbsent(History, () => History.fromJsonFactory); + + return _appHistoryRangeGet( + id: id, + from: from, + to: to, + propertyName: propertyName, + ); + } + + /// + ///@param id + ///@param from + ///@param to + ///@param propertyName + @GET(path: '/app/history/range') + Future> _appHistoryRangeGet({ + @Query('id') int? id, + @Query('from') DateTime? from, + @Query('to') DateTime? to, + @Query('propertyName') String? propertyName, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["History"], + deprecated: false, + ), + }); + + /// + ///@param TypeName + ///@param IconName + ///@param DeviceId + Future> appLayoutSingleGet({ + String? typeName, + String? iconName, + int? deviceId, + }) { + generatedMapping.putIfAbsent( + LayoutResponse, + () => LayoutResponse.fromJsonFactory, + ); + + return _appLayoutSingleGet( + typeName: typeName, + iconName: iconName, + deviceId: deviceId, + ); + } + + /// + ///@param TypeName + ///@param IconName + ///@param DeviceId + @GET(path: '/app/layout/single') + Future> _appLayoutSingleGet({ + @Query('TypeName') String? typeName, + @Query('IconName') String? iconName, + @Query('DeviceId') int? deviceId, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }); + + /// + ///@param request + Future>> appLayoutMultiGet({ + List? request, + }) { + generatedMapping.putIfAbsent( + LayoutResponse, + () => LayoutResponse.fromJsonFactory, + ); + + return _appLayoutMultiGet(request: request); + } + + /// + ///@param request + @GET(path: '/app/layout/multi') + Future>> _appLayoutMultiGet({ + @Query('request') List? request, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }); + + /// + Future>> appLayoutAllGet() { + generatedMapping.putIfAbsent( + LayoutResponse, + () => LayoutResponse.fromJsonFactory, + ); + + return _appLayoutAllGet(); + } + + /// + @GET(path: '/app/layout/all') + Future>> _appLayoutAllGet({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }); + + /// + ///@param name + Future> appLayoutIconByNameGet({String? name}) { + generatedMapping.putIfAbsent(SvgIcon, () => SvgIcon.fromJsonFactory); + + return _appLayoutIconByNameGet(name: name); + } + + /// + ///@param name + @GET(path: '/app/layout/iconByName') + Future> _appLayoutIconByNameGet({ + @Query('name') String? name, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }); + + /// + Future appLayoutPatch() { + return _appLayoutPatch(); + } + + /// + @PATCH(path: '/app/layout', optionalBody: true) + Future _appLayoutPatch({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Layout"], + deprecated: false, + ), + }); + + /// + Future notificationSendNotificationPost({ + required AppNotification? body, + }) { + return _notificationSendNotificationPost(body: body); + } + + /// + @POST(path: '/notification/sendNotification', optionalBody: true) + Future _notificationSendNotificationPost({ + @Body() required AppNotification? body, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }); + + /// + Future> notificationFirebaseOptionsGet() { + return _notificationFirebaseOptionsGet(); + } + + /// + @GET(path: '/notification/firebaseOptions') + Future> _notificationFirebaseOptionsGet({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }); + + /// + ///@param uniqueName + ///@param deviceId + Future> notificationNextNotificationIdGet({ + String? uniqueName, + int? deviceId, + }) { + return _notificationNextNotificationIdGet( + uniqueName: uniqueName, + deviceId: deviceId, + ); + } + + /// + ///@param uniqueName + ///@param deviceId + @GET(path: '/notification/nextNotificationId') + Future> _notificationNextNotificationIdGet({ + @Query('uniqueName') String? uniqueName, + @Query('deviceId') int? deviceId, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }); + + /// + Future> + notificationAllOneTimeNotificationsGet() { + generatedMapping.putIfAbsent( + AllOneTimeNotifications, + () => AllOneTimeNotifications.fromJsonFactory, + ); + + return _notificationAllOneTimeNotificationsGet(); + } + + /// + @GET(path: '/notification/allOneTimeNotifications') + Future> + _notificationAllOneTimeNotificationsGet({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }); + + /// + Future>> + notificationAllGlobalNotificationsGet() { + generatedMapping.putIfAbsent( + NotificationSetup, + () => NotificationSetup.fromJsonFactory, + ); + + return _notificationAllGlobalNotificationsGet(); + } + + /// + @GET(path: '/notification/allGlobalNotifications') + Future>> + _notificationAllGlobalNotificationsGet({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Notification"], + deprecated: false, + ), + }); + + /// + Future painlessTimePut() { + return _painlessTimePut(); + } + + /// + @PUT(path: '/painless/time', optionalBody: true) + Future _painlessTimePut({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Painless"], + deprecated: false, + ), + }); + + /// + Future> securityGet() { + generatedMapping.putIfAbsent( + AppCloudConfiguration, + () => AppCloudConfiguration.fromJsonFactory, + ); + + return _securityGet(); + } + + /// + @GET(path: '/security') + Future> _securityGet({ + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Security"], + deprecated: false, + ), + }); + + /// + Future appSmarthomePost({ + required JsonApiSmarthomeMessage? body, + }) { + return _appSmarthomePost(body: body); + } + + /// + @POST(path: '/app/smarthome', optionalBody: true) + Future _appSmarthomePost({ + @Body() required JsonApiSmarthomeMessage? body, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Smarthome"], + deprecated: false, + ), + }); + + /// + ///@param deviceId + Future appSmarthomeGet({int? deviceId}) { + return _appSmarthomeGet(deviceId: deviceId); + } + + /// + ///@param deviceId + @GET(path: '/app/smarthome') + Future _appSmarthomeGet({ + @Query('deviceId') int? deviceId, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Smarthome"], + deprecated: false, + ), + }); + + /// + Future appSmarthomeLogPost({required List? body}) { + return _appSmarthomeLogPost(body: body); + } + + /// + @POST(path: '/app/smarthome/log', optionalBody: true) + Future _appSmarthomeLogPost({ + @Body() required List? body, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Smarthome"], + deprecated: false, + ), + }); + + /// + Future windmillPost({ + required WindmillSmarthomeMessage? body, + }) { + return _windmillPost(body: body); + } + + /// + @POST(path: '/windmill', optionalBody: true) + Future _windmillPost({ + @Body() required WindmillSmarthomeMessage? body, + @chopper.Tag() + SwaggerMetaData swaggerMetaData = const SwaggerMetaData( + description: '', + summary: '', + operationId: '', + consumes: [], + produces: [], + security: [], + tags: ["Windmill"], + deprecated: false, + ), + }); +} + +@JsonSerializable(explicitToJson: true) +class AllOneTimeNotifications { + const AllOneTimeNotifications({required this.topics}); + + factory AllOneTimeNotifications.fromJson(Map json) => + _$AllOneTimeNotificationsFromJson(json); + + static const toJsonFactory = _$AllOneTimeNotificationsToJson; + Map toJson() => _$AllOneTimeNotificationsToJson(this); + + @JsonKey(name: 'topics', defaultValue: []) + final List topics; + static const fromJsonFactory = _$AllOneTimeNotificationsFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is AllOneTimeNotifications && + (identical(other.topics, topics) || + const DeepCollectionEquality().equals(other.topics, topics))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(topics) ^ runtimeType.hashCode; +} + +extension $AllOneTimeNotificationsExtension on AllOneTimeNotifications { + AllOneTimeNotifications copyWith({List? topics}) { + return AllOneTimeNotifications(topics: topics ?? this.topics); + } + + AllOneTimeNotifications copyWithWrapped({Wrapped>? topics}) { + return AllOneTimeNotifications( + topics: (topics != null ? topics.value : this.topics), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class AppCloudConfiguration { + const AppCloudConfiguration({ + required this.host, + required this.port, + required this.key, + required this.id, + }); + + factory AppCloudConfiguration.fromJson(Map json) => + _$AppCloudConfigurationFromJson(json); + + static const toJsonFactory = _$AppCloudConfigurationToJson; + Map toJson() => _$AppCloudConfigurationToJson(this); + + @JsonKey(name: 'host') + final String host; + @JsonKey(name: 'port') + final int port; + @JsonKey(name: 'key') + final String key; + @JsonKey(name: 'id') + final String id; + static const fromJsonFactory = _$AppCloudConfigurationFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is AppCloudConfiguration && + (identical(other.host, host) || + const DeepCollectionEquality().equals(other.host, host)) && + (identical(other.port, port) || + const DeepCollectionEquality().equals(other.port, port)) && + (identical(other.key, key) || + const DeepCollectionEquality().equals(other.key, key)) && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(host) ^ + const DeepCollectionEquality().hash(port) ^ + const DeepCollectionEquality().hash(key) ^ + const DeepCollectionEquality().hash(id) ^ + runtimeType.hashCode; +} + +extension $AppCloudConfigurationExtension on AppCloudConfiguration { + AppCloudConfiguration copyWith({ + String? host, + int? port, + String? key, + String? id, + }) { + return AppCloudConfiguration( + host: host ?? this.host, + port: port ?? this.port, + key: key ?? this.key, + id: id ?? this.id, + ); + } + + AppCloudConfiguration copyWithWrapped({ + Wrapped? host, + Wrapped? port, + Wrapped? key, + Wrapped? id, + }) { + return AppCloudConfiguration( + host: (host != null ? host.value : this.host), + port: (port != null ? port.value : this.port), + key: (key != null ? key.value : this.key), + id: (id != null ? id.value : this.id), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class AppLog { + const AppLog({ + required this.logLevel, + required this.timeStamp, + required this.message, + this.loggerName, + }); + + factory AppLog.fromJson(Map json) => _$AppLogFromJson(json); + + static const toJsonFactory = _$AppLogToJson; + Map toJson() => _$AppLogToJson(this); + + @JsonKey( + name: 'logLevel', + toJson: appLogLevelToJson, + fromJson: appLogLevelFromJson, + ) + final enums.AppLogLevel logLevel; + @JsonKey(name: 'timeStamp') + final DateTime timeStamp; + @JsonKey(name: 'message') + final String message; + @JsonKey(name: 'loggerName') + final String? loggerName; + static const fromJsonFactory = _$AppLogFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is AppLog && + (identical(other.logLevel, logLevel) || + const DeepCollectionEquality().equals( + other.logLevel, + logLevel, + )) && + (identical(other.timeStamp, timeStamp) || + const DeepCollectionEquality().equals( + other.timeStamp, + timeStamp, + )) && + (identical(other.message, message) || + const DeepCollectionEquality().equals( + other.message, + message, + )) && + (identical(other.loggerName, loggerName) || + const DeepCollectionEquality().equals( + other.loggerName, + loggerName, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(logLevel) ^ + const DeepCollectionEquality().hash(timeStamp) ^ + const DeepCollectionEquality().hash(message) ^ + const DeepCollectionEquality().hash(loggerName) ^ + runtimeType.hashCode; +} + +extension $AppLogExtension on AppLog { + AppLog copyWith({ + enums.AppLogLevel? logLevel, + DateTime? timeStamp, + String? message, + String? loggerName, + }) { + return AppLog( + logLevel: logLevel ?? this.logLevel, + timeStamp: timeStamp ?? this.timeStamp, + message: message ?? this.message, + loggerName: loggerName ?? this.loggerName, + ); + } + + AppLog copyWithWrapped({ + Wrapped? logLevel, + Wrapped? timeStamp, + Wrapped? message, + Wrapped? loggerName, + }) { + return AppLog( + logLevel: (logLevel != null ? logLevel.value : this.logLevel), + timeStamp: (timeStamp != null ? timeStamp.value : this.timeStamp), + message: (message != null ? message.value : this.message), + loggerName: (loggerName != null ? loggerName.value : this.loggerName), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class DashboardDeviceLayout { + const DashboardDeviceLayout({required this.dashboardProperties}); + + factory DashboardDeviceLayout.fromJson(Map json) => + _$DashboardDeviceLayoutFromJson(json); + + static const toJsonFactory = _$DashboardDeviceLayoutToJson; + Map toJson() => _$DashboardDeviceLayoutToJson(this); + + @JsonKey(name: 'dashboardProperties', defaultValue: []) + final List dashboardProperties; + static const fromJsonFactory = _$DashboardDeviceLayoutFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is DashboardDeviceLayout && + (identical(other.dashboardProperties, dashboardProperties) || + const DeepCollectionEquality().equals( + other.dashboardProperties, + dashboardProperties, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(dashboardProperties) ^ + runtimeType.hashCode; +} + +extension $DashboardDeviceLayoutExtension on DashboardDeviceLayout { + DashboardDeviceLayout copyWith({ + List? dashboardProperties, + }) { + return DashboardDeviceLayout( + dashboardProperties: dashboardProperties ?? this.dashboardProperties, + ); + } + + DashboardDeviceLayout copyWithWrapped({ + Wrapped>? dashboardProperties, + }) { + return DashboardDeviceLayout( + dashboardProperties: (dashboardProperties != null + ? dashboardProperties.value + : this.dashboardProperties), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class DetailDeviceLayout { + const DetailDeviceLayout({ + required this.propertyInfos, + required this.tabInfos, + required this.historyProperties, + }); + + factory DetailDeviceLayout.fromJson(Map json) => + _$DetailDeviceLayoutFromJson(json); + + static const toJsonFactory = _$DetailDeviceLayoutToJson; + Map toJson() => _$DetailDeviceLayoutToJson(this); + + @JsonKey(name: 'propertyInfos', defaultValue: []) + final List propertyInfos; + @JsonKey(name: 'tabInfos', defaultValue: []) + final List tabInfos; + @JsonKey(name: 'historyProperties', defaultValue: []) + final List historyProperties; + static const fromJsonFactory = _$DetailDeviceLayoutFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is DetailDeviceLayout && + (identical(other.propertyInfos, propertyInfos) || + const DeepCollectionEquality().equals( + other.propertyInfos, + propertyInfos, + )) && + (identical(other.tabInfos, tabInfos) || + const DeepCollectionEquality().equals( + other.tabInfos, + tabInfos, + )) && + (identical(other.historyProperties, historyProperties) || + const DeepCollectionEquality().equals( + other.historyProperties, + historyProperties, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(propertyInfos) ^ + const DeepCollectionEquality().hash(tabInfos) ^ + const DeepCollectionEquality().hash(historyProperties) ^ + runtimeType.hashCode; +} + +extension $DetailDeviceLayoutExtension on DetailDeviceLayout { + DetailDeviceLayout copyWith({ + List? propertyInfos, + List? tabInfos, + List? historyProperties, + }) { + return DetailDeviceLayout( + propertyInfos: propertyInfos ?? this.propertyInfos, + tabInfos: tabInfos ?? this.tabInfos, + historyProperties: historyProperties ?? this.historyProperties, + ); + } + + DetailDeviceLayout copyWithWrapped({ + Wrapped>? propertyInfos, + Wrapped>? tabInfos, + Wrapped>? historyProperties, + }) { + return DetailDeviceLayout( + propertyInfos: (propertyInfos != null + ? propertyInfos.value + : this.propertyInfos), + tabInfos: (tabInfos != null ? tabInfos.value : this.tabInfos), + historyProperties: (historyProperties != null + ? historyProperties.value + : this.historyProperties), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class DetailTabInfo { + const DetailTabInfo({ + required this.id, + required this.iconName, + required this.order, + this.linkedDevice, + required this.showOnlyInDeveloperMode, + }); + + factory DetailTabInfo.fromJson(Map json) => + _$DetailTabInfoFromJson(json); + + static const toJsonFactory = _$DetailTabInfoToJson; + Map toJson() => _$DetailTabInfoToJson(this); + + @JsonKey(name: 'id') + final int id; + @JsonKey(name: 'iconName') + final String iconName; + @JsonKey(name: 'order') + final int order; + @JsonKey(name: 'linkedDevice') + final LinkedDeviceTab? linkedDevice; + @JsonKey(name: 'showOnlyInDeveloperMode') + final bool showOnlyInDeveloperMode; + static const fromJsonFactory = _$DetailTabInfoFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is DetailTabInfo && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id)) && + (identical(other.iconName, iconName) || + const DeepCollectionEquality().equals( + other.iconName, + iconName, + )) && + (identical(other.order, order) || + const DeepCollectionEquality().equals(other.order, order)) && + (identical(other.linkedDevice, linkedDevice) || + const DeepCollectionEquality().equals( + other.linkedDevice, + linkedDevice, + )) && + (identical( + other.showOnlyInDeveloperMode, + showOnlyInDeveloperMode, + ) || + const DeepCollectionEquality().equals( + other.showOnlyInDeveloperMode, + showOnlyInDeveloperMode, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(id) ^ + const DeepCollectionEquality().hash(iconName) ^ + const DeepCollectionEquality().hash(order) ^ + const DeepCollectionEquality().hash(linkedDevice) ^ + const DeepCollectionEquality().hash(showOnlyInDeveloperMode) ^ + runtimeType.hashCode; +} + +extension $DetailTabInfoExtension on DetailTabInfo { + DetailTabInfo copyWith({ + int? id, + String? iconName, + int? order, + LinkedDeviceTab? linkedDevice, + bool? showOnlyInDeveloperMode, + }) { + return DetailTabInfo( + id: id ?? this.id, + iconName: iconName ?? this.iconName, + order: order ?? this.order, + linkedDevice: linkedDevice ?? this.linkedDevice, + showOnlyInDeveloperMode: + showOnlyInDeveloperMode ?? this.showOnlyInDeveloperMode, + ); + } + + DetailTabInfo copyWithWrapped({ + Wrapped? id, + Wrapped? iconName, + Wrapped? order, + Wrapped? linkedDevice, + Wrapped? showOnlyInDeveloperMode, + }) { + return DetailTabInfo( + id: (id != null ? id.value : this.id), + iconName: (iconName != null ? iconName.value : this.iconName), + order: (order != null ? order.value : this.order), + linkedDevice: (linkedDevice != null + ? linkedDevice.value + : this.linkedDevice), + showOnlyInDeveloperMode: (showOnlyInDeveloperMode != null + ? showOnlyInDeveloperMode.value + : this.showOnlyInDeveloperMode), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class DeviceLayout { + const DeviceLayout({ + required this.uniqueName, + required this.iconName, + this.typeName, + this.typeNames, + this.ids, + this.dashboardDeviceLayout, + this.detailDeviceLayout, + this.notificationSetup, + required this.version, + required this.showOnlyInDeveloperMode, + required this.hash, + this.additionalData, + }); + + factory DeviceLayout.fromJson(Map json) => + _$DeviceLayoutFromJson(json); + + static const toJsonFactory = _$DeviceLayoutToJson; + Map toJson() => _$DeviceLayoutToJson(this); + + @JsonKey(name: 'uniqueName') + final String uniqueName; + @JsonKey(name: 'iconName') + final String iconName; + @JsonKey(name: 'typeName') + final String? typeName; + @JsonKey(name: 'typeNames', defaultValue: []) + final List? typeNames; + @JsonKey(name: 'ids', defaultValue: []) + final List? ids; + @JsonKey(name: 'dashboardDeviceLayout') + final DashboardDeviceLayout? dashboardDeviceLayout; + @JsonKey(name: 'detailDeviceLayout') + final DetailDeviceLayout? detailDeviceLayout; + @JsonKey(name: 'notificationSetup', defaultValue: []) + final List? notificationSetup; + @JsonKey(name: 'version') + final int version; + @JsonKey(name: 'showOnlyInDeveloperMode') + final bool showOnlyInDeveloperMode; + @JsonKey(name: 'hash') + final String hash; + @JsonKey(name: 'additionalData') + final Map? additionalData; + static const fromJsonFactory = _$DeviceLayoutFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is DeviceLayout && + (identical(other.uniqueName, uniqueName) || + const DeepCollectionEquality().equals( + other.uniqueName, + uniqueName, + )) && + (identical(other.iconName, iconName) || + const DeepCollectionEquality().equals( + other.iconName, + iconName, + )) && + (identical(other.typeName, typeName) || + const DeepCollectionEquality().equals( + other.typeName, + typeName, + )) && + (identical(other.typeNames, typeNames) || + const DeepCollectionEquality().equals( + other.typeNames, + typeNames, + )) && + (identical(other.ids, ids) || + const DeepCollectionEquality().equals(other.ids, ids)) && + (identical(other.dashboardDeviceLayout, dashboardDeviceLayout) || + const DeepCollectionEquality().equals( + other.dashboardDeviceLayout, + dashboardDeviceLayout, + )) && + (identical(other.detailDeviceLayout, detailDeviceLayout) || + const DeepCollectionEquality().equals( + other.detailDeviceLayout, + detailDeviceLayout, + )) && + (identical(other.notificationSetup, notificationSetup) || + const DeepCollectionEquality().equals( + other.notificationSetup, + notificationSetup, + )) && + (identical(other.version, version) || + const DeepCollectionEquality().equals( + other.version, + version, + )) && + (identical( + other.showOnlyInDeveloperMode, + showOnlyInDeveloperMode, + ) || + const DeepCollectionEquality().equals( + other.showOnlyInDeveloperMode, + showOnlyInDeveloperMode, + )) && + (identical(other.hash, hash) || + const DeepCollectionEquality().equals(other.hash, hash)) && + (identical(other.additionalData, additionalData) || + const DeepCollectionEquality().equals( + other.additionalData, + additionalData, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(uniqueName) ^ + const DeepCollectionEquality().hash(iconName) ^ + const DeepCollectionEquality().hash(typeName) ^ + const DeepCollectionEquality().hash(typeNames) ^ + const DeepCollectionEquality().hash(ids) ^ + const DeepCollectionEquality().hash(dashboardDeviceLayout) ^ + const DeepCollectionEquality().hash(detailDeviceLayout) ^ + const DeepCollectionEquality().hash(notificationSetup) ^ + const DeepCollectionEquality().hash(version) ^ + const DeepCollectionEquality().hash(showOnlyInDeveloperMode) ^ + const DeepCollectionEquality().hash(hash) ^ + const DeepCollectionEquality().hash(additionalData) ^ + runtimeType.hashCode; +} + +extension $DeviceLayoutExtension on DeviceLayout { + DeviceLayout copyWith({ + String? uniqueName, + String? iconName, + String? typeName, + List? typeNames, + List? ids, + DashboardDeviceLayout? dashboardDeviceLayout, + DetailDeviceLayout? detailDeviceLayout, + List? notificationSetup, + int? version, + bool? showOnlyInDeveloperMode, + String? hash, + Map? additionalData, + }) { + return DeviceLayout( + uniqueName: uniqueName ?? this.uniqueName, + iconName: iconName ?? this.iconName, + typeName: typeName ?? this.typeName, + typeNames: typeNames ?? this.typeNames, + ids: ids ?? this.ids, + dashboardDeviceLayout: + dashboardDeviceLayout ?? this.dashboardDeviceLayout, + detailDeviceLayout: detailDeviceLayout ?? this.detailDeviceLayout, + notificationSetup: notificationSetup ?? this.notificationSetup, + version: version ?? this.version, + showOnlyInDeveloperMode: + showOnlyInDeveloperMode ?? this.showOnlyInDeveloperMode, + hash: hash ?? this.hash, + additionalData: additionalData ?? this.additionalData, + ); + } + + DeviceLayout copyWithWrapped({ + Wrapped? uniqueName, + Wrapped? iconName, + Wrapped? typeName, + Wrapped?>? typeNames, + Wrapped?>? ids, + Wrapped? dashboardDeviceLayout, + Wrapped? detailDeviceLayout, + Wrapped?>? notificationSetup, + Wrapped? version, + Wrapped? showOnlyInDeveloperMode, + Wrapped? hash, + Wrapped?>? additionalData, + }) { + return DeviceLayout( + uniqueName: (uniqueName != null ? uniqueName.value : this.uniqueName), + iconName: (iconName != null ? iconName.value : this.iconName), + typeName: (typeName != null ? typeName.value : this.typeName), + typeNames: (typeNames != null ? typeNames.value : this.typeNames), + ids: (ids != null ? ids.value : this.ids), + dashboardDeviceLayout: (dashboardDeviceLayout != null + ? dashboardDeviceLayout.value + : this.dashboardDeviceLayout), + detailDeviceLayout: (detailDeviceLayout != null + ? detailDeviceLayout.value + : this.detailDeviceLayout), + notificationSetup: (notificationSetup != null + ? notificationSetup.value + : this.notificationSetup), + version: (version != null ? version.value : this.version), + showOnlyInDeveloperMode: (showOnlyInDeveloperMode != null + ? showOnlyInDeveloperMode.value + : this.showOnlyInDeveloperMode), + hash: (hash != null ? hash.value : this.hash), + additionalData: (additionalData != null + ? additionalData.value + : this.additionalData), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class DeviceOverview { + const DeviceOverview({ + required this.id, + required this.friendlyName, + required this.typeName, + required this.typeNames, + }); + + factory DeviceOverview.fromJson(Map json) => + _$DeviceOverviewFromJson(json); + + static const toJsonFactory = _$DeviceOverviewToJson; + Map toJson() => _$DeviceOverviewToJson(this); + + @JsonKey(name: 'id') + final int id; + @JsonKey(name: 'friendlyName') + final String friendlyName; + @JsonKey(name: 'typeName') + final String typeName; + @JsonKey(name: 'typeNames', defaultValue: []) + final List typeNames; + static const fromJsonFactory = _$DeviceOverviewFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is DeviceOverview && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id)) && + (identical(other.friendlyName, friendlyName) || + const DeepCollectionEquality().equals( + other.friendlyName, + friendlyName, + )) && + (identical(other.typeName, typeName) || + const DeepCollectionEquality().equals( + other.typeName, + typeName, + )) && + (identical(other.typeNames, typeNames) || + const DeepCollectionEquality().equals( + other.typeNames, + typeNames, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(id) ^ + const DeepCollectionEquality().hash(friendlyName) ^ + const DeepCollectionEquality().hash(typeName) ^ + const DeepCollectionEquality().hash(typeNames) ^ + runtimeType.hashCode; +} + +extension $DeviceOverviewExtension on DeviceOverview { + DeviceOverview copyWith({ + int? id, + String? friendlyName, + String? typeName, + List? typeNames, + }) { + return DeviceOverview( + id: id ?? this.id, + friendlyName: friendlyName ?? this.friendlyName, + typeName: typeName ?? this.typeName, + typeNames: typeNames ?? this.typeNames, + ); + } + + DeviceOverview copyWithWrapped({ + Wrapped? id, + Wrapped? friendlyName, + Wrapped? typeName, + Wrapped>? typeNames, + }) { + return DeviceOverview( + id: (id != null ? id.value : this.id), + friendlyName: (friendlyName != null + ? friendlyName.value + : this.friendlyName), + typeName: (typeName != null ? typeName.value : this.typeName), + typeNames: (typeNames != null ? typeNames.value : this.typeNames), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class DeviceRenameRequest { + const DeviceRenameRequest({required this.id, required this.newName}); + + factory DeviceRenameRequest.fromJson(Map json) => + _$DeviceRenameRequestFromJson(json); + + static const toJsonFactory = _$DeviceRenameRequestToJson; + Map toJson() => _$DeviceRenameRequestToJson(this); + + @JsonKey(name: 'id') + final int id; + @JsonKey(name: 'newName') + final String newName; + static const fromJsonFactory = _$DeviceRenameRequestFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is DeviceRenameRequest && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id)) && + (identical(other.newName, newName) || + const DeepCollectionEquality().equals(other.newName, newName))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(id) ^ + const DeepCollectionEquality().hash(newName) ^ + runtimeType.hashCode; +} + +extension $DeviceRenameRequestExtension on DeviceRenameRequest { + DeviceRenameRequest copyWith({int? id, String? newName}) { + return DeviceRenameRequest( + id: id ?? this.id, + newName: newName ?? this.newName, + ); + } + + DeviceRenameRequest copyWithWrapped({ + Wrapped? id, + Wrapped? newName, + }) { + return DeviceRenameRequest( + id: (id != null ? id.value : this.id), + newName: (newName != null ? newName.value : this.newName), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class EditParameter { + const EditParameter({ + required this.command, + required this.$value, + this.id, + this.messageType, + this.displayName, + this.parameters, + this.extensionData, + }); + + factory EditParameter.fromJson(Map json) => + _$EditParameterFromJson(json); + + static const toJsonFactory = _$EditParameterToJson; + Map toJson() => _$EditParameterToJson(this); + + @JsonKey(name: 'command') + final int command; + @JsonKey(name: 'value') + final dynamic $value; + @JsonKey(name: 'id') + final int? id; + @JsonKey( + name: 'messageType', + toJson: messageTypeNullableToJson, + fromJson: messageTypeNullableFromJson, + ) + final enums.MessageType? messageType; + @JsonKey(name: 'displayName') + final String? displayName; + @JsonKey(name: 'parameters', defaultValue: []) + final List? parameters; + @JsonKey(name: 'extensionData') + final Map? extensionData; + static const fromJsonFactory = _$EditParameterFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is EditParameter && + (identical(other.command, command) || + const DeepCollectionEquality().equals( + other.command, + command, + )) && + (identical(other.$value, $value) || + const DeepCollectionEquality().equals(other.$value, $value)) && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id)) && + (identical(other.messageType, messageType) || + const DeepCollectionEquality().equals( + other.messageType, + messageType, + )) && + (identical(other.displayName, displayName) || + const DeepCollectionEquality().equals( + other.displayName, + displayName, + )) && + (identical(other.parameters, parameters) || + const DeepCollectionEquality().equals( + other.parameters, + parameters, + )) && + (identical(other.extensionData, extensionData) || + const DeepCollectionEquality().equals( + other.extensionData, + extensionData, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(command) ^ + const DeepCollectionEquality().hash($value) ^ + const DeepCollectionEquality().hash(id) ^ + const DeepCollectionEquality().hash(messageType) ^ + const DeepCollectionEquality().hash(displayName) ^ + const DeepCollectionEquality().hash(parameters) ^ + const DeepCollectionEquality().hash(extensionData) ^ + runtimeType.hashCode; +} + +extension $EditParameterExtension on EditParameter { + EditParameter copyWith({ + int? command, + dynamic $value, + int? id, + enums.MessageType? messageType, + String? displayName, + List? parameters, + Map? extensionData, + }) { + return EditParameter( + command: command ?? this.command, + $value: $value ?? this.$value, + id: id ?? this.id, + messageType: messageType ?? this.messageType, + displayName: displayName ?? this.displayName, + parameters: parameters ?? this.parameters, + extensionData: extensionData ?? this.extensionData, + ); + } + + EditParameter copyWithWrapped({ + Wrapped? command, + Wrapped? $value, + Wrapped? id, + Wrapped? messageType, + Wrapped? displayName, + Wrapped?>? parameters, + Wrapped?>? extensionData, + }) { + return EditParameter( + command: (command != null ? command.value : this.command), + $value: ($value != null ? $value.value : this.$value), + id: (id != null ? id.value : this.id), + messageType: (messageType != null ? messageType.value : this.messageType), + displayName: (displayName != null ? displayName.value : this.displayName), + parameters: (parameters != null ? parameters.value : this.parameters), + extensionData: (extensionData != null + ? extensionData.value + : this.extensionData), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class FirebaseOptions { + const FirebaseOptions({ + required this.apiKey, + required this.appId, + required this.messagingSenderId, + required this.projectId, + required this.storageBucket, + this.iosBundleId, + this.authDomain, + }); + + factory FirebaseOptions.fromJson(Map json) => + _$FirebaseOptionsFromJson(json); + + static const toJsonFactory = _$FirebaseOptionsToJson; + Map toJson() => _$FirebaseOptionsToJson(this); + + @JsonKey(name: 'apiKey') + final String apiKey; + @JsonKey(name: 'appId') + final String appId; + @JsonKey(name: 'messagingSenderId') + final String messagingSenderId; + @JsonKey(name: 'projectId') + final String projectId; + @JsonKey(name: 'storageBucket') + final String storageBucket; + @JsonKey(name: 'iosBundleId') + final String? iosBundleId; + @JsonKey(name: 'authDomain') + final String? authDomain; + static const fromJsonFactory = _$FirebaseOptionsFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is FirebaseOptions && + (identical(other.apiKey, apiKey) || + const DeepCollectionEquality().equals(other.apiKey, apiKey)) && + (identical(other.appId, appId) || + const DeepCollectionEquality().equals(other.appId, appId)) && + (identical(other.messagingSenderId, messagingSenderId) || + const DeepCollectionEquality().equals( + other.messagingSenderId, + messagingSenderId, + )) && + (identical(other.projectId, projectId) || + const DeepCollectionEquality().equals( + other.projectId, + projectId, + )) && + (identical(other.storageBucket, storageBucket) || + const DeepCollectionEquality().equals( + other.storageBucket, + storageBucket, + )) && + (identical(other.iosBundleId, iosBundleId) || + const DeepCollectionEquality().equals( + other.iosBundleId, + iosBundleId, + )) && + (identical(other.authDomain, authDomain) || + const DeepCollectionEquality().equals( + other.authDomain, + authDomain, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(apiKey) ^ + const DeepCollectionEquality().hash(appId) ^ + const DeepCollectionEquality().hash(messagingSenderId) ^ + const DeepCollectionEquality().hash(projectId) ^ + const DeepCollectionEquality().hash(storageBucket) ^ + const DeepCollectionEquality().hash(iosBundleId) ^ + const DeepCollectionEquality().hash(authDomain) ^ + runtimeType.hashCode; +} + +extension $FirebaseOptionsExtension on FirebaseOptions { + FirebaseOptions copyWith({ + String? apiKey, + String? appId, + String? messagingSenderId, + String? projectId, + String? storageBucket, + String? iosBundleId, + String? authDomain, + }) { + return FirebaseOptions( + apiKey: apiKey ?? this.apiKey, + appId: appId ?? this.appId, + messagingSenderId: messagingSenderId ?? this.messagingSenderId, + projectId: projectId ?? this.projectId, + storageBucket: storageBucket ?? this.storageBucket, + iosBundleId: iosBundleId ?? this.iosBundleId, + authDomain: authDomain ?? this.authDomain, + ); + } + + FirebaseOptions copyWithWrapped({ + Wrapped? apiKey, + Wrapped? appId, + Wrapped? messagingSenderId, + Wrapped? projectId, + Wrapped? storageBucket, + Wrapped? iosBundleId, + Wrapped? authDomain, + }) { + return FirebaseOptions( + apiKey: (apiKey != null ? apiKey.value : this.apiKey), + appId: (appId != null ? appId.value : this.appId), + messagingSenderId: (messagingSenderId != null + ? messagingSenderId.value + : this.messagingSenderId), + projectId: (projectId != null ? projectId.value : this.projectId), + storageBucket: (storageBucket != null + ? storageBucket.value + : this.storageBucket), + iosBundleId: (iosBundleId != null ? iosBundleId.value : this.iosBundleId), + authDomain: (authDomain != null ? authDomain.value : this.authDomain), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class History { + const History({required this.historyRecords, required this.propertyName}); + + factory History.fromJson(Map json) => + _$HistoryFromJson(json); + + static const toJsonFactory = _$HistoryToJson; + Map toJson() => _$HistoryToJson(this); + + @JsonKey(name: 'historyRecords', defaultValue: []) + final List historyRecords; + @JsonKey(name: 'propertyName') + final String propertyName; + static const fromJsonFactory = _$HistoryFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is History && + (identical(other.historyRecords, historyRecords) || + const DeepCollectionEquality().equals( + other.historyRecords, + historyRecords, + )) && + (identical(other.propertyName, propertyName) || + const DeepCollectionEquality().equals( + other.propertyName, + propertyName, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(historyRecords) ^ + const DeepCollectionEquality().hash(propertyName) ^ + runtimeType.hashCode; +} + +extension $HistoryExtension on History { + History copyWith({ + List? historyRecords, + String? propertyName, + }) { + return History( + historyRecords: historyRecords ?? this.historyRecords, + propertyName: propertyName ?? this.propertyName, + ); + } + + History copyWithWrapped({ + Wrapped>? historyRecords, + Wrapped? propertyName, + }) { + return History( + historyRecords: (historyRecords != null + ? historyRecords.value + : this.historyRecords), + propertyName: (propertyName != null + ? propertyName.value + : this.propertyName), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class HistoryPropertyInfo { + const HistoryPropertyInfo({ + required this.propertyName, + required this.xAxisName, + required this.unitOfMeasurement, + required this.iconName, + required this.brightThemeColor, + required this.darkThemeColor, + required this.chartType, + }); + + factory HistoryPropertyInfo.fromJson(Map json) => + _$HistoryPropertyInfoFromJson(json); + + static const toJsonFactory = _$HistoryPropertyInfoToJson; + Map toJson() => _$HistoryPropertyInfoToJson(this); + + @JsonKey(name: 'propertyName') + final String propertyName; + @JsonKey(name: 'xAxisName') + final String xAxisName; + @JsonKey(name: 'unitOfMeasurement') + final String unitOfMeasurement; + @JsonKey(name: 'iconName') + final String iconName; + @JsonKey(name: 'brightThemeColor') + final int brightThemeColor; + @JsonKey(name: 'darkThemeColor') + final int darkThemeColor; + @JsonKey(name: 'chartType') + final String chartType; + static const fromJsonFactory = _$HistoryPropertyInfoFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is HistoryPropertyInfo && + (identical(other.propertyName, propertyName) || + const DeepCollectionEquality().equals( + other.propertyName, + propertyName, + )) && + (identical(other.xAxisName, xAxisName) || + const DeepCollectionEquality().equals( + other.xAxisName, + xAxisName, + )) && + (identical(other.unitOfMeasurement, unitOfMeasurement) || + const DeepCollectionEquality().equals( + other.unitOfMeasurement, + unitOfMeasurement, + )) && + (identical(other.iconName, iconName) || + const DeepCollectionEquality().equals( + other.iconName, + iconName, + )) && + (identical(other.brightThemeColor, brightThemeColor) || + const DeepCollectionEquality().equals( + other.brightThemeColor, + brightThemeColor, + )) && + (identical(other.darkThemeColor, darkThemeColor) || + const DeepCollectionEquality().equals( + other.darkThemeColor, + darkThemeColor, + )) && + (identical(other.chartType, chartType) || + const DeepCollectionEquality().equals( + other.chartType, + chartType, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(propertyName) ^ + const DeepCollectionEquality().hash(xAxisName) ^ + const DeepCollectionEquality().hash(unitOfMeasurement) ^ + const DeepCollectionEquality().hash(iconName) ^ + const DeepCollectionEquality().hash(brightThemeColor) ^ + const DeepCollectionEquality().hash(darkThemeColor) ^ + const DeepCollectionEquality().hash(chartType) ^ + runtimeType.hashCode; +} + +extension $HistoryPropertyInfoExtension on HistoryPropertyInfo { + HistoryPropertyInfo copyWith({ + String? propertyName, + String? xAxisName, + String? unitOfMeasurement, + String? iconName, + int? brightThemeColor, + int? darkThemeColor, + String? chartType, + }) { + return HistoryPropertyInfo( + propertyName: propertyName ?? this.propertyName, + xAxisName: xAxisName ?? this.xAxisName, + unitOfMeasurement: unitOfMeasurement ?? this.unitOfMeasurement, + iconName: iconName ?? this.iconName, + brightThemeColor: brightThemeColor ?? this.brightThemeColor, + darkThemeColor: darkThemeColor ?? this.darkThemeColor, + chartType: chartType ?? this.chartType, + ); + } + + HistoryPropertyInfo copyWithWrapped({ + Wrapped? propertyName, + Wrapped? xAxisName, + Wrapped? unitOfMeasurement, + Wrapped? iconName, + Wrapped? brightThemeColor, + Wrapped? darkThemeColor, + Wrapped? chartType, + }) { + return HistoryPropertyInfo( + propertyName: (propertyName != null + ? propertyName.value + : this.propertyName), + xAxisName: (xAxisName != null ? xAxisName.value : this.xAxisName), + unitOfMeasurement: (unitOfMeasurement != null + ? unitOfMeasurement.value + : this.unitOfMeasurement), + iconName: (iconName != null ? iconName.value : this.iconName), + brightThemeColor: (brightThemeColor != null + ? brightThemeColor.value + : this.brightThemeColor), + darkThemeColor: (darkThemeColor != null + ? darkThemeColor.value + : this.darkThemeColor), + chartType: (chartType != null ? chartType.value : this.chartType), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class HistoryPropertyState { + const HistoryPropertyState({ + required this.deviceId, + required this.propertyName, + required this.enabled, + }); + + factory HistoryPropertyState.fromJson(Map json) => + _$HistoryPropertyStateFromJson(json); + + static const toJsonFactory = _$HistoryPropertyStateToJson; + Map toJson() => _$HistoryPropertyStateToJson(this); + + @JsonKey(name: 'deviceId') + final int deviceId; + @JsonKey(name: 'propertyName') + final String propertyName; + @JsonKey(name: 'enabled') + final bool enabled; + static const fromJsonFactory = _$HistoryPropertyStateFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is HistoryPropertyState && + (identical(other.deviceId, deviceId) || + const DeepCollectionEquality().equals( + other.deviceId, + deviceId, + )) && + (identical(other.propertyName, propertyName) || + const DeepCollectionEquality().equals( + other.propertyName, + propertyName, + )) && + (identical(other.enabled, enabled) || + const DeepCollectionEquality().equals(other.enabled, enabled))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(deviceId) ^ + const DeepCollectionEquality().hash(propertyName) ^ + const DeepCollectionEquality().hash(enabled) ^ + runtimeType.hashCode; +} + +extension $HistoryPropertyStateExtension on HistoryPropertyState { + HistoryPropertyState copyWith({ + int? deviceId, + String? propertyName, + bool? enabled, + }) { + return HistoryPropertyState( + deviceId: deviceId ?? this.deviceId, + propertyName: propertyName ?? this.propertyName, + enabled: enabled ?? this.enabled, + ); + } + + HistoryPropertyState copyWithWrapped({ + Wrapped? deviceId, + Wrapped? propertyName, + Wrapped? enabled, + }) { + return HistoryPropertyState( + deviceId: (deviceId != null ? deviceId.value : this.deviceId), + propertyName: (propertyName != null + ? propertyName.value + : this.propertyName), + enabled: (enabled != null ? enabled.value : this.enabled), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class HistoryRecord { + const HistoryRecord({this.val, required this.ts}); + + factory HistoryRecord.fromJson(Map json) => + _$HistoryRecordFromJson(json); + + static const toJsonFactory = _$HistoryRecordToJson; + Map toJson() => _$HistoryRecordToJson(this); + + @JsonKey(name: 'val') + final double? val; + @JsonKey(name: 'ts') + final int ts; + static const fromJsonFactory = _$HistoryRecordFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is HistoryRecord && + (identical(other.val, val) || + const DeepCollectionEquality().equals(other.val, val)) && + (identical(other.ts, ts) || + const DeepCollectionEquality().equals(other.ts, ts))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(val) ^ + const DeepCollectionEquality().hash(ts) ^ + runtimeType.hashCode; +} + +extension $HistoryRecordExtension on HistoryRecord { + HistoryRecord copyWith({double? val, int? ts}) { + return HistoryRecord(val: val ?? this.val, ts: ts ?? this.ts); + } + + HistoryRecord copyWithWrapped({Wrapped? val, Wrapped? ts}) { + return HistoryRecord( + val: (val != null ? val.value : this.val), + ts: (ts != null ? ts.value : this.ts), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class IconResponse { + const IconResponse({required this.icon, required this.name}); + + factory IconResponse.fromJson(Map json) => + _$IconResponseFromJson(json); + + static const toJsonFactory = _$IconResponseToJson; + Map toJson() => _$IconResponseToJson(this); + + @JsonKey(name: 'icon') + final SvgIcon icon; + @JsonKey(name: 'name') + final String name; + static const fromJsonFactory = _$IconResponseFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is IconResponse && + (identical(other.icon, icon) || + const DeepCollectionEquality().equals(other.icon, icon)) && + (identical(other.name, name) || + const DeepCollectionEquality().equals(other.name, name))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(icon) ^ + const DeepCollectionEquality().hash(name) ^ + runtimeType.hashCode; +} + +extension $IconResponseExtension on IconResponse { + IconResponse copyWith({SvgIcon? icon, String? name}) { + return IconResponse(icon: icon ?? this.icon, name: name ?? this.name); + } + + IconResponse copyWithWrapped({ + Wrapped? icon, + Wrapped? name, + }) { + return IconResponse( + icon: (icon != null ? icon.value : this.icon), + name: (name != null ? name.value : this.name), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class JsonApiSmarthomeMessage { + const JsonApiSmarthomeMessage({ + required this.parameters, + required this.id, + required this.messageType, + required this.command, + }); + + factory JsonApiSmarthomeMessage.fromJson(Map json) => + _$JsonApiSmarthomeMessageFromJson(json); + + static const toJsonFactory = _$JsonApiSmarthomeMessageToJson; + Map toJson() => _$JsonApiSmarthomeMessageToJson(this); + + @JsonKey(name: 'parameters', defaultValue: []) + final List parameters; + @JsonKey(name: 'id') + final int id; + @JsonKey( + name: 'messageType', + toJson: messageTypeToJson, + fromJson: messageTypeFromJson, + ) + final enums.MessageType messageType; + @JsonKey(name: 'command') + final int command; + static const fromJsonFactory = _$JsonApiSmarthomeMessageFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is JsonApiSmarthomeMessage && + (identical(other.parameters, parameters) || + const DeepCollectionEquality().equals( + other.parameters, + parameters, + )) && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id)) && + (identical(other.messageType, messageType) || + const DeepCollectionEquality().equals( + other.messageType, + messageType, + )) && + (identical(other.command, command) || + const DeepCollectionEquality().equals(other.command, command))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(parameters) ^ + const DeepCollectionEquality().hash(id) ^ + const DeepCollectionEquality().hash(messageType) ^ + const DeepCollectionEquality().hash(command) ^ + runtimeType.hashCode; +} + +extension $JsonApiSmarthomeMessageExtension on JsonApiSmarthomeMessage { + JsonApiSmarthomeMessage copyWith({ + List? parameters, + int? id, + enums.MessageType? messageType, + int? command, + }) { + return JsonApiSmarthomeMessage( + parameters: parameters ?? this.parameters, + id: id ?? this.id, + messageType: messageType ?? this.messageType, + command: command ?? this.command, + ); + } + + JsonApiSmarthomeMessage copyWithWrapped({ + Wrapped>? parameters, + Wrapped? id, + Wrapped? messageType, + Wrapped? command, + }) { + return JsonApiSmarthomeMessage( + parameters: (parameters != null ? parameters.value : this.parameters), + id: (id != null ? id.value : this.id), + messageType: (messageType != null ? messageType.value : this.messageType), + command: (command != null ? command.value : this.command), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class LayoutRequest { + const LayoutRequest({ + required this.typeName, + required this.iconName, + required this.deviceId, + }); + + factory LayoutRequest.fromJson(Map json) => + _$LayoutRequestFromJson(json); + + static const toJsonFactory = _$LayoutRequestToJson; + Map toJson() => _$LayoutRequestToJson(this); + + @JsonKey(name: 'typeName') + final String typeName; + @JsonKey(name: 'iconName') + final String iconName; + @JsonKey(name: 'deviceId') + final int deviceId; + static const fromJsonFactory = _$LayoutRequestFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is LayoutRequest && + (identical(other.typeName, typeName) || + const DeepCollectionEquality().equals( + other.typeName, + typeName, + )) && + (identical(other.iconName, iconName) || + const DeepCollectionEquality().equals( + other.iconName, + iconName, + )) && + (identical(other.deviceId, deviceId) || + const DeepCollectionEquality().equals( + other.deviceId, + deviceId, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(typeName) ^ + const DeepCollectionEquality().hash(iconName) ^ + const DeepCollectionEquality().hash(deviceId) ^ + runtimeType.hashCode; +} + +extension $LayoutRequestExtension on LayoutRequest { + LayoutRequest copyWith({String? typeName, String? iconName, int? deviceId}) { + return LayoutRequest( + typeName: typeName ?? this.typeName, + iconName: iconName ?? this.iconName, + deviceId: deviceId ?? this.deviceId, + ); + } + + LayoutRequest copyWithWrapped({ + Wrapped? typeName, + Wrapped? iconName, + Wrapped? deviceId, + }) { + return LayoutRequest( + typeName: (typeName != null ? typeName.value : this.typeName), + iconName: (iconName != null ? iconName.value : this.iconName), + deviceId: (deviceId != null ? deviceId.value : this.deviceId), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class LayoutResponse { + const LayoutResponse({this.layout, this.icon, required this.additionalIcons}); + + factory LayoutResponse.fromJson(Map json) => + _$LayoutResponseFromJson(json); + + static const toJsonFactory = _$LayoutResponseToJson; + Map toJson() => _$LayoutResponseToJson(this); + + @JsonKey(name: 'layout') + final DeviceLayout? layout; + @JsonKey(name: 'icon') + final IconResponse? icon; + @JsonKey(name: 'additionalIcons', defaultValue: []) + final List additionalIcons; + static const fromJsonFactory = _$LayoutResponseFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is LayoutResponse && + (identical(other.layout, layout) || + const DeepCollectionEquality().equals(other.layout, layout)) && + (identical(other.icon, icon) || + const DeepCollectionEquality().equals(other.icon, icon)) && + (identical(other.additionalIcons, additionalIcons) || + const DeepCollectionEquality().equals( + other.additionalIcons, + additionalIcons, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(layout) ^ + const DeepCollectionEquality().hash(icon) ^ + const DeepCollectionEquality().hash(additionalIcons) ^ + runtimeType.hashCode; +} + +extension $LayoutResponseExtension on LayoutResponse { + LayoutResponse copyWith({ + DeviceLayout? layout, + IconResponse? icon, + List? additionalIcons, + }) { + return LayoutResponse( + layout: layout ?? this.layout, + icon: icon ?? this.icon, + additionalIcons: additionalIcons ?? this.additionalIcons, + ); + } + + LayoutResponse copyWithWrapped({ + Wrapped? layout, + Wrapped? icon, + Wrapped>? additionalIcons, + }) { + return LayoutResponse( + layout: (layout != null ? layout.value : this.layout), + icon: (icon != null ? icon.value : this.icon), + additionalIcons: (additionalIcons != null + ? additionalIcons.value + : this.additionalIcons), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class LinkedDeviceTab { + const LinkedDeviceTab({ + required this.deviceIdPropertyName, + required this.deviceType, + }); + + factory LinkedDeviceTab.fromJson(Map json) => + _$LinkedDeviceTabFromJson(json); + + static const toJsonFactory = _$LinkedDeviceTabToJson; + Map toJson() => _$LinkedDeviceTabToJson(this); + + @JsonKey(name: 'deviceIdPropertyName') + final String deviceIdPropertyName; + @JsonKey(name: 'deviceType') + final String deviceType; + static const fromJsonFactory = _$LinkedDeviceTabFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is LinkedDeviceTab && + (identical(other.deviceIdPropertyName, deviceIdPropertyName) || + const DeepCollectionEquality().equals( + other.deviceIdPropertyName, + deviceIdPropertyName, + )) && + (identical(other.deviceType, deviceType) || + const DeepCollectionEquality().equals( + other.deviceType, + deviceType, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(deviceIdPropertyName) ^ + const DeepCollectionEquality().hash(deviceType) ^ + runtimeType.hashCode; +} + +extension $LinkedDeviceTabExtension on LinkedDeviceTab { + LinkedDeviceTab copyWith({String? deviceIdPropertyName, String? deviceType}) { + return LinkedDeviceTab( + deviceIdPropertyName: deviceIdPropertyName ?? this.deviceIdPropertyName, + deviceType: deviceType ?? this.deviceType, + ); + } + + LinkedDeviceTab copyWithWrapped({ + Wrapped? deviceIdPropertyName, + Wrapped? deviceType, + }) { + return LinkedDeviceTab( + deviceIdPropertyName: (deviceIdPropertyName != null + ? deviceIdPropertyName.value + : this.deviceIdPropertyName), + deviceType: (deviceType != null ? deviceType.value : this.deviceType), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class NotificationSetup { + const NotificationSetup({ + required this.uniqueName, + required this.translatableName, + required this.times, + this.deviceIds, + required this.global, + }); + + factory NotificationSetup.fromJson(Map json) => + _$NotificationSetupFromJson(json); + + static const toJsonFactory = _$NotificationSetupToJson; + Map toJson() => _$NotificationSetupToJson(this); + + @JsonKey(name: 'uniqueName') + final String uniqueName; + @JsonKey(name: 'translatableName') + final String translatableName; + @JsonKey(name: 'times') + final int times; + @JsonKey(name: 'deviceIds', defaultValue: []) + final List? deviceIds; + @JsonKey(name: 'global') + final bool global; + static const fromJsonFactory = _$NotificationSetupFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is NotificationSetup && + (identical(other.uniqueName, uniqueName) || + const DeepCollectionEquality().equals( + other.uniqueName, + uniqueName, + )) && + (identical(other.translatableName, translatableName) || + const DeepCollectionEquality().equals( + other.translatableName, + translatableName, + )) && + (identical(other.times, times) || + const DeepCollectionEquality().equals(other.times, times)) && + (identical(other.deviceIds, deviceIds) || + const DeepCollectionEquality().equals( + other.deviceIds, + deviceIds, + )) && + (identical(other.global, global) || + const DeepCollectionEquality().equals(other.global, global))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(uniqueName) ^ + const DeepCollectionEquality().hash(translatableName) ^ + const DeepCollectionEquality().hash(times) ^ + const DeepCollectionEquality().hash(deviceIds) ^ + const DeepCollectionEquality().hash(global) ^ + runtimeType.hashCode; +} + +extension $NotificationSetupExtension on NotificationSetup { + NotificationSetup copyWith({ + String? uniqueName, + String? translatableName, + int? times, + List? deviceIds, + bool? global, + }) { + return NotificationSetup( + uniqueName: uniqueName ?? this.uniqueName, + translatableName: translatableName ?? this.translatableName, + times: times ?? this.times, + deviceIds: deviceIds ?? this.deviceIds, + global: global ?? this.global, + ); + } + + NotificationSetup copyWithWrapped({ + Wrapped? uniqueName, + Wrapped? translatableName, + Wrapped? times, + Wrapped?>? deviceIds, + Wrapped? global, + }) { + return NotificationSetup( + uniqueName: (uniqueName != null ? uniqueName.value : this.uniqueName), + translatableName: (translatableName != null + ? translatableName.value + : this.translatableName), + times: (times != null ? times.value : this.times), + deviceIds: (deviceIds != null ? deviceIds.value : this.deviceIds), + global: (global != null ? global.value : this.global), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class PropertyEditInformation { + const PropertyEditInformation({ + required this.messageType, + required this.editParameter, + required this.editType, + this.display, + this.hubMethod, + this.valueName, + this.activeValue, + this.dialog, + this.extensionData, + }); + + factory PropertyEditInformation.fromJson(Map json) => + _$PropertyEditInformationFromJson(json); + + static const toJsonFactory = _$PropertyEditInformationToJson; + Map toJson() => _$PropertyEditInformationToJson(this); + + @JsonKey( + name: 'messageType', + toJson: messageTypeToJson, + fromJson: messageTypeFromJson, + ) + final enums.MessageType messageType; + @JsonKey(name: 'editParameter', defaultValue: []) + final List editParameter; + @JsonKey(name: 'editType') + final String editType; + @JsonKey(name: 'display') + final String? display; + @JsonKey(name: 'hubMethod') + final String? hubMethod; + @JsonKey(name: 'valueName') + final String? valueName; + @JsonKey(name: 'activeValue') + final dynamic activeValue; + @JsonKey(name: 'dialog') + final String? dialog; + @JsonKey(name: 'extensionData') + final Map? extensionData; + static const fromJsonFactory = _$PropertyEditInformationFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is PropertyEditInformation && + (identical(other.messageType, messageType) || + const DeepCollectionEquality().equals( + other.messageType, + messageType, + )) && + (identical(other.editParameter, editParameter) || + const DeepCollectionEquality().equals( + other.editParameter, + editParameter, + )) && + (identical(other.editType, editType) || + const DeepCollectionEquality().equals( + other.editType, + editType, + )) && + (identical(other.display, display) || + const DeepCollectionEquality().equals( + other.display, + display, + )) && + (identical(other.hubMethod, hubMethod) || + const DeepCollectionEquality().equals( + other.hubMethod, + hubMethod, + )) && + (identical(other.valueName, valueName) || + const DeepCollectionEquality().equals( + other.valueName, + valueName, + )) && + (identical(other.activeValue, activeValue) || + const DeepCollectionEquality().equals( + other.activeValue, + activeValue, + )) && + (identical(other.dialog, dialog) || + const DeepCollectionEquality().equals(other.dialog, dialog)) && + (identical(other.extensionData, extensionData) || + const DeepCollectionEquality().equals( + other.extensionData, + extensionData, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(messageType) ^ + const DeepCollectionEquality().hash(editParameter) ^ + const DeepCollectionEquality().hash(editType) ^ + const DeepCollectionEquality().hash(display) ^ + const DeepCollectionEquality().hash(hubMethod) ^ + const DeepCollectionEquality().hash(valueName) ^ + const DeepCollectionEquality().hash(activeValue) ^ + const DeepCollectionEquality().hash(dialog) ^ + const DeepCollectionEquality().hash(extensionData) ^ + runtimeType.hashCode; +} + +extension $PropertyEditInformationExtension on PropertyEditInformation { + PropertyEditInformation copyWith({ + enums.MessageType? messageType, + List? editParameter, + String? editType, + String? display, + String? hubMethod, + String? valueName, + dynamic activeValue, + String? dialog, + Map? extensionData, + }) { + return PropertyEditInformation( + messageType: messageType ?? this.messageType, + editParameter: editParameter ?? this.editParameter, + editType: editType ?? this.editType, + display: display ?? this.display, + hubMethod: hubMethod ?? this.hubMethod, + valueName: valueName ?? this.valueName, + activeValue: activeValue ?? this.activeValue, + dialog: dialog ?? this.dialog, + extensionData: extensionData ?? this.extensionData, + ); + } + + PropertyEditInformation copyWithWrapped({ + Wrapped? messageType, + Wrapped>? editParameter, + Wrapped? editType, + Wrapped? display, + Wrapped? hubMethod, + Wrapped? valueName, + Wrapped? activeValue, + Wrapped? dialog, + Wrapped?>? extensionData, + }) { + return PropertyEditInformation( + messageType: (messageType != null ? messageType.value : this.messageType), + editParameter: (editParameter != null + ? editParameter.value + : this.editParameter), + editType: (editType != null ? editType.value : this.editType), + display: (display != null ? display.value : this.display), + hubMethod: (hubMethod != null ? hubMethod.value : this.hubMethod), + valueName: (valueName != null ? valueName.value : this.valueName), + activeValue: (activeValue != null ? activeValue.value : this.activeValue), + dialog: (dialog != null ? dialog.value : this.dialog), + extensionData: (extensionData != null + ? extensionData.value + : this.extensionData), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class RestCreatedDevice { + const RestCreatedDevice({ + this.typeNames, + required this.id, + required this.typeName, + required this.friendlyName, + required this.startAutomatically, + }); + + factory RestCreatedDevice.fromJson(Map json) => + _$RestCreatedDeviceFromJson(json); + + static const toJsonFactory = _$RestCreatedDeviceToJson; + Map toJson() => _$RestCreatedDeviceToJson(this); + + @JsonKey(name: 'typeNames', defaultValue: []) + final List? typeNames; + @JsonKey(name: 'id') + final int id; + @JsonKey(name: 'typeName') + final String typeName; + @JsonKey(name: 'friendlyName') + final String friendlyName; + @JsonKey(name: 'startAutomatically') + final bool startAutomatically; + static const fromJsonFactory = _$RestCreatedDeviceFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is RestCreatedDevice && + (identical(other.typeNames, typeNames) || + const DeepCollectionEquality().equals( + other.typeNames, + typeNames, + )) && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id)) && + (identical(other.typeName, typeName) || + const DeepCollectionEquality().equals( + other.typeName, + typeName, + )) && + (identical(other.friendlyName, friendlyName) || + const DeepCollectionEquality().equals( + other.friendlyName, + friendlyName, + )) && + (identical(other.startAutomatically, startAutomatically) || + const DeepCollectionEquality().equals( + other.startAutomatically, + startAutomatically, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(typeNames) ^ + const DeepCollectionEquality().hash(id) ^ + const DeepCollectionEquality().hash(typeName) ^ + const DeepCollectionEquality().hash(friendlyName) ^ + const DeepCollectionEquality().hash(startAutomatically) ^ + runtimeType.hashCode; +} + +extension $RestCreatedDeviceExtension on RestCreatedDevice { + RestCreatedDevice copyWith({ + List? typeNames, + int? id, + String? typeName, + String? friendlyName, + bool? startAutomatically, + }) { + return RestCreatedDevice( + typeNames: typeNames ?? this.typeNames, + id: id ?? this.id, + typeName: typeName ?? this.typeName, + friendlyName: friendlyName ?? this.friendlyName, + startAutomatically: startAutomatically ?? this.startAutomatically, + ); + } + + RestCreatedDevice copyWithWrapped({ + Wrapped?>? typeNames, + Wrapped? id, + Wrapped? typeName, + Wrapped? friendlyName, + Wrapped? startAutomatically, + }) { + return RestCreatedDevice( + typeNames: (typeNames != null ? typeNames.value : this.typeNames), + id: (id != null ? id.value : this.id), + typeName: (typeName != null ? typeName.value : this.typeName), + friendlyName: (friendlyName != null + ? friendlyName.value + : this.friendlyName), + startAutomatically: (startAutomatically != null + ? startAutomatically.value + : this.startAutomatically), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class SetHistoryRequest { + const SetHistoryRequest({ + required this.enable, + required this.ids, + required this.name, + }); + + factory SetHistoryRequest.fromJson(Map json) => + _$SetHistoryRequestFromJson(json); + + static const toJsonFactory = _$SetHistoryRequestToJson; + Map toJson() => _$SetHistoryRequestToJson(this); + + @JsonKey(name: 'enable') + final bool enable; + @JsonKey(name: 'ids', defaultValue: []) + final List ids; + @JsonKey(name: 'name') + final String name; + static const fromJsonFactory = _$SetHistoryRequestFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is SetHistoryRequest && + (identical(other.enable, enable) || + const DeepCollectionEquality().equals(other.enable, enable)) && + (identical(other.ids, ids) || + const DeepCollectionEquality().equals(other.ids, ids)) && + (identical(other.name, name) || + const DeepCollectionEquality().equals(other.name, name))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(enable) ^ + const DeepCollectionEquality().hash(ids) ^ + const DeepCollectionEquality().hash(name) ^ + runtimeType.hashCode; +} + +extension $SetHistoryRequestExtension on SetHistoryRequest { + SetHistoryRequest copyWith({bool? enable, List? ids, String? name}) { + return SetHistoryRequest( + enable: enable ?? this.enable, + ids: ids ?? this.ids, + name: name ?? this.name, + ); + } + + SetHistoryRequest copyWithWrapped({ + Wrapped? enable, + Wrapped>? ids, + Wrapped? name, + }) { + return SetHistoryRequest( + enable: (enable != null ? enable.value : this.enable), + ids: (ids != null ? ids.value : this.ids), + name: (name != null ? name.value : this.name), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class TextSettings { + const TextSettings({ + this.fontSize, + this.fontFamily, + required this.fontWeight, + required this.fontStyle, + }); + + factory TextSettings.fromJson(Map json) => + _$TextSettingsFromJson(json); + + static const toJsonFactory = _$TextSettingsToJson; + Map toJson() => _$TextSettingsToJson(this); + + @JsonKey(name: 'fontSize') + final double? fontSize; + @JsonKey(name: 'fontFamily') + final String? fontFamily; + @JsonKey( + name: 'fontWeight', + toJson: fontWeightSettingToJson, + fromJson: fontWeightSettingFromJson, + ) + final enums.FontWeightSetting fontWeight; + @JsonKey( + name: 'fontStyle', + toJson: fontStyleSettingToJson, + fromJson: fontStyleSettingFromJson, + ) + final enums.FontStyleSetting fontStyle; + static const fromJsonFactory = _$TextSettingsFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is TextSettings && + (identical(other.fontSize, fontSize) || + const DeepCollectionEquality().equals( + other.fontSize, + fontSize, + )) && + (identical(other.fontFamily, fontFamily) || + const DeepCollectionEquality().equals( + other.fontFamily, + fontFamily, + )) && + (identical(other.fontWeight, fontWeight) || + const DeepCollectionEquality().equals( + other.fontWeight, + fontWeight, + )) && + (identical(other.fontStyle, fontStyle) || + const DeepCollectionEquality().equals( + other.fontStyle, + fontStyle, + ))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(fontSize) ^ + const DeepCollectionEquality().hash(fontFamily) ^ + const DeepCollectionEquality().hash(fontWeight) ^ + const DeepCollectionEquality().hash(fontStyle) ^ + runtimeType.hashCode; +} + +extension $TextSettingsExtension on TextSettings { + TextSettings copyWith({ + double? fontSize, + String? fontFamily, + enums.FontWeightSetting? fontWeight, + enums.FontStyleSetting? fontStyle, + }) { + return TextSettings( + fontSize: fontSize ?? this.fontSize, + fontFamily: fontFamily ?? this.fontFamily, + fontWeight: fontWeight ?? this.fontWeight, + fontStyle: fontStyle ?? this.fontStyle, + ); + } + + TextSettings copyWithWrapped({ + Wrapped? fontSize, + Wrapped? fontFamily, + Wrapped? fontWeight, + Wrapped? fontStyle, + }) { + return TextSettings( + fontSize: (fontSize != null ? fontSize.value : this.fontSize), + fontFamily: (fontFamily != null ? fontFamily.value : this.fontFamily), + fontWeight: (fontWeight != null ? fontWeight.value : this.fontWeight), + fontStyle: (fontStyle != null ? fontStyle.value : this.fontStyle), + ); + } +} + +@JsonSerializable(explicitToJson: true) +class WindmillSmarthomeMessage { + const WindmillSmarthomeMessage({ + required this.parameters, + required this.id, + required this.idHex, + required this.messageType, + required this.command, + }); + + factory WindmillSmarthomeMessage.fromJson(Map json) => + _$WindmillSmarthomeMessageFromJson(json); + + static const toJsonFactory = _$WindmillSmarthomeMessageToJson; + Map toJson() => _$WindmillSmarthomeMessageToJson(this); + + @JsonKey(name: 'parameters', defaultValue: []) + final List parameters; + @JsonKey(name: 'id') + final int id; + @JsonKey(name: 'idHex') + final String idHex; + @JsonKey( + name: 'messageType', + toJson: messageTypeToJson, + fromJson: messageTypeFromJson, + ) + final enums.MessageType messageType; + @JsonKey(name: 'command') + final int command; + static const fromJsonFactory = _$WindmillSmarthomeMessageFromJson; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other is WindmillSmarthomeMessage && + (identical(other.parameters, parameters) || + const DeepCollectionEquality().equals( + other.parameters, + parameters, + )) && + (identical(other.id, id) || + const DeepCollectionEquality().equals(other.id, id)) && + (identical(other.idHex, idHex) || + const DeepCollectionEquality().equals(other.idHex, idHex)) && + (identical(other.messageType, messageType) || + const DeepCollectionEquality().equals( + other.messageType, + messageType, + )) && + (identical(other.command, command) || + const DeepCollectionEquality().equals(other.command, command))); + } + + @override + String toString() => jsonEncode(this); + + @override + int get hashCode => + const DeepCollectionEquality().hash(parameters) ^ + const DeepCollectionEquality().hash(id) ^ + const DeepCollectionEquality().hash(idHex) ^ + const DeepCollectionEquality().hash(messageType) ^ + const DeepCollectionEquality().hash(command) ^ + runtimeType.hashCode; +} + +extension $WindmillSmarthomeMessageExtension on WindmillSmarthomeMessage { + WindmillSmarthomeMessage copyWith({ + List? parameters, + int? id, + String? idHex, + enums.MessageType? messageType, + int? command, + }) { + return WindmillSmarthomeMessage( + parameters: parameters ?? this.parameters, + id: id ?? this.id, + idHex: idHex ?? this.idHex, + messageType: messageType ?? this.messageType, + command: command ?? this.command, + ); + } + + WindmillSmarthomeMessage copyWithWrapped({ + Wrapped>? parameters, + Wrapped? id, + Wrapped? idHex, + Wrapped? messageType, + Wrapped? command, + }) { + return WindmillSmarthomeMessage( + parameters: (parameters != null ? parameters.value : this.parameters), + id: (id != null ? id.value : this.id), + idHex: (idHex != null ? idHex.value : this.idHex), + messageType: (messageType != null ? messageType.value : this.messageType), + command: (command != null ? command.value : this.command), + ); + } +} + +int? appLogLevelNullableToJson(enums.AppLogLevel? appLogLevel) { + return appLogLevel?.value; +} + +int? appLogLevelToJson(enums.AppLogLevel appLogLevel) { + return appLogLevel.value; +} + +enums.AppLogLevel appLogLevelFromJson( + Object? appLogLevel, [ + enums.AppLogLevel? defaultValue, +]) { + return enums.AppLogLevel.values.firstWhereOrNull( + (e) => e.value == appLogLevel, + ) ?? + defaultValue ?? + enums.AppLogLevel.swaggerGeneratedUnknown; +} + +enums.AppLogLevel? appLogLevelNullableFromJson( + Object? appLogLevel, [ + enums.AppLogLevel? defaultValue, +]) { + if (appLogLevel == null) { + return null; + } + return enums.AppLogLevel.values.firstWhereOrNull( + (e) => e.value == appLogLevel, + ) ?? + defaultValue; +} + +String appLogLevelExplodedListToJson(List? appLogLevel) { + return appLogLevel?.map((e) => e.value!).join(',') ?? ''; +} + +List appLogLevelListToJson(List? appLogLevel) { + if (appLogLevel == null) { + return []; + } + + return appLogLevel.map((e) => e.value!).toList(); +} + +List appLogLevelListFromJson( + List? appLogLevel, [ + List? defaultValue, +]) { + if (appLogLevel == null) { + return defaultValue ?? []; + } + + return appLogLevel.map((e) => appLogLevelFromJson(e.toString())).toList(); +} + +List? appLogLevelNullableListFromJson( + List? appLogLevel, [ + List? defaultValue, +]) { + if (appLogLevel == null) { + return defaultValue; + } + + return appLogLevel.map((e) => appLogLevelFromJson(e.toString())).toList(); +} + +String? dasboardSpecialTypeNullableToJson( + enums.DasboardSpecialType? dasboardSpecialType, +) { + return dasboardSpecialType?.value; +} + +String? dasboardSpecialTypeToJson( + enums.DasboardSpecialType dasboardSpecialType, +) { + return dasboardSpecialType.value; +} + +enums.DasboardSpecialType dasboardSpecialTypeFromJson( + Object? dasboardSpecialType, [ + enums.DasboardSpecialType? defaultValue, +]) { + return enums.DasboardSpecialType.values.firstWhereOrNull( + (e) => e.value == dasboardSpecialType, + ) ?? + defaultValue ?? + enums.DasboardSpecialType.swaggerGeneratedUnknown; +} + +enums.DasboardSpecialType? dasboardSpecialTypeNullableFromJson( + Object? dasboardSpecialType, [ + enums.DasboardSpecialType? defaultValue, +]) { + if (dasboardSpecialType == null) { + return null; + } + return enums.DasboardSpecialType.values.firstWhereOrNull( + (e) => e.value == dasboardSpecialType, + ) ?? + defaultValue; +} + +String dasboardSpecialTypeExplodedListToJson( + List? dasboardSpecialType, +) { + return dasboardSpecialType?.map((e) => e.value!).join(',') ?? ''; +} + +List dasboardSpecialTypeListToJson( + List? dasboardSpecialType, +) { + if (dasboardSpecialType == null) { + return []; + } + + return dasboardSpecialType.map((e) => e.value!).toList(); +} + +List dasboardSpecialTypeListFromJson( + List? dasboardSpecialType, [ + List? defaultValue, +]) { + if (dasboardSpecialType == null) { + return defaultValue ?? []; + } + + return dasboardSpecialType + .map((e) => dasboardSpecialTypeFromJson(e.toString())) + .toList(); +} + +List? dasboardSpecialTypeNullableListFromJson( + List? dasboardSpecialType, [ + List? defaultValue, +]) { + if (dasboardSpecialType == null) { + return defaultValue; + } + + return dasboardSpecialType + .map((e) => dasboardSpecialTypeFromJson(e.toString())) + .toList(); +} + +String? fontStyleSettingNullableToJson( + enums.FontStyleSetting? fontStyleSetting, +) { + return fontStyleSetting?.value; +} + +String? fontStyleSettingToJson(enums.FontStyleSetting fontStyleSetting) { + return fontStyleSetting.value; +} + +enums.FontStyleSetting fontStyleSettingFromJson( + Object? fontStyleSetting, [ + enums.FontStyleSetting? defaultValue, +]) { + return enums.FontStyleSetting.values.firstWhereOrNull( + (e) => e.value == fontStyleSetting, + ) ?? + defaultValue ?? + enums.FontStyleSetting.swaggerGeneratedUnknown; +} + +enums.FontStyleSetting? fontStyleSettingNullableFromJson( + Object? fontStyleSetting, [ + enums.FontStyleSetting? defaultValue, +]) { + if (fontStyleSetting == null) { + return null; + } + return enums.FontStyleSetting.values.firstWhereOrNull( + (e) => e.value == fontStyleSetting, + ) ?? + defaultValue; +} + +String fontStyleSettingExplodedListToJson( + List? fontStyleSetting, +) { + return fontStyleSetting?.map((e) => e.value!).join(',') ?? ''; +} + +List fontStyleSettingListToJson( + List? fontStyleSetting, +) { + if (fontStyleSetting == null) { + return []; + } + + return fontStyleSetting.map((e) => e.value!).toList(); +} + +List fontStyleSettingListFromJson( + List? fontStyleSetting, [ + List? defaultValue, +]) { + if (fontStyleSetting == null) { + return defaultValue ?? []; + } + + return fontStyleSetting + .map((e) => fontStyleSettingFromJson(e.toString())) + .toList(); +} + +List? fontStyleSettingNullableListFromJson( + List? fontStyleSetting, [ + List? defaultValue, +]) { + if (fontStyleSetting == null) { + return defaultValue; + } + + return fontStyleSetting + .map((e) => fontStyleSettingFromJson(e.toString())) + .toList(); +} + +String? fontWeightSettingNullableToJson( + enums.FontWeightSetting? fontWeightSetting, +) { + return fontWeightSetting?.value; +} + +String? fontWeightSettingToJson(enums.FontWeightSetting fontWeightSetting) { + return fontWeightSetting.value; +} + +enums.FontWeightSetting fontWeightSettingFromJson( + Object? fontWeightSetting, [ + enums.FontWeightSetting? defaultValue, +]) { + return enums.FontWeightSetting.values.firstWhereOrNull( + (e) => e.value == fontWeightSetting, + ) ?? + defaultValue ?? + enums.FontWeightSetting.swaggerGeneratedUnknown; +} + +enums.FontWeightSetting? fontWeightSettingNullableFromJson( + Object? fontWeightSetting, [ + enums.FontWeightSetting? defaultValue, +]) { + if (fontWeightSetting == null) { + return null; + } + return enums.FontWeightSetting.values.firstWhereOrNull( + (e) => e.value == fontWeightSetting, + ) ?? + defaultValue; +} + +String fontWeightSettingExplodedListToJson( + List? fontWeightSetting, +) { + return fontWeightSetting?.map((e) => e.value!).join(',') ?? ''; +} + +List fontWeightSettingListToJson( + List? fontWeightSetting, +) { + if (fontWeightSetting == null) { + return []; + } + + return fontWeightSetting.map((e) => e.value!).toList(); +} + +List fontWeightSettingListFromJson( + List? fontWeightSetting, [ + List? defaultValue, +]) { + if (fontWeightSetting == null) { + return defaultValue ?? []; + } + + return fontWeightSetting + .map((e) => fontWeightSettingFromJson(e.toString())) + .toList(); +} + +List? fontWeightSettingNullableListFromJson( + List? fontWeightSetting, [ + List? defaultValue, +]) { + if (fontWeightSetting == null) { + return defaultValue; + } + + return fontWeightSetting + .map((e) => fontWeightSettingFromJson(e.toString())) + .toList(); +} + +String? messageTypeNullableToJson(enums.MessageType? messageType) { + return messageType?.value; +} + +String? messageTypeToJson(enums.MessageType messageType) { + return messageType.value; +} + +enums.MessageType messageTypeFromJson( + Object? messageType, [ + enums.MessageType? defaultValue, +]) { + return enums.MessageType.values.firstWhereOrNull( + (e) => e.value == messageType, + ) ?? + defaultValue ?? + enums.MessageType.swaggerGeneratedUnknown; +} + +enums.MessageType? messageTypeNullableFromJson( + Object? messageType, [ + enums.MessageType? defaultValue, +]) { + if (messageType == null) { + return null; + } + return enums.MessageType.values.firstWhereOrNull( + (e) => e.value == messageType, + ) ?? + defaultValue; +} + +String messageTypeExplodedListToJson(List? messageType) { + return messageType?.map((e) => e.value!).join(',') ?? ''; +} + +List messageTypeListToJson(List? messageType) { + if (messageType == null) { + return []; + } + + return messageType.map((e) => e.value!).toList(); +} + +List messageTypeListFromJson( + List? messageType, [ + List? defaultValue, +]) { + if (messageType == null) { + return defaultValue ?? []; + } + + return messageType.map((e) => messageTypeFromJson(e.toString())).toList(); +} + +List? messageTypeNullableListFromJson( + List? messageType, [ + List? defaultValue, +]) { + if (messageType == null) { + return defaultValue; + } + + return messageType.map((e) => messageTypeFromJson(e.toString())).toList(); +} + +typedef $JsonFactory = T Function(Map json); + +class $CustomJsonDecoder { + $CustomJsonDecoder(this.factories); + + final Map factories; + + dynamic decode(dynamic entity) { + if (entity is Iterable) { + return _decodeList(entity); + } + + if (entity is T) { + return entity; + } + + if (isTypeOf()) { + return entity; + } + + if (isTypeOf()) { + return entity; + } + + if (entity is Map) { + return _decodeMap(entity); + } + + return entity; + } + + T _decodeMap(Map values) { + final jsonFactory = factories[T]; + if (jsonFactory == null || jsonFactory is! $JsonFactory) { + return throw "Could not find factory for type $T. Is '$T: $T.fromJsonFactory' included in the CustomJsonDecoder instance creation in bootstrapper.dart?"; + } + + return jsonFactory(values); + } + + List _decodeList(Iterable values) => + values.where((v) => v != null).map((v) => decode(v) as T).toList(); +} + +class $JsonSerializableConverter extends chopper.JsonConverter { + @override + FutureOr> convertResponse( + chopper.Response response, + ) async { + if (response.bodyString.isEmpty) { + // In rare cases, when let's say 204 (no content) is returned - + // we cannot decode the missing json with the result type specified + return chopper.Response(response.base, null, error: response.error); + } + + if (ResultType == String) { + return response.copyWith(); + } + + if (ResultType == DateTime) { + return response.copyWith( + body: + DateTime.parse((response.body as String).replaceAll('"', '')) + as ResultType, + ); + } + + final jsonRes = await super.convertResponse(response); + return jsonRes.copyWith( + body: $jsonDecoder.decode(jsonRes.body) as ResultType, + ); + } +} + +final $jsonDecoder = $CustomJsonDecoder(generatedMapping); + +// ignore: unused_element +String? _dateToJson(DateTime? date) { + if (date == null) { + return null; + } + + final year = date.year.toString(); + final month = date.month < 10 ? '0${date.month}' : date.month.toString(); + final day = date.day < 10 ? '0${date.day}' : date.day.toString(); + + return '$year-$month-$day'; +} + +class Wrapped { + final T value; + const Wrapped.value(this.value); +} diff --git a/lib/screens/about_page.dart b/lib/screens/about_page.dart index 572363c..5f7b062 100644 --- a/lib/screens/about_page.dart +++ b/lib/screens/about_page.dart @@ -11,7 +11,7 @@ import 'package:smarthome/models/version_and_url.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class AboutPage extends ConsumerWidget { - const AboutPage({final Key? key}) : super(key: key); + const AboutPage({super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { @@ -24,7 +24,9 @@ class AboutPage extends ConsumerWidget { } Widget buildBody(final BuildContext context, final WidgetRef ref) { - final iconColor = AdaptiveTheme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black; + final iconColor = AdaptiveTheme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black; Future getBranch() async { final head = await rootBundle.loadString('.git/HEAD'); @@ -89,7 +91,8 @@ class AboutPage extends ConsumerWidget { ), FutureBuilder( future: UpdateManager.getVersionAndUrl(), - builder: (final context, final AsyncSnapshot snapshot) { + builder: (final context, + final AsyncSnapshot snapshot) { return ListTile( title: Row(children: [ Text(UpdateManager.getVersionString(snapshot.data)), @@ -128,14 +131,17 @@ class AboutPage extends ConsumerWidget { width: 32, ), title: const Text("Schau doch mal in den Code auf GitHub rein"), - onTap: () => HelperMethods.openUrl("https://github.com/susch19/smarthome"), + onTap: () => + HelperMethods.openUrl("https://github.com/susch19/smarthome"), ), const Divider(), ListTile( - leading: SvgPicture.asset("assets/vectors/smarthome_icon.svg", width: 32), - title: const Text("Wer hat dieses schicke Icon gemacht? Finde es heraus!"), - onTap: () => - HelperMethods.openUrl("https://iconarchive.com/show/flatwoken-icons-by-alecive/Apps-Home-icon.html"), + leading: SvgPicture.asset("assets/vectors/smarthome_icon.svg", + width: 32), + title: const Text( + "Wer hat dieses schicke Icon gemacht? Finde es heraus!"), + onTap: () => HelperMethods.openUrl( + "https://iconarchive.com/show/flatwoken-icons-by-alecive/Apps-Home-icon.html"), ), // Divider(), // ListTile( diff --git a/lib/screens/dynamic_ui_creator_page.dart b/lib/screens/dynamic_ui_creator_page.dart index 5dca1af..6d01593 100644 --- a/lib/screens/dynamic_ui_creator_page.dart +++ b/lib/screens/dynamic_ui_creator_page.dart @@ -5,9 +5,9 @@ import 'package:smarthome/devices/generic/stores/value_store.dart'; import 'package:smarthome/helper/connection_manager.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; import 'package:smarthome/helper/theme_manager.dart'; +import 'package:flutter_riverpod/legacy.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:smarthome/models/message.dart'; final _layoutProvider = FutureProvider>((final ref) async { final connection = ref.watch(hubConnectionConnectedProvider); @@ -22,7 +22,7 @@ final _selectedLayoutProvider = StateProvider((final ref) { }); class DynamicUiCreatorPage extends ConsumerStatefulWidget { - const DynamicUiCreatorPage({final Key? key}) : super(key: key); + const DynamicUiCreatorPage({super.key}); @override DynamicUiCreatorPageState createState() => DynamicUiCreatorPageState(); @@ -68,7 +68,8 @@ class DynamicUiCreatorPageState extends ConsumerState { (final e) => ListTile( title: Text(e.uniqueName), onTap: (() { - ref.read(_selectedLayoutProvider.notifier).state = e; + ref.read(_selectedLayoutProvider.notifier).state = + e; }), ), ) @@ -82,10 +83,20 @@ class DynamicUiCreatorPageState extends ConsumerState { ), ); } - return getEditView(context, selected); + return EditView(layout: selected); } +} + +class EditView extends StatelessWidget { + const EditView({ + super.key, + required this.layout, + }); + + final DeviceLayout layout; - Widget getEditView(final BuildContext context, final DeviceLayout layout) { + @override + Widget build(final BuildContext context) { return Column( children: [ Wrap( @@ -105,7 +116,10 @@ class DynamicUiCreatorPageState extends ConsumerState { spacing: 8, children: elements.map((final e) { return GenericDevice.getEditWidgetFor( - context, -1, e, ValueStore(-1, 1.0, "", Command.Brightness), ref); + -1, + e, + ValueStore(-1, 1.0, "", Command.brightness), + ); }).toList(), ), ), @@ -117,6 +131,5 @@ class DynamicUiCreatorPageState extends ConsumerState { ), ], ); - // return GenericDevice.getEditWidgetFor(context, -1, layout.dashboardDeviceLayout, null, ref); } } diff --git a/lib/screens/history_configure_screen.dart b/lib/screens/history_configure_screen.dart index 012930d..1e0424c 100644 --- a/lib/screens/history_configure_screen.dart +++ b/lib/screens/history_configure_screen.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:smarthome/devices/generic/generic_device_exporter.dart'; import 'package:smarthome/helper/connection_manager.dart'; import 'package:smarthome/helper/iterable_extensions.dart'; import 'package:smarthome/helper/theme_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; final _serverDevices = FutureProvider>((final ref) async { final connection = ref.watch(hubConnectionConnectedProvider); @@ -11,7 +13,8 @@ final _serverDevices = FutureProvider>((final ref) async { final serverDevices = await connection.invoke("GetAllDevices", args: []); if (serverDevices is! List) return []; - serverDevices.sort((final a, final b) => (a["typeName"] as String).compareTo(b["typeName"] as String)); + serverDevices.sort((final a, final b) => + (a["typeName"] as String).compareTo(b["typeName"] as String)); return serverDevices; }); @@ -19,16 +22,21 @@ final _historyProperties = FutureProvider>((final ref) async { final connection = ref.watch(hubConnectionConnectedProvider); if (connection == null) return []; - final serverDevices = await connection.invoke("GetHistoryPropertySettings", args: []); + final serverDevices = + await connection.invoke("GetHistoryPropertySettings", args: []); if (serverDevices is! List) return []; return serverDevices; }); -final _overwrittenStates = StateProvider.family, int>(((final ref, final _) => {})); -final _overwrittenStatesGrouped = StateProvider.family, String>(((final ref, final _) => {})); +final _overwrittenStates = + StateProvider.family, int>(((final ref, final _) => {})); +final _overwrittenStatesGrouped = + StateProvider.family, String>( + ((final ref, final _) => {})); -final _serverHistoryProperties = FutureProvider>>((final ref) async { +final _serverHistoryProperties = + FutureProvider>>((final ref) async { final serverDevices = (await ref.watch(_serverDevices.future)); final serverProps = (await ref.watch(_historyProperties.future)); return serverDevices @@ -41,11 +49,13 @@ final _serverHistoryProperties = FutureProvider>>((fin element.key != "sendPayload" && element.key != "transition_Time") .toMap((final element) => element.key, (final element) { - if (element.key == "id" || element.key == "typeName" || element.key == "friendlyName") { + if (element.key == "id" || + element.key == "typeName" || + element.key == "friendlyName") { return element.value; } - final prop = - serverProps.firstOrNull((final d) => d["deviceId"] == i["id"] && d["propertyName"] == element.key); + final prop = serverProps.firstOrDefault((final d) => + d["deviceId"] == i["id"] && d["propertyName"] == element.key); if (prop == null) return false; return prop["enabled"] as bool; })) @@ -55,7 +65,7 @@ final _serverHistoryProperties = FutureProvider>>((fin class HistoryConfigureScreen extends ConsumerWidget { final String title; final bool byDeviceType; - const HistoryConfigureScreen(this.title, this.byDeviceType, {final Key? key}) : super(key: key); + const HistoryConfigureScreen(this.title, this.byDeviceType, {super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { @@ -69,7 +79,7 @@ class HistoryConfigureScreen extends ConsumerWidget { Widget buildBody(final BuildContext context, final WidgetRef ref) { final devices = ref.watch(_serverHistoryProperties); - final connection = ref.watch(hubConnectionConnectedProvider); + final api = ref.watch(apiProvider); return RefreshIndicator( child: Container( decoration: ThemeManager.getBackgroundDecoration(context), @@ -80,30 +90,52 @@ class HistoryConfigureScreen extends ConsumerWidget { return d .groupBy((final p0) => p0["typeName"] as String) .map((final key, final values) { - final ids = values.map((final e) => e["id"] as int).toList(growable: true); + final ids = values + .map((final e) => e["id"] as int) + .toList(growable: true); final props = values - .mapMany((final e) => e.keys.where((final element) => - element != "id" && element != "typeName" && element != "friendlyName")) + .mapMany((final e) => e.keys.where( + (final element) => + element != "id" && + element != "typeName" && + element != "friendlyName")) .distinct() .toList(); final exp = ExpansionTile( title: Text(key), children: props .map((final e) => Consumer( - builder: (final context, final ref, final child) { - final overwrite = ref.watch(_overwrittenStatesGrouped(key)); + builder: (final context, final ref, + final child) { + final overwrite = ref.watch( + _overwrittenStatesGrouped(key)); final val = values.all( - (final element) => !element.containsKey(e) || element[e] as bool == true); + (final element) => + !element.containsKey(e) || + element[e] as bool == true); return ListTile( title: Text(e), trailing: Checkbox( - value: overwrite.containsKey(e) ? overwrite[e] : val, - onChanged: (final v) { - if (v == null || connection == null) return; - connection.invoke("SetHistories", args: [v, ids, e]); - final stateNotifier = ref.read(_overwrittenStatesGrouped(key).notifier); - final newState = stateNotifier.state.entries.toMap( - (final element) => element.key, (final element) => element.value); + value: overwrite.containsKey(e) + ? overwrite[e] + : val, + onChanged: (final v) async { + if (v == null) return; + await api.appHistoryPatch( + body: SetHistoryRequest( + enable: v, + ids: ids, + name: e)); + final stateNotifier = ref.read( + _overwrittenStatesGrouped(key) + .notifier); + final newState = stateNotifier + .state.entries + .toMap( + (final element) => + element.key, + (final element) => + element.value); newState[e] = v; stateNotifier.state = newState; }, @@ -126,20 +158,35 @@ class HistoryConfigureScreen extends ConsumerWidget { title: Text("$id : ${e["friendlyName"]}"), children: e.entries .where((final element) => - element.key != "id" && element.key != "typeName" && element.key != "friendlyName") + element.key != "id" && + element.key != "typeName" && + element.key != "friendlyName") .map((final e) => Consumer( - builder: (final context, final ref, final child) { - final overwrite = ref.watch(_overwrittenStates(id)); + builder: + (final context, final ref, final child) { + final overwrite = + ref.watch(_overwrittenStates(id)); return ListTile( title: Text(e.key), trailing: Checkbox( - value: overwrite.containsKey(e.key) ? overwrite[e.key] : e.value, - onChanged: (final v) { - if (v == null || connection == null) return; - connection.invoke("SetHistory", args: [v, id, e.key]); - final stateNotifier = ref.read(_overwrittenStates(id).notifier); - final newState = stateNotifier.state.entries - .toMap((final element) => element.key, (final element) => element.value); + value: overwrite.containsKey(e.key) + ? overwrite[e.key] + : e.value, + onChanged: (final v) async { + if (v == null) return; + + await api.appHistoryPatch( + body: SetHistoryRequest( + enable: v, + ids: [id], + name: e.key)); + final stateNotifier = ref.read( + _overwrittenStates(id).notifier); + final newState = + stateNotifier.state.entries.toMap( + (final element) => element.key, + (final element) => + element.value); newState[e.key] = v; stateNotifier.state = newState; }, @@ -151,7 +198,10 @@ class HistoryConfigureScreen extends ConsumerWidget { ); }).toList(); }, - error: (final o, final s) => [const Text("Daten konnten nicht vom Server angefragt werden:\r\n")], + error: (final o, final s) => [ + const Text( + "Daten konnten nicht vom Server angefragt werden:\r\n") + ], loading: () => [const Text("Loading...")])), ), onRefresh: () async { diff --git a/lib/screens/screen_export.dart b/lib/screens/screen_export.dart index 38084af..39d3c3a 100644 --- a/lib/screens/screen_export.dart +++ b/lib/screens/screen_export.dart @@ -1,2 +1,2 @@ export 'about_page.dart'; -export 'server_search_page.dart'; \ No newline at end of file +export 'server_search_page.dart'; diff --git a/lib/screens/server_search_page.dart b/lib/screens/server_search_page.dart index eaf8797..a3b839e 100644 --- a/lib/screens/server_search_page.dart +++ b/lib/screens/server_search_page.dart @@ -7,22 +7,26 @@ import 'package:smarthome/helper/mdns_manager.dart'; import 'package:smarthome/helper/theme_manager.dart'; import 'package:smarthome/models/server_record.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; -final _currentAppVersionProvider = FutureProvider((final ref) async { +final _currentAppVersionProvider = + FutureProvider((final ref) async { return await PackageInfo.fromPlatform(); }); -final serverRecordsProvider = FutureProvider.autoDispose>((final ref) async { +final serverRecordsProvider = + FutureProvider.autoDispose>((final ref) async { final records = ServerSearchScreen.refresh(force: true); return records; }); -final _chosenValueProvider = StateProvider.family((final ref, final value) { +final _chosenValueProvider = + StateProvider.family((final ref, final value) { return ""; }); class ServerSearchScreen extends ConsumerWidget { - const ServerSearchScreen({final Key? key}) : super(key: key); + const ServerSearchScreen({super.key}); @override Widget build(final BuildContext context, final WidgetRef ref) { @@ -47,7 +51,7 @@ class ServerSearchScreen extends ConsumerWidget { .toList() .injectForIndex((final i) => i < 1 ? null : const Divider()) .toList(), - error: (final _, final __) => [Container()], + error: (final _, final __) => [const SizedBox()], loading: () => [ Row( mainAxisAlignment: MainAxisAlignment.center, @@ -69,15 +73,15 @@ class ServerSearchScreen extends ConsumerWidget { ); } - StatelessWidget mapServerValue( - final ServerRecord e, final BuildContext context, final AsyncValue appVersion) { + Widget mapServerValue(final ServerRecord e, final BuildContext context, + final AsyncValue appVersion) { final menuItems = e.reachableAddresses .map((final a) => DropdownMenuItem( value: a.ipAddress, child: Text(a.ipAddress.toString()), )) .toList(); - if (menuItems.isEmpty) return Container(); + if (menuItems.isEmpty) return const SizedBox(); return ListTile( title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -88,8 +92,10 @@ class ServerSearchScreen extends ConsumerWidget { style: const TextStyle(fontWeight: FontWeight.bold), ), appVersion.when( - data: (final data) => Icon(e.minAppVersion < data ? Icons.check : Icons.warning), - error: (final error, final stackTrace) => Text(error.toString()), + data: (final data) => + Icon(e.minAppVersion < data ? Icons.check : Icons.warning), + error: (final error, final stackTrace) => + Text(error.toString()), loading: () => const Text("Loading...")), ], ), @@ -101,11 +107,13 @@ class ServerSearchScreen extends ConsumerWidget { final value = ref.watch(_chosenValueProvider(e.fqdn)); return DropdownButton( - value: value == "" ? e.reachableAddresses[0].ipAddress : value, + value: + value == "" ? e.reachableAddresses[0].ipAddress : value, items: menuItems, onChanged: (final value) { if (value is! String) return; - ref.read(_chosenValueProvider(e.fqdn).notifier).state = value; + ref.read(_chosenValueProvider(e.fqdn).notifier).state = + value; }, ); }, @@ -114,7 +122,9 @@ class ServerSearchScreen extends ConsumerWidget { ), Container( margin: const EdgeInsets.symmetric(vertical: 8.0), - child: e.debug ? const Text("❗ Testserver, betreten auf eigene Gefahr") : Container(), + child: e.debug + ? const Text("❗ Testserver, betreten auf eigene Gefahr") + : const SizedBox(), ), Consumer( builder: (final context, final ref, final child) { @@ -140,8 +150,10 @@ class ServerSearchScreen extends ConsumerWidget { final List records = []; if (!MdnsManager.initialized) await MdnsManager.initialize(); - await for (final record - in MdnsManager.getRecords(timeToLive: force ? const Duration(milliseconds: 1) : const Duration(minutes: 5))) { + await for (final record in MdnsManager.getRecords( + timeToLive: force + ? const Duration(milliseconds: 1) + : const Duration(minutes: 5))) { records.add(record); } return records; diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index b248821..a319342 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -6,13 +6,14 @@ import 'package:smarthome/helper/theme_manager.dart'; import 'package:smarthome/main.dart'; import 'package:smarthome/models/ipport.dart'; import 'package:smarthome/screens/history_configure_screen.dart'; +import 'package:flutter_riverpod/legacy.dart'; import '../helper/connection_manager.dart'; import 'screen_export.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class SettingsPage extends ConsumerStatefulWidget { - const SettingsPage({final Key? key}) : super(key: key); + const SettingsPage({super.key}); @override SettingsPageState createState() => SettingsPageState(); @@ -22,14 +23,15 @@ class SettingsPageState extends ConsumerState { bool isEnabled = false; final TextEditingController _textEditingController = TextEditingController(); - static final _maxExtendSlider = StateProvider((final ref) => ref.watch(maxCrossAxisExtentProvider)); + static final _maxExtendSlider = StateProvider( + (final ref) => ref.watch(maxCrossAxisExtentProvider)); @override Widget build(final BuildContext context) { final theme = AdaptiveTheme.of(context); final hubConnection = ref.watch(hubConnectionConnectedProvider); // final _ = ref.watch(brightnessProvider); - final settings = ref.watch(settingsProvider); + final settings = ref.watch(settingsManagerProvider); _textEditingController.text = settings.serverUrl; return Scaffold( @@ -54,7 +56,8 @@ class SettingsPageState extends ConsumerState { children: [ Icon( Icons.smartphone, - color: theme.theme.iconTheme.color!.withOpacity(theme.mode.isSystem ? 1 : 0.3), + color: theme.theme.iconTheme.color! + .withOpacity(theme.mode.isSystem ? 1 : 0.3), ), const Text("System folgen"), ], @@ -66,8 +69,12 @@ class SettingsPageState extends ConsumerState { MaterialButton( child: Column( children: [ - Icon(theme.mode.isLight ? Icons.light_mode : Icons.light_mode_outlined, - color: theme.theme.iconTheme.color!.withOpacity(theme.mode.isLight ? 1 : 0.3)), + Icon( + theme.mode.isLight + ? Icons.light_mode + : Icons.light_mode_outlined, + color: theme.theme.iconTheme.color! + .withOpacity(theme.mode.isLight ? 1 : 0.3)), const Text("Helles Design"), ], ), @@ -78,8 +85,12 @@ class SettingsPageState extends ConsumerState { MaterialButton( child: Column( children: [ - Icon(theme.mode.isDark ? Icons.dark_mode : Icons.dark_mode_outlined, - color: theme.theme.iconTheme.color!.withOpacity(theme.mode.isDark ? 1 : 0.3)), + Icon( + theme.mode.isDark + ? Icons.dark_mode + : Icons.dark_mode_outlined, + color: theme.theme.iconTheme.color! + .withOpacity(theme.mode.isDark ? 1 : 0.3)), const Text("Dunkles Design"), ], ), @@ -95,7 +106,9 @@ class SettingsPageState extends ConsumerState { trailing: Switch( value: !settings.groupingEnabled, onChanged: (final a) { - SettingsManager.setGroupingEnabled(!a); + ref + .read(settingsManagerProvider.notifier) + .setGroupingEnabled(!a); }, ), ), @@ -109,8 +122,10 @@ class SettingsPageState extends ConsumerState { max: 700, divisions: 60, onChangeEnd: (final value) { - ref.read(maxCrossAxisExtentProvider.notifier).state = value; - PreferencesManager.instance.setDouble("DashboardCardSize", value); + ref.read(maxCrossAxisExtentProvider.notifier).state = + value; + PreferencesManager.instance + .setDouble("DashboardCardSize", value); }, onChanged: (final a) { ref.read(_maxExtendSlider.notifier).state = a; @@ -151,7 +166,8 @@ class SettingsPageState extends ConsumerState { Navigator.push( context, MaterialPageRoute( - builder: (final c) => const HistoryConfigureScreen("Historie Konfiguration", true))); + builder: (final c) => const HistoryConfigureScreen( + "Historie Konfiguration", true))); }, ), ListTile( @@ -160,7 +176,8 @@ class SettingsPageState extends ConsumerState { Navigator.push( context, MaterialPageRoute( - builder: (final c) => const HistoryConfigureScreen("Historie Konfiguration", false))); + builder: (final c) => const HistoryConfigureScreen( + "Historie Konfiguration", false))); }, ), const Divider(), @@ -173,7 +190,10 @@ class SettingsPageState extends ConsumerState { ListTile( title: const Text("Serversuche öffnen"), onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (final c) => const ServerSearchScreen())) + Navigator.push( + context, + MaterialPageRoute( + builder: (final c) => const ServerSearchScreen())) .then((final value) { if (value is IpPort) { final isIpv6 = value.type.name == "IPv6"; @@ -181,7 +201,9 @@ class SettingsPageState extends ConsumerState { final uri = Uri.parse( "http://${isIpv6 ? "[" : ""}${value.ipAddress}${isIpv6 ? "]" : ""}:${value.port}/SmartHome"); - SettingsManager.setServerUrl(uri.toString()); + ref + .read(settingsManagerProvider.notifier) + .setServerUrl(uri.toString()); _textEditingController.text = uri.toString(); } }); @@ -191,17 +213,20 @@ class SettingsPageState extends ConsumerState { leading: const Text("URL"), title: TextField( controller: _textEditingController, - decoration: const InputDecoration(hintText: "URL vom Smarthome Server"), + decoration: + const InputDecoration(hintText: "URL vom Smarthome Server"), onSubmitted: (final s) { - SettingsManager.setServerUrl(s); + ref.read(settingsManagerProvider.notifier).setServerUrl(s); }, ), trailing: MaterialButton( child: const Text("Speichern"), onPressed: () { - SettingsManager.setServerUrl(_textEditingController.text); - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar(content: Text("Neue URL wurde gespeichert"))); + ref + .read(settingsManagerProvider.notifier) + .setServerUrl(_textEditingController.text); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text("Neue URL wurde gespeichert"))); }, ), ), @@ -210,7 +235,9 @@ class SettingsPageState extends ConsumerState { trailing: Switch( value: settings.showDebugInformation, onChanged: (final a) { - SettingsManager.setShowDebugInformation(a); + ref + .read(settingsManagerProvider.notifier) + .setShowDebugInformation(a); }, ), ), @@ -222,7 +249,7 @@ class SettingsPageState extends ConsumerState { ), onTap: () => hubConnection?.invoke("UpdateTime"), ) - : Container(), + : const SizedBox(), // settings.showDebugInformation // ? ListTile( // title: const Text( @@ -235,8 +262,8 @@ class SettingsPageState extends ConsumerState { // ), // ), // ) - // : Container(), - settings.showDebugInformation ? const Divider() : Container(), + // : const SizedBox(), + settings.showDebugInformation ? const Divider() : const SizedBox(), ListTile( leading: const Text("Über"), onTap: () => Navigator.push( diff --git a/lib/screens/setup_page.dart b/lib/screens/setup_page.dart new file mode 100644 index 0000000..fddfd36 --- /dev/null +++ b/lib/screens/setup_page.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:smarthome/helper/settings_manager.dart'; + +class SetupPage extends HookConsumerWidget { + const SetupPage({super.key}); + + @override + Widget build(final BuildContext context, final WidgetRef ref) { + final settingsProv = ref.watch(settingsManagerProvider); + final nameController = useTextEditingController(text: settingsProv.name); + final urlController = + useTextEditingController(text: settingsProv.serverUrl); + final urlError = useState(null); + final nameError = useState(null); + + return Scaffold( + body: Center( + child: Card( + elevation: 2, + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 40, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + "Bitte geben Sie die Daten für das Setup ein.", + ), + ), + ListTile( + title: TextField( + onChanged: (final value) { + validateInput(urlController.text, urlError, + nameController.text, nameError); + }, + controller: nameController, + decoration: InputDecoration( + errorText: nameError.value, + label: Text("Name"), + border: const OutlineInputBorder()), + ), + ), + ListTile( + title: TextField( + onChanged: (final value) { + validateInput(urlController.text, urlError, + nameController.text, nameError); + }, + controller: urlController, + decoration: InputDecoration( + errorText: urlError.value, + label: Text("URL"), + border: const OutlineInputBorder()), + ), + ), + ], + ), + ), + ), + SizedBox.fromSize( + size: const Size.fromHeight(48), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: FilledButton( + onPressed: () { + if (!validateInput(urlController.text, urlError, + nameController.text, nameError)) { + return; + } + + final settingsManager = + ref.read(settingsManagerProvider.notifier); + settingsManager.setServerUrl(urlController.text); + settingsManager.setName(nameController.text); + }, + child: Text( + "Weiter", + ), + ), + ), + ) + ], + ), + ), + ), + ), + ); + } + + bool validateInput(final String url, final ValueNotifier urlError, final String name, + final ValueNotifier nameError) { + final uri = Uri.tryParse(url); + bool anyError = false; + if (uri == null || + !uri.hasScheme || + !uri.hasAuthority || + uri.host.isEmpty) { + urlError.value = "URL has wrong format"; + anyError = true; + } else { + urlError.value = null; + } + if (name.isEmpty) { + nameError.value = "Name eingeben"; + anyError = true; + } else { + nameError.value = null; + } + + return !anyError; + } +} diff --git a/lib/session/cert_file.dart b/lib/session/cert_file.dart index b33669b..1cf8203 100644 --- a/lib/session/cert_file.dart +++ b/lib/session/cert_file.dart @@ -1,9 +1,8 @@ -class CertFile{ - String serverUrl; - String username; - String email; - String pw; - - CertFile(this.serverUrl, this.username, this.email, this.pw); +class CertFile { + String serverUrl; + String username; + String email; + String pw; -} \ No newline at end of file + CertFile(this.serverUrl, this.username, this.email, this.pw); +} diff --git a/lib/session/requests.dart b/lib/session/requests.dart index 1f85b62..decc81e 100644 --- a/lib/session/requests.dart +++ b/lib/session/requests.dart @@ -1,4 +1,3 @@ - // import 'package:json_annotation/json_annotation.dart'; // part 'requests.g.dart'; @@ -10,4 +9,4 @@ // String email; // String password; -// } \ No newline at end of file +// } diff --git a/lib/signalr/smarthome_protocol.dart b/lib/signalr/smarthome_protocol.dart index dc0b0fd..601a598 100644 --- a/lib/signalr/smarthome_protocol.dart +++ b/lib/signalr/smarthome_protocol.dart @@ -12,14 +12,11 @@ import 'package:signalr_netcore/itransport.dart'; import 'package:signalr_netcore/text_message_format.dart'; import 'package:signalr_netcore/utils.dart'; import 'package:encrypt/encrypt.dart'; -import 'package:smarthome/cloud/app_cloud_configuration.dart'; class SmarthomeProtocol implements IHubProtocol { // Properties - static AppCloudConfiguration? cloudConfig; - - Key get _key => Key(cloudConfig?.keyBytes ?? Uint8List(32)); + Key get _key => Key(Uint8List(32)); @override String get name => "smarthome"; @@ -41,7 +38,8 @@ class SmarthomeProtocol implements IHubProtocol { List parseMessages(final Object input, final Logger? logger) { // Only JsonContent is allowed. if (!(input is Uint8List)) { - throw GeneralError("Invalid input for JSON hub protocol. Expected a string."); + throw GeneralError( + "Invalid input for JSON hub protocol. Expected a string."); } final List hubMessages = []; @@ -53,15 +51,21 @@ class SmarthomeProtocol implements IHubProtocol { final len = _getLengthOfBytes(input.sublist(lastIndex, lastIndex + 4)); final iv = IV(input.sublist(lastIndex + 4, lastIndex + 20)); - final inputWithoutLen = input.sublist(lastIndex + 20, lastIndex + len + 20); + final inputWithoutLen = + input.sublist(lastIndex + 20, lastIndex + len + 20); lastIndex = lastIndex + len + 20; final List decrypted; try { - decrypted = encrypter.decryptBytes(Encrypted(inputWithoutLen), iv: iv); + decrypted = + encrypter.decryptBytes(Encrypted(inputWithoutLen), iv: iv); } catch (e) { //Don't know where these 16 bytes come from. The server is not sending anything at this point //Therefore filtering it the dirty way, so the connection doesn't have to be reestablished - if (len == 16 && e.toString() == "Invalid argument(s): Invalid or corrupted pad block") return []; + if (len == 16 && + e.toString() == + "Invalid argument(s): Invalid or corrupted pad block") { + return []; + } rethrow; } @@ -123,37 +127,50 @@ class SmarthomeProtocol implements IHubProtocol { } } - static InvocationMessage _getInvocationMessageFromJson(final Map jsonData) { - final MessageHeaders? headers = createMessageHeadersFromJson(jsonData["headers"]); + static InvocationMessage _getInvocationMessageFromJson( + final Map jsonData) { + final MessageHeaders? headers = + createMessageHeadersFromJson(jsonData["headers"]); final message = InvocationMessage( target: jsonData["target"], arguments: jsonData["arguments"]?.cast().toList(), - streamIds: (jsonData["streamIds"] == null) ? null : (List.from(jsonData["streamIds"] as List)), + streamIds: (jsonData["streamIds"] == null) + ? null + : (List.from(jsonData["streamIds"] as List)), headers: headers, invocationId: jsonData["invocationId"] as String?); - _assertNotEmptyString(message.target, "Invalid payload for Invocation message."); + _assertNotEmptyString( + message.target, "Invalid payload for Invocation message."); if (message.invocationId != null) { - _assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message."); + _assertNotEmptyString( + message.invocationId, "Invalid payload for Invocation message."); } return message; } - static StreamItemMessage _getStreamItemMessageFromJson(final Map jsonData) { - final MessageHeaders? headers = createMessageHeadersFromJson(jsonData["headers"]); - final message = - StreamItemMessage(item: jsonData["item"], headers: headers, invocationId: jsonData["invocationId"] as String?); + static StreamItemMessage _getStreamItemMessageFromJson( + final Map jsonData) { + final MessageHeaders? headers = + createMessageHeadersFromJson(jsonData["headers"]); + final message = StreamItemMessage( + item: jsonData["item"], + headers: headers, + invocationId: jsonData["invocationId"] as String?); - _assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message."); + _assertNotEmptyString( + message.invocationId, "Invalid payload for StreamItem message."); if (message.item == null) { throw InvalidPayloadException("Invalid payload for StreamItem message."); } return message; } - static CompletionMessage _getCompletionMessageFromJson(final Map jsonData) { - final MessageHeaders? headers = createMessageHeadersFromJson(jsonData["headers"]); + static CompletionMessage _getCompletionMessageFromJson( + final Map jsonData) { + final MessageHeaders? headers = + createMessageHeadersFromJson(jsonData["headers"]); final message = CompletionMessage( error: jsonData["error"], result: jsonData["result"], @@ -165,18 +182,22 @@ class SmarthomeProtocol implements IHubProtocol { } if ((message.result == null) && (message.error != null)) { - _assertNotEmptyString(message.error, "Invalid payload for Completion message."); + _assertNotEmptyString( + message.error, "Invalid payload for Completion message."); } return message; } - static PingMessage _getPingMessageFromJson(final Map jsonData) { + static PingMessage _getPingMessageFromJson( + final Map jsonData) { return PingMessage(); } - static CloseMessage _getCloseMessageFromJson(final Map jsonData) { - return CloseMessage(error: jsonData["error"], allowReconnect: jsonData["allowReconnect"]); + static CloseMessage _getCloseMessageFromJson( + final Map jsonData) { + return CloseMessage( + error: jsonData["error"], allowReconnect: jsonData["allowReconnect"]); } /// Writes the specified HubMessage to a string and returns it. @@ -188,13 +209,15 @@ class SmarthomeProtocol implements IHubProtocol { Object writeMessage(final HubMessageBase message) { final jsonObj = _messageAsMap(message); final iv = IV.fromSecureRandom(16); - final compressed = gzip.encode(utf8.encode(TextMessageFormat.write(json.encode(jsonObj)))); + final compressed = + gzip.encode(utf8.encode(TextMessageFormat.write(json.encode(jsonObj)))); final res = encrypt(compressed, iv); // final compressed = gzip.encode(Uint8List.fromList([...iv.bytes, ...res])); // return GZipCodec().encode(Uint8List.fromList([..._getBytesOfInt(res.length), ...iv.bytes, ...res])); - return Uint8List.fromList([..._getBytesOfInt(res.length), ...iv.bytes, ...res]); + return Uint8List.fromList( + [..._getBytesOfInt(res.length), ...iv.bytes, ...res]); } int _getLengthOfBytes(final Uint8List list) { @@ -275,7 +298,11 @@ class SmarthomeProtocol implements IHubProtocol { } if (message is CloseMessage) { - return {"type": messageType, "error": message.error, "allowReconnect": message.allowReconnect}; + return { + "type": messageType, + "error": message.error, + "allowReconnect": message.allowReconnect + }; } if (message is CancelInvocationMessage) { @@ -285,7 +312,8 @@ class SmarthomeProtocol implements IHubProtocol { throw GeneralError("Converting '${message.type}' is not implemented."); } - static void _assertNotEmptyString(final String? value, final String errorMessage) { + static void _assertNotEmptyString( + final String? value, final String errorMessage) { if (isStringEmpty(value)) { throw InvalidPayloadException(errorMessage); } diff --git a/lib/swagger_jsons/swagger.json b/lib/swagger_jsons/swagger.json new file mode 100644 index 0000000..41d3c7d --- /dev/null +++ b/lib/swagger_jsons/swagger.json @@ -0,0 +1,2336 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "AppBrokerASP", + "version": "1.0" + }, + "paths": { + "/app": { + "get": { + "tags": [ + "App" + ], + "parameters": [ + { + "name": "name", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "application/json": { + "schema": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "text/json": { + "schema": { + "type": "string", + "format": "uuid", + "nullable": true + } + } + } + } + } + }, + "patch": { + "tags": [ + "App" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "newName", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/app/settings": { + "get": { + "tags": [ + "App" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "key", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "tags": [ + "App" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "key", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "value", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/device/rebuild/{id}": { + "patch": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device": { + "patch": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "onlyNew", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "put": { + "tags": [ + "Device" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/RestCreatedDevice" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestCreatedDevice" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/RestCreatedDevice" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/RestCreatedDevice" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "includeState", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/states/{id}": { + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/history/{id}": { + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "from", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "propName", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/state/{id}/{name}": { + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/state/{id}": { + "post": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/app/device": { + "get": { + "tags": [ + "Device" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Device" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Device" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Device" + } + } + } + } + } + } + }, + "patch": { + "tags": [ + "Device" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/DeviceRenameRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceRenameRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/DeviceRenameRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/DeviceRenameRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/app/device/overview": { + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "onlyShowInApp", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceOverview" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceOverview" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceOverview" + } + } + } + } + } + } + } + }, + "/app/history/settings": { + "get": { + "tags": [ + "History" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyState" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyState" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyState" + } + } + } + } + } + } + } + }, + "/app/history": { + "patch": { + "tags": [ + "History" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/SetHistoryRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetHistoryRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SetHistoryRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SetHistoryRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "get": { + "tags": [ + "History" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "dt", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/History" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/History" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/History" + } + } + } + } + } + } + } + }, + "/app/history/range": { + "get": { + "tags": [ + "History" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "from", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "propertyName", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/History" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/History" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/History" + } + } + } + } + } + } + }, + "/app/layout/single": { + "get": { + "tags": [ + "Layout" + ], + "parameters": [ + { + "name": "TypeName", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IconName", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "DeviceId", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/LayoutResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/LayoutResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + } + } + } + } + }, + "/app/layout/multi": { + "get": { + "tags": [ + "Layout" + ], + "parameters": [ + { + "name": "request", + "in": "query", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutRequest" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + } + } + } + } + } + }, + "/app/layout/all": { + "get": { + "tags": [ + "Layout" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + } + } + } + } + } + }, + "/app/layout/iconByName": { + "get": { + "tags": [ + "Layout" + ], + "parameters": [ + { + "name": "name", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SvgIcon" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SvgIcon" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SvgIcon" + } + } + } + } + } + } + }, + "/app/layout": { + "patch": { + "tags": [ + "Layout" + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/notification/sendNotification": { + "post": { + "tags": [ + "Notification" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/AppNotification" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppNotification" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/AppNotification" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/AppNotification" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/notification/firebaseOptions": { + "get": { + "tags": [ + "Notification" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FirebaseOptions" + } + } + }, + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FirebaseOptions" + } + } + }, + "text/json": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FirebaseOptions" + } + } + } + } + } + } + } + }, + "/notification/nextNotificationId": { + "get": { + "tags": [ + "Notification" + ], + "parameters": [ + { + "name": "uniqueName", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/notification/allOneTimeNotifications": { + "get": { + "tags": [ + "Notification" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/AllOneTimeNotifications" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllOneTimeNotifications" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/AllOneTimeNotifications" + } + } + } + } + } + } + }, + "/notification/allGlobalNotifications": { + "get": { + "tags": [ + "Notification" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationSetup" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationSetup" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationSetup" + } + } + } + } + } + } + } + }, + "/painless/time": { + "put": { + "tags": [ + "Painless" + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/security": { + "get": { + "tags": [ + "Security" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/AppCloudConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppCloudConfiguration" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/AppCloudConfiguration" + } + } + } + } + } + } + }, + "/app/smarthome": { + "post": { + "tags": [ + "Smarthome" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiSmarthomeMessage" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonApiSmarthomeMessage" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/JsonApiSmarthomeMessage" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiSmarthomeMessage" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "get": { + "tags": [ + "Smarthome" + ], + "parameters": [ + { + "name": "deviceId", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/app/smarthome/log": { + "post": { + "tags": [ + "Smarthome" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AppLog" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AppLog" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AppLog" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AppLog" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/windmill": { + "post": { + "tags": [ + "Windmill" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/WindmillSmarthomeMessage" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/WindmillSmarthomeMessage" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/WindmillSmarthomeMessage" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/WindmillSmarthomeMessage" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "AllOneTimeNotifications": { + "required": [ + "topics" + ], + "type": "object", + "properties": { + "topics": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "AppCloudConfiguration": { + "required": [ + "host", + "id", + "key", + "port" + ], + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer", + "format": "int32" + }, + "key": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "AppLog": { + "required": [ + "logLevel", + "message", + "timeStamp" + ], + "type": "object", + "properties": { + "logLevel": { + "$ref": "#/components/schemas/AppLogLevel" + }, + "timeStamp": { + "type": "string", + "format": "date-time" + }, + "message": { + "type": "string" + }, + "loggerName": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "AppLogLevel": { + "enum": [ + 0, + 1, + 2, + 3, + 4 + ], + "type": "integer", + "format": "int32", + "x-enumNames": [ + "Fatal", + "Error", + "Warning", + "Info", + "Debug" + ], + "x-enum-varnames": [ + "Fatal", + "Error", + "Warning", + "Info", + "Debug" + ] + }, + "AppNotification": { + "required": [ + "topic", + "typeName", + "wasOneTime" + ], + "type": "object", + "properties": { + "typeName": { + "type": "string", + "readOnly": true + }, + "topic": { + "type": "string" + }, + "wasOneTime": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Command": { + "type": "integer", + "format": "int32" + }, + "DasboardSpecialType": { + "enum": [ + "None", + "Right", + "Availability", + "Color", + "Disabled", + "Battery" + ], + "type": "string", + "x-enumNames": [ + "None", + "Right", + "Availability", + "Color", + "Disabled", + "Battery" + ], + "x-enum-varnames": [ + "None", + "Right", + "Availability", + "Color", + "Disabled", + "Battery" + ] + }, + "DashboardDeviceLayout": { + "required": [ + "dashboardProperties" + ], + "type": "object", + "properties": { + "dashboardProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DashboardPropertyInfo" + } + } + }, + "additionalProperties": false + }, + "DashboardPropertyInfo": { + "required": [ + "displayName", + "format", + "name", + "order", + "specialType", + "unitOfMeasurement" + ], + "type": "object", + "properties": { + "specialType": { + "$ref": "#/components/schemas/DasboardSpecialType" + }, + "name": { + "type": "string" + }, + "order": { + "type": "integer", + "format": "int32" + }, + "textStyle": { + "$ref": "#/components/schemas/TextSettings" + }, + "editInfo": { + "$ref": "#/components/schemas/PropertyEditInformation" + }, + "rowNr": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "unitOfMeasurement": { + "type": "string" + }, + "format": { + "type": "string" + }, + "showOnlyInDeveloperMode": { + "type": "boolean", + "nullable": true + }, + "deviceId": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "expanded": { + "type": "boolean", + "nullable": true + }, + "precision": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { }, + "nullable": true, + "readOnly": true + }, + "displayName": { + "type": "string" + } + }, + "additionalProperties": { } + }, + "DetailDeviceLayout": { + "required": [ + "historyProperties", + "propertyInfos", + "tabInfos" + ], + "type": "object", + "properties": { + "propertyInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DetailPropertyInfo" + } + }, + "tabInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DetailTabInfo" + } + }, + "historyProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyInfo" + } + } + }, + "additionalProperties": false + }, + "DetailPropertyInfo": { + "required": [ + "blurryCard", + "displayName", + "format", + "name", + "order", + "specialType", + "unitOfMeasurement" + ], + "type": "object", + "properties": { + "tabInfoId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "blurryCard": { + "type": "boolean" + }, + "specialType": { + "type": "string" + }, + "name": { + "type": "string" + }, + "order": { + "type": "integer", + "format": "int32" + }, + "textStyle": { + "$ref": "#/components/schemas/TextSettings" + }, + "editInfo": { + "$ref": "#/components/schemas/PropertyEditInformation" + }, + "rowNr": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "unitOfMeasurement": { + "type": "string" + }, + "format": { + "type": "string" + }, + "showOnlyInDeveloperMode": { + "type": "boolean", + "nullable": true + }, + "deviceId": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "expanded": { + "type": "boolean", + "nullable": true + }, + "precision": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { }, + "nullable": true, + "readOnly": true + }, + "displayName": { + "type": "string" + } + }, + "additionalProperties": { } + }, + "DetailTabInfo": { + "required": [ + "iconName", + "id", + "order", + "showOnlyInDeveloperMode" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "iconName": { + "type": "string" + }, + "order": { + "type": "integer", + "format": "int32" + }, + "linkedDevice": { + "$ref": "#/components/schemas/LinkedDeviceTab" + }, + "showOnlyInDeveloperMode": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Device": { + "required": [ + "friendlyName", + "id", + "startAutomatically", + "typeName", + "typeNames" + ], + "type": "object", + "properties": { + "typeNames": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true + }, + "id": { + "type": "integer", + "format": "int64" + }, + "typeName": { + "type": "string" + }, + "friendlyName": { + "type": "string" + }, + "startAutomatically": { + "type": "boolean" + } + }, + "additionalProperties": { } + }, + "DeviceLayout": { + "required": [ + "additionalData", + "hash", + "iconName", + "showOnlyInDeveloperMode", + "uniqueName", + "version" + ], + "type": "object", + "properties": { + "uniqueName": { + "type": "string" + }, + "iconName": { + "type": "string" + }, + "typeName": { + "type": "string", + "nullable": true + }, + "typeNames": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "nullable": true + }, + "dashboardDeviceLayout": { + "$ref": "#/components/schemas/DashboardDeviceLayout" + }, + "detailDeviceLayout": { + "$ref": "#/components/schemas/DetailDeviceLayout" + }, + "notificationSetup": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationSetup" + }, + "nullable": true + }, + "version": { + "type": "integer", + "format": "int32" + }, + "showOnlyInDeveloperMode": { + "type": "boolean" + }, + "hash": { + "type": "string" + }, + "additionalData": { + "type": "object", + "additionalProperties": { }, + "readOnly": true + } + }, + "additionalProperties": { } + }, + "DeviceOverview": { + "required": [ + "friendlyName", + "id", + "typeName", + "typeNames" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "friendlyName": { + "type": "string" + }, + "typeName": { + "type": "string" + }, + "typeNames": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "DeviceRenameRequest": { + "required": [ + "id", + "newName" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "newName": { + "type": "string" + } + }, + "additionalProperties": false + }, + "EditParameter": { + "required": [ + "command", + "value" + ], + "type": "object", + "properties": { + "command": { + "$ref": "#/components/schemas/Command" + }, + "value": { }, + "id": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "messageType": { + "$ref": "#/components/schemas/MessageType" + }, + "displayName": { + "type": "string", + "nullable": true + }, + "parameters": { + "type": "array", + "items": { }, + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { }, + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": { } + }, + "FirebaseOptions": { + "required": [ + "apiKey", + "appId", + "messagingSenderId", + "projectId", + "storageBucket" + ], + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "appId": { + "type": "string" + }, + "messagingSenderId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "storageBucket": { + "type": "string" + }, + "iosBundleId": { + "type": "string", + "nullable": true + }, + "authDomain": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "FontStyleSetting": { + "enum": [ + "Normal", + "Italic" + ], + "type": "string", + "x-enumNames": [ + "Normal", + "Italic" + ], + "x-enum-varnames": [ + "Normal", + "Italic" + ] + }, + "FontWeightSetting": { + "enum": [ + "Normal", + "Bold" + ], + "type": "string", + "x-enumNames": [ + "Normal", + "Bold" + ], + "x-enum-varnames": [ + "Normal", + "Bold" + ] + }, + "History": { + "required": [ + "historyRecords", + "propertyName" + ], + "type": "object", + "properties": { + "historyRecords": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryRecord" + } + }, + "propertyName": { + "type": "string" + } + }, + "additionalProperties": false + }, + "HistoryPropertyInfo": { + "required": [ + "brightThemeColor", + "chartType", + "darkThemeColor", + "iconName", + "propertyName", + "unitOfMeasurement", + "xAxisName" + ], + "type": "object", + "properties": { + "propertyName": { + "type": "string" + }, + "xAxisName": { + "type": "string" + }, + "unitOfMeasurement": { + "type": "string" + }, + "iconName": { + "type": "string" + }, + "brightThemeColor": { + "type": "integer", + "format": "int32" + }, + "darkThemeColor": { + "type": "integer", + "format": "int32" + }, + "chartType": { + "type": "string" + } + }, + "additionalProperties": false + }, + "HistoryPropertyState": { + "required": [ + "deviceId", + "enabled", + "propertyName" + ], + "type": "object", + "properties": { + "deviceId": { + "type": "integer", + "format": "int64" + }, + "propertyName": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "HistoryRecord": { + "required": [ + "ts" + ], + "type": "object", + "properties": { + "val": { + "type": "number", + "format": "double", + "nullable": true + }, + "ts": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "IconResponse": { + "required": [ + "icon", + "name" + ], + "type": "object", + "properties": { + "icon": { + "$ref": "#/components/schemas/SvgIcon" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "JsonApiSmarthomeMessage": { + "required": [ + "command", + "id", + "messageType", + "parameters" + ], + "type": "object", + "properties": { + "parameters": { + "type": "array", + "items": { } + }, + "id": { + "type": "integer", + "format": "int64" + }, + "messageType": { + "$ref": "#/components/schemas/MessageType" + }, + "command": { + "$ref": "#/components/schemas/Command" + } + }, + "additionalProperties": false + }, + "LayoutRequest": { + "required": [ + "deviceId", + "iconName", + "typeName" + ], + "type": "object", + "properties": { + "typeName": { + "type": "string" + }, + "iconName": { + "type": "string" + }, + "deviceId": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "LayoutResponse": { + "required": [ + "additionalIcons" + ], + "type": "object", + "properties": { + "layout": { + "$ref": "#/components/schemas/DeviceLayout" + }, + "icon": { + "$ref": "#/components/schemas/IconResponse" + }, + "additionalIcons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IconResponse" + } + } + }, + "additionalProperties": false + }, + "LinkedDeviceTab": { + "required": [ + "deviceIdPropertyName", + "deviceType" + ], + "type": "object", + "properties": { + "deviceIdPropertyName": { + "type": "string" + }, + "deviceType": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MessageType": { + "enum": [ + "Get", + "Update", + "Options", + "Relay" + ], + "type": "string", + "x-enumNames": [ + "Get", + "Update", + "Options", + "Relay" + ], + "x-enum-varnames": [ + "Get", + "Update", + "Options", + "Relay" + ] + }, + "NotificationSetup": { + "required": [ + "global", + "times", + "translatableName", + "uniqueName" + ], + "type": "object", + "properties": { + "uniqueName": { + "type": "string" + }, + "translatableName": { + "type": "string" + }, + "times": { + "type": "integer", + "format": "int32" + }, + "deviceIds": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "nullable": true + }, + "global": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "PropertyEditInformation": { + "required": [ + "editParameter", + "editType", + "messageType" + ], + "type": "object", + "properties": { + "messageType": { + "$ref": "#/components/schemas/MessageType" + }, + "editParameter": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EditParameter" + } + }, + "editType": { + "type": "string" + }, + "display": { + "type": "string", + "nullable": true + }, + "hubMethod": { + "type": "string", + "nullable": true + }, + "valueName": { + "type": "string", + "nullable": true + }, + "activeValue": { + "nullable": true + }, + "dialog": { + "type": "string", + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { }, + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": { } + }, + "RestCreatedDevice": { + "required": [ + "friendlyName", + "id", + "startAutomatically", + "typeName", + "typeNames" + ], + "type": "object", + "properties": { + "typeNames": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true + }, + "id": { + "type": "integer", + "format": "int64" + }, + "typeName": { + "type": "string" + }, + "friendlyName": { + "type": "string" + }, + "startAutomatically": { + "type": "boolean" + } + }, + "additionalProperties": { } + }, + "SetHistoryRequest": { + "required": [ + "enable", + "ids", + "name" + ], + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SvgIcon": { + "required": [ + "hash", + "name", + "typeName" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "data": { + "type": "string", + "format": "byte", + "nullable": true + }, + "typeName": { + "type": "string" + } + }, + "additionalProperties": false + }, + "TextSettings": { + "required": [ + "fontStyle", + "fontWeight" + ], + "type": "object", + "properties": { + "fontSize": { + "type": "number", + "format": "double", + "nullable": true + }, + "fontFamily": { + "type": "string", + "nullable": true + }, + "fontWeight": { + "$ref": "#/components/schemas/FontWeightSetting" + }, + "fontStyle": { + "$ref": "#/components/schemas/FontStyleSetting" + } + }, + "additionalProperties": false + }, + "WindmillSmarthomeMessage": { + "required": [ + "command", + "id", + "idHex", + "messageType", + "parameters" + ], + "type": "object", + "properties": { + "parameters": { + "type": "array", + "items": { } + }, + "id": { + "type": "integer", + "format": "int64" + }, + "idHex": { + "type": "string" + }, + "messageType": { + "$ref": "#/components/schemas/MessageType" + }, + "command": { + "$ref": "#/components/schemas/Command" + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/lib/swagger_jsons/swagger.json.bak b/lib/swagger_jsons/swagger.json.bak new file mode 100644 index 0000000..0ad01c2 --- /dev/null +++ b/lib/swagger_jsons/swagger.json.bak @@ -0,0 +1,1946 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "AppBrokerASP", + "version": "1.0" + }, + "paths": { + "/device/rebuild/{id}": { + "patch": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device": { + "patch": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "onlyNew", + "in": "query", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "put": { + "tags": [ + "Device" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RestCreatedDevice" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RestCreatedDevice" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RestCreatedDevice" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "includeState", + "in": "query", + "required": true, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/states/{id}": { + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/history/{id}": { + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "from", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "propName", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/state/{id}/{name}": { + "get": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/device/state/{id}": { + "post": { + "tags": [ + "Device" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/app/device": { + "get": { + "tags": [ + "Device" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Device" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Device" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Device" + } + } + } + } + } + } + }, + "patch": { + "tags": [ + "Device" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceRenameRequest" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceRenameRequest" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceRenameRequest" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/app/device/overview": { + "get": { + "tags": [ + "Device" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceOverview" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceOverview" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceOverview" + } + } + } + } + } + } + } + }, + "/app/history/settings": { + "get": { + "tags": [ + "History" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyState" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyState" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyState" + } + } + } + } + } + } + } + }, + "/app/history": { + "patch": { + "tags": [ + "History" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetHistoryRequest" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetHistoryRequest" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetHistoryRequest" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "get": { + "tags": [ + "History" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "dt", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/History" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/History" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/History" + } + } + } + } + } + } + } + }, + "/app/history/range": { + "get": { + "tags": [ + "History" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "from", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "propertyName", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/History" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/History" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/History" + } + } + } + } + } + } + }, + "/app/layout/single": { + "get": { + "tags": [ + "Layout" + ], + "parameters": [ + { + "name": "TypeName", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "IconName", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "DeviceId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/LayoutResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/LayoutResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + } + } + } + } + }, + "/app/layout/multi": { + "get": { + "tags": [ + "Layout" + ], + "parameters": [ + { + "name": "request", + "in": "query", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutRequest" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LayoutResponse" + } + } + } + } + } + } + } + }, + "/app/layout": { + "patch": { + "tags": [ + "Layout" + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/notification/sendNotification": { + "get": { + "tags": [ + "Notification" + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/notification/firebaseOptions": { + "get": { + "tags": [ + "Notification" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FirebaseOptions" + } + } + }, + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FirebaseOptions" + } + } + }, + "text/json": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FirebaseOptions" + } + } + } + } + } + } + } + }, + "/painless/time": { + "put": { + "tags": [ + "Painless" + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/security": { + "get": { + "tags": [ + "Security" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/AppCloudConfiguration" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppCloudConfiguration" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/AppCloudConfiguration" + } + } + } + } + } + } + }, + "/app/smarthome": { + "post": { + "tags": [ + "Smarthome" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/JsonSmarthomeMessage" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/JsonSmarthomeMessage" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/JsonSmarthomeMessage" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "get": { + "tags": [ + "Smarthome" + ], + "parameters": [ + { + "name": "deviceId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "AppCloudConfiguration": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer", + "format": "int32" + }, + "key": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Command": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 100 + ], + "type": "integer", + "format": "int32" + }, + "DasboardSpecialType": { + "enum": [ + 0, + 1, + -4, + -3, + -2, + -1 + ], + "type": "integer", + "format": "int32" + }, + "DashbardPropertyInfo": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "order": { + "type": "integer", + "format": "int32" + }, + "textStyle": { + "allOf": [ + { + "$ref": "#/components/schemas/TextStyle" + } + ], + "nullable": true + }, + "editInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/PropertyEditInformation" + } + ], + "nullable": true + }, + "rowNr": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "unitOfMeasurement": { + "type": "string" + }, + "format": { + "type": "string" + }, + "showOnlyInDeveloperMode": { + "type": "boolean", + "nullable": true + }, + "deviceId": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "expanded": { + "type": "boolean", + "nullable": true + }, + "precision": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ] + }, + "nullable": true + }, + "displayName": { + "type": "string" + }, + "specialType": { + "allOf": [ + { + "$ref": "#/components/schemas/DasboardSpecialType" + } + ] + } + }, + "additionalProperties": false + }, + "DashboardDeviceLayout": { + "type": "object", + "properties": { + "dashboardProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DashbardPropertyInfo" + } + } + }, + "additionalProperties": false + }, + "DetailDeviceLayout": { + "type": "object", + "properties": { + "propertyInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DetailPropertyInfo" + } + }, + "tabInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DetailTabInfo" + } + }, + "historyProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryPropertyInfo" + } + } + }, + "additionalProperties": false + }, + "DetailPropertyInfo": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "order": { + "type": "integer", + "format": "int32" + }, + "textStyle": { + "allOf": [ + { + "$ref": "#/components/schemas/TextStyle" + } + ], + "nullable": true + }, + "editInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/PropertyEditInformation" + } + ], + "nullable": true + }, + "rowNr": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "unitOfMeasurement": { + "type": "string" + }, + "format": { + "type": "string" + }, + "showOnlyInDeveloperMode": { + "type": "boolean", + "nullable": true + }, + "deviceId": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "expanded": { + "type": "boolean", + "nullable": true + }, + "precision": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ] + }, + "nullable": true + }, + "displayName": { + "type": "string" + }, + "tabInfoId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "blurryCard": { + "type": "boolean" + }, + "specialType": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DetailTabInfo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "iconName": { + "type": "string" + }, + "order": { + "type": "integer", + "format": "int32" + }, + "linkedDevice": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkedDeviceTab" + } + ], + "nullable": true + }, + "showOnlyInDeveloperMode": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "Device": { + "type": "object", + "properties": { + "typeNames": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true + }, + "subscribers": { + "uniqueItems": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/Subscriber" + }, + "readOnly": true + }, + "id": { + "type": "integer", + "format": "int64" + }, + "typeName": { + "type": "string" + }, + "showInApp": { + "type": "boolean" + }, + "friendlyName": { + "type": "string" + }, + "initialized": { + "type": "boolean" + }, + "startAutomatically": { + "type": "boolean" + }, + "dynamicStateData": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ] + }, + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": false + }, + "DeviceLayout": { + "type": "object", + "properties": { + "uniqueName": { + "type": "string" + }, + "typeName": { + "type": "string", + "nullable": true + }, + "typeNames": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "nullable": true + }, + "dashboardDeviceLayout": { + "allOf": [ + { + "$ref": "#/components/schemas/DashboardDeviceLayout" + } + ], + "nullable": true + }, + "detailDeviceLayout": { + "allOf": [ + { + "$ref": "#/components/schemas/DetailDeviceLayout" + } + ], + "nullable": true + }, + "additionalData": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ], + "nullable": true + } + }, + "version": { + "type": "integer", + "format": "int32" + }, + "showOnlyInDeveloperMode": { + "type": "boolean" + }, + "hash": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DeviceOverview": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "required": true + }, + "typeName": { + "type": "string" + }, + "typeNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "friendlyName": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DeviceRenameRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "newName": { + "type": "string" + } + }, + "additionalProperties": false + }, + "EditParameter": { + "type": "object", + "properties": { + "command": { + "allOf": [ + { + "$ref": "#/components/schemas/Command" + } + ] + }, + "value": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ] + }, + "id": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "messageType": { + "allOf": [ + { + "$ref": "#/components/schemas/MessageType" + } + ], + "nullable": true + }, + "displayName": { + "type": "string", + "nullable": true + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ], + "nullable": true + }, + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ] + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "EditType": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "type": "integer", + "format": "int32" + }, + "FirebaseOptions": { + "type": "object", + "properties": { + "apiKey": { + "type": "string" + }, + "appId": { + "type": "string" + }, + "messagingSenderId": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "storageBucket": { + "type": "string" + }, + "iosBundleId": { + "type": "string", + "nullable": true + }, + "authDomain": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "FontStyle": { + "enum": [ + 0, + 1 + ], + "type": "integer", + "format": "int32" + }, + "FontWeight": { + "enum": [ + 0, + 1 + ], + "type": "integer", + "format": "int32" + }, + "History": { + "type": "object", + "properties": { + "historyRecords": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryRecord" + } + }, + "propertyName": { + "type": "string" + } + }, + "additionalProperties": false + }, + "HistoryPropertyInfo": { + "type": "object", + "properties": { + "propertyName": { + "type": "string" + }, + "xAxisName": { + "type": "string" + }, + "unitOfMeasurement": { + "type": "string" + }, + "iconName": { + "type": "string" + }, + "brightThemeColor": { + "type": "integer", + "format": "int32" + }, + "darkThemeColor": { + "type": "integer", + "format": "int32" + }, + "chartType": { + "type": "string" + } + }, + "additionalProperties": false + }, + "HistoryPropertyState": { + "type": "object", + "properties": { + "deviceId": { + "type": "integer", + "format": "int64" + }, + "propertyName": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "HistoryRecord": { + "type": "object", + "properties": { + "val": { + "type": "number", + "format": "double", + "nullable": true + }, + "ts": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "JsonSmarthomeMessage": { + "type": "object", + "properties": { + "messageType": { + "allOf": [ + { + "$ref": "#/components/schemas/MessageType" + } + ] + }, + "command": { + "allOf": [ + { + "$ref": "#/components/schemas/Command" + } + ] + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ], + "nullable": true + } + }, + "nodeId": { + "type": "integer", + "format": "int32" + }, + "longNodeId": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "LayoutRequest": { + "type": "object", + "properties": { + "typeName": { + "type": "string" + }, + "iconName": { + "type": "string" + }, + "deviceId": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "LayoutResponse": { + "type": "object", + "properties": { + "layout": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceLayout" + } + ], + "nullable": true + }, + "icon": { + "allOf": [ + { + "$ref": "#/components/schemas/SvgIcon" + } + ], + "nullable": true + } + }, + "additionalProperties": false + }, + "LinkedDeviceTab": { + "type": "object", + "properties": { + "deviceIdPropertyName": { + "type": "string" + }, + "deviceType": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MessageType": { + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "integer", + "format": "int32" + }, + "PropertyEditInformation": { + "type": "object", + "properties": { + "editCommand": { + "allOf": [ + { + "$ref": "#/components/schemas/MessageType" + } + ] + }, + "editParameter": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EditParameter" + } + }, + "editType": { + "allOf": [ + { + "$ref": "#/components/schemas/EditType" + } + ] + }, + "display": { + "type": "string", + "nullable": true + }, + "hubMethod": { + "type": "string", + "nullable": true + }, + "valueName": { + "type": "string", + "nullable": true + }, + "activeValue": { + "nullable": true + }, + "dialog": { + "type": "string", + "nullable": true + }, + "extensionData": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ] + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "RestCreatedDevice": { + "type": "object", + "properties": { + "typeNames": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true + }, + "subscribers": { + "uniqueItems": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/Subscriber" + }, + "readOnly": true + }, + "id": { + "type": "integer", + "format": "int64" + }, + "typeName": { + "type": "string" + }, + "showInApp": { + "type": "boolean" + }, + "friendlyName": { + "type": "string" + }, + "initialized": { + "type": "boolean" + }, + "startAutomatically": { + "type": "boolean" + }, + "dynamicStateData": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "string" + } + ] + }, + "nullable": true, + "readOnly": true + }, + "idStr": { + "type": "string" + }, + "fileBased": { + "type": "boolean", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SetHistoryRequest": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Subscriber": { + "type": "object", + "properties": { + "connectionId": { + "type": "string" + }, + "smarthomeClient": { } + }, + "additionalProperties": false + }, + "SvgIcon": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "path": { + "type": "string" + }, + "data": { + "type": "string", + "format": "byte", + "nullable": true + }, + "typeName": { + "type": "string" + } + }, + "additionalProperties": false + }, + "TextStyle": { + "type": "object", + "properties": { + "fontSize": { + "type": "number", + "format": "double", + "nullable": true + }, + "fontFamily": { + "type": "string" + }, + "fontWeight": { + "allOf": [ + { + "$ref": "#/components/schemas/FontWeight" + } + ] + }, + "fontStyle": { + "allOf": [ + { + "$ref": "#/components/schemas/FontStyle" + } + ] + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 853de5e..dbdf376 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,14 +5,20 @@ import FlutterMacOS import Foundation +import device_info_plus +import firebase_core +import firebase_messaging import flutter_local_notifications import package_info_plus import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.yaml b/pubspec.yaml index d24630f..6f0d310 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,15 +7,14 @@ description: Controlling Smarthome devices with AppBroker. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # Read more about versioning at semver.org. -version: 1.2.3+0 +version: 1.3.0+0 environment: - sdk: ">=2.17.0" + sdk: '>=3.9.2 <4.0.0' platforms: android: linux: - macos: web: windows: @@ -23,53 +22,69 @@ dependencies: flutter: sdk: flutter adaptive_theme: ^3.1.1 - collection: ^1.15.0 + collection: ^1.18.0 + device_info_plus: ^12.1.0 + encrypt: ^5.0.3 equatable: ^2.0.3 expandable: ^5.0.1 - flutter_local_notifications: ^15.1.0 + firebase_messaging: ^16.0.2 + flutter_local_notifications: ^19.0.0-dev.7 + flutter_local_notifications_windows: ^1.0.0-dev.4 flutter_staggered_grid_view: ^0.7.0 flutter_svg: ^2.0.7 + format: ^1.5.2 github: ^9.0.0 - http: ^0.13.6 - json_annotation: ^4.6.0 + http: ^1.2.2 + intl: ^0.20.2 + json_annotation: ^4.9.0 + logging: ^1.2.0 multicast_dns: ^0.3.2 new_gradient_app_bar: ^0.2.0 - package_info_plus: ^4.1.0 + oktoast: ^3.3.1 + package_info_plus: ^9.0.0 + path: ^1.9.0 quiver: ^3.0.1+1 shared_preferences_web: ^2.0.3 shared_preferences: ^2.0.13 - # signalr_core: ^1.1.1 - syncfusion_flutter_charts: ^22.2.8 - syncfusion_flutter_core: ^22.2.8 - syncfusion_flutter_gauges: ^22.2.8 - synchronized: ^3.0.0 - tuple: ^2.0.0 + signalr_netcore: ^1.4.4 + + syncfusion_flutter_charts: ^31.1.22 + syncfusion_flutter_core: ^31.1.22 + syncfusion_flutter_gauges: ^31.1.22 + url_launcher_web: ^2.0.20 url_launcher: ^6.0.20 version: ^3.0.2 - oktoast: ^3.3.1 - riverpod: - flutter_riverpod: ^2.4.4 - signalr_netcore: ^1.3.6 - path: ^1.8.3 - intl: ^0.18.1 - logging: ^1.2.0 - url_launcher_web: ^2.0.20 - encrypt: + hooks_riverpod: ^3.0.1 + flutter_hooks: + riverpod_annotation: ^3.0.1 + chopper: ^8.0.1 + riverpod: ^3.0.1 + flutter_riverpod: ^3.0.0 + json_path: ^0.8.0 + visibility_detector: ^0.4.0+2 dev_dependencies: flutter_test: sdk: flutter - visibility_detector: ^0.4.0+2 - archive : ^3.3.1 - build_runner: - flutter_lints: - json_serializable: - crypto: - encrypt: - pointycastle: ^3.6.1 - riverpod_generator: - riverpod_annotation: ^2.2.1 + archive : ^4.0.7 + build_runner: ^2.4.13 + flutter_lints: ^6.0.0 + json_serializable: ^6.9.0 + custom_lint: + riverpod_generator: ^3.0.1 + riverpod_lint: ^3.0.1 + chopper_generator: ^8.0.1 + swagger_dart_code_generator: ^4.0.2 + +#Flutter SDK pinned this to 0.7.6, but other package require at least 0.7.7 +dependency_overrides: + test_api: ^0.7.7 + +global_options: + json_serializable: + runs_before: + - retrofit_generator flutter: uses-material-design: true @@ -77,6 +92,7 @@ flutter: - assets/images/ - assets/vectors/ - .git/HEAD # This file points out the current branch of the project. + - assets/certs/ fonts: - family: Smarthome fonts: diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 68b64e5..c537bb6 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 4f78848..ec8e8d4 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 88b22e5..d121b47 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,10 +3,12 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_core url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + flutter_local_notifications_windows ) set(PLUGIN_BUNDLED_LIBRARIES)