diff --git a/AppIcons.zip b/AppIcons.zip new file mode 100644 index 0000000..ebac8b8 Binary files /dev/null and b/AppIcons.zip differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/100.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..f18dbe2 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/102.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/102.png new file mode 100644 index 0000000..d3a2fd3 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/102.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/1024.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..276ccf6 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/114.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..1826306 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/120.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..42a215f Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/128.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..a7ecab5 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/144.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..9201138 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/152.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..b84a094 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/16.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..d4efac4 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/167.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..1248351 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/172.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..f450d43 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/180.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..f767ef2 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/196.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..5940859 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/20.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..066a91d Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/216.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..c13636e Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/256.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..f180ffe Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/29.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..9423992 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/32.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..34d3c35 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/40.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..081e0c4 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/48.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..beeb227 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/50.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..e05042b Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/512.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..8de9dfc Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/55.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..fa22fe3 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/57.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..c689f59 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/58.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..abd7071 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/60.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..4a80004 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/64.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..45a3eed Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/66.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/66.png new file mode 100644 index 0000000..921c001 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/66.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/72.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..975cf75 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/76.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..663def2 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/80.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..b3e1e96 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/87.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..cca1a48 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/88.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..0325b5b Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/92.png b/AppIcons/Assets.xcassets/AppIcon.appiconset/92.png new file mode 100644 index 0000000..6e95013 Binary files /dev/null and b/AppIcons/Assets.xcassets/AppIcon.appiconset/92.png differ diff --git a/AppIcons/Assets.xcassets/AppIcon.appiconset/Contents.json b/AppIcons/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..ffab254 --- /dev/null +++ b/AppIcons/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"102.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"41mm","scale":"2x","size":"45x45","expected-size":"102","role":"appLauncher"},{"idiom":"watch","filename":"92.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"41mm","scale":"2x","size":"46x46","expected-size":"92","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"idiom":"watch","filename":"66.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"45mm","scale":"2x","size":"33x33","expected-size":"66","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} \ No newline at end of file diff --git a/AppIcons/android/mipmap-hdpi/ic_cloud_provisioning.png b/AppIcons/android/mipmap-hdpi/ic_cloud_provisioning.png new file mode 100644 index 0000000..975cf75 Binary files /dev/null and b/AppIcons/android/mipmap-hdpi/ic_cloud_provisioning.png differ diff --git a/AppIcons/android/mipmap-mdpi/ic_cloud_provisioning.png b/AppIcons/android/mipmap-mdpi/ic_cloud_provisioning.png new file mode 100644 index 0000000..beeb227 Binary files /dev/null and b/AppIcons/android/mipmap-mdpi/ic_cloud_provisioning.png differ diff --git a/AppIcons/android/mipmap-xhdpi/ic_cloud_provisioning.png b/AppIcons/android/mipmap-xhdpi/ic_cloud_provisioning.png new file mode 100644 index 0000000..cec3522 Binary files /dev/null and b/AppIcons/android/mipmap-xhdpi/ic_cloud_provisioning.png differ diff --git a/AppIcons/android/mipmap-xxhdpi/ic_cloud_provisioning.png b/AppIcons/android/mipmap-xxhdpi/ic_cloud_provisioning.png new file mode 100644 index 0000000..9201138 Binary files /dev/null and b/AppIcons/android/mipmap-xxhdpi/ic_cloud_provisioning.png differ diff --git a/AppIcons/android/mipmap-xxxhdpi/ic_cloud_provisioning.png b/AppIcons/android/mipmap-xxxhdpi/ic_cloud_provisioning.png new file mode 100644 index 0000000..b45bb29 Binary files /dev/null and b/AppIcons/android/mipmap-xxxhdpi/ic_cloud_provisioning.png differ diff --git a/AppIcons/appstore.png b/AppIcons/appstore.png new file mode 100644 index 0000000..276ccf6 Binary files /dev/null and b/AppIcons/appstore.png differ diff --git a/AppIcons/playstore.png b/AppIcons/playstore.png new file mode 100644 index 0000000..8de9dfc Binary files /dev/null and b/AppIcons/playstore.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4..975cf75 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b7..beeb227 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d4391..cec3522 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d..9201138 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372e..b45bb29 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/assets/device/PFC200.jpg b/assets/device/PFC200.jpg new file mode 100644 index 0000000..20c6b81 Binary files /dev/null and b/assets/device/PFC200.jpg differ diff --git a/config/credentials.json b/config/credentials.json index 405c414..81fc9db 100644 --- a/config/credentials.json +++ b/config/credentials.json @@ -1,5 +1,5 @@ { - "username": "...", - "password": "...", - "address": "..." + "username": "admin", + "password": "nE2Bkf9dwjLASq4VykUNjw7Rivve8W7elowfqYWhuFXaDtLT", + "address": "hfu-semester-projekt-oobp.azurewebsites.net" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 1538145..3bfd56e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_provisioning_for_iot/screens/main_page.dart'; + +import 'screens/main_screen.dart'; void main() { - runApp(const MainPage()); + runApp(const MainScreen()); } - diff --git a/lib/objects/bluetooth_device_entry.dart b/lib/objects/bluetooth_device_entry.dart new file mode 100644 index 0000000..3a3f11e --- /dev/null +++ b/lib/objects/bluetooth_device_entry.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; +import 'package:flutter_provisioning_for_iot/objects/bluetooth_object.dart'; +import 'package:flutter_provisioning_for_iot/screens/bluetooth_device_settings.dart'; + +class BluetoothDeviceEntry extends ListTile { + BluetoothDeviceEntry({ + super.key, + required BuildContext context, + required BluetoothDeviceSettings settingsPage, + required BluetoothObject bluetoothObject, + required BluetoothDevice device, + GestureTapCallback? onTap, + GestureLongPressCallback? onLongPress, + bool enabled = true, + }) : super( + onTap: onTap, + onLongPress: onLongPress, + enabled: enabled, + leading: Icon(bluetoothObject.isConnected ? Icons.bluetooth_connected : Icons.bluetooth), + title: Text(bluetoothObject.name), + subtitle: Text(bluetoothObject.address.toString()), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + device.isBonded ? const Icon(Icons.link) : const SizedBox(width: 0, height: 0), + Container( + margin: const EdgeInsets.all(8.0), + child: DefaultTextStyle( + style: _computeTextStyle(bluetoothObject.rssi), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(bluetoothObject.rssi.toString()), + const Text('dBm'), + ], + ), + ), + ), + IconButton( + onPressed: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => settingsPage)); //const BluetoothDeviceEntry())); + }, + icon: const Icon(Icons.settings)), + ], + ), + ); + + static TextStyle _computeTextStyle(int rssi) { + /**/ if (rssi >= -35) { + return TextStyle(color: Colors.greenAccent[700]); + } else if (rssi >= -45) { + return TextStyle(color: Color.lerp(Colors.greenAccent[700], Colors.lightGreen, -(rssi + 35) / 10)); + } else if (rssi >= -55) { + return TextStyle(color: Color.lerp(Colors.lightGreen, Colors.lime[600], -(rssi + 45) / 10)); + } else if (rssi >= -65) { + return TextStyle(color: Color.lerp(Colors.lime[600], Colors.amber, -(rssi + 55) / 10)); + } else if (rssi >= -75) { + return TextStyle(color: Color.lerp(Colors.amber, Colors.deepOrangeAccent, -(rssi + 65) / 10)); + } else if (rssi >= -85) { + return TextStyle(color: Color.lerp(Colors.deepOrangeAccent, Colors.redAccent, -(rssi + 75) / 10)); + } else { + return const TextStyle(color: Colors.redAccent); + } + } +} diff --git a/lib/objects/bluetooth_object.dart b/lib/objects/bluetooth_object.dart new file mode 100644 index 0000000..5e12759 --- /dev/null +++ b/lib/objects/bluetooth_object.dart @@ -0,0 +1,263 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../objects/cloud_service_api.dart'; + +class BluetoothObject { + late int _rssi = -1; + late BluetoothDevice _device; + String _address = ""; + String _name = ""; + + String id = ""; + String _primaryThumbprint = ""; + String _secondaryThumbprint = ""; + + late BluetoothConnection? _connection; + late Stream _connectionStream; + late StreamSubscription _connectionStreamSubscription; + + CloudServiceAPI _cloudServiceAPI = CloudServiceAPI(); + + late Uint8List _messageBufferBits; + late String _messageBufferChars = ""; + + bool _isDisconnecting = false; + + String get primaryThumbprint { + return _primaryThumbprint; + } + + String get secondaryThumbprint { + return _secondaryThumbprint; + } + + int get rssi { + return _rssi; + } + + String get name { + return _name; + } + + String get address { + return _address; + } + + BluetoothDevice get device { + return _device; + } + + BluetoothConnection? get connection { + return _connection; + } + + bool get isConnected { + return (_connection == null ? false : true); + } + + BluetoothObject(BluetoothDiscoveryResult result) { + _rssi = result.rssi; + _device = result.device; + _address = _device.address; + _name = _device.name ?? "device.name.UNKNOWN"; + _connection = null; + } + + Future bondDevice() async { + try { + bool bonded = false; + if (_device.isBonded) { + debugPrint('Unbonding from $_address...'); + await FlutterBluetoothSerial.instance.removeDeviceBondWithAddress(_address); + debugPrint('Unbonding from $_address has succed'); + } else { + debugPrint('Bonding with $_address...'); + bonded = (await FlutterBluetoothSerial.instance.bondDeviceAtAddress(_address))!; + debugPrint('Bonding with $_address has ${bonded ? 'succed' : 'failed'}.'); + } + /*setState(() { + _discoveryResults[_discoveryResults.indexOf( + result)] = + BluetoothDiscoveryResult( + device: BluetoothDevice( + name: device.name ?? '', + address: address, + type: device.type, + bondState: bonded + ? BluetoothBondState.bonded + : BluetoothBondState.none, + ), + rssi: result.rssi); + });*/ + } catch (ex) { + /* + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text( + 'Error occured while bonding'), + content: Text(ex.toString()), + actions: [ + TextButton( + child: const Text("Close"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + );*/ + } + } + + Future disconnectDevice() async { + _isDisconnecting = true; + if (_connection != null) { + await _connection!.finish(); + _connection = null; + } + _isDisconnecting = false; + } + + Future connectDevice() async { + if (isConnected) { + await _connection!.finish(); + _connection = null; + } + + _connection = await BluetoothConnection.toAddress(_address); + debugPrint("Connected to the device"); + + _connectionStream = _connection!.input!; + _connectionStreamSubscription = _connectionStream.listen(_connectionOnListen); + _connectionStreamSubscription.onDone(_connectionOnDone); + } + + void _connectionOnDone() { + if (_isDisconnecting) { + debugPrint("Disconnecting locally!"); + } else { + debugPrint("Disconnected remotely!"); + } + } + + Future _connectionOnListen(Uint8List data) async { + final String dataDecoded = const AsciiDecoder().convert(data); + + debugPrint("received: $data"); + debugPrint("received decoded: $dataDecoded"); + + _messageBufferChars += dataDecoded; + + if (data[data.length - 1] == 10) { + debugPrint("received buffer: $_messageBufferChars"); + int spaceIndex = _messageBufferChars.indexOf(" "); + String firstParameter = ""; + String secondParameter = ""; + try { + firstParameter = _messageBufferChars.substring(0, spaceIndex); + secondParameter = _messageBufferChars.substring(spaceIndex + 1, _messageBufferChars.length - 2); + } catch (ex) { + debugPrint(ex.toString()); + } + debugPrint("we still go on!"); + _messageBufferChars = ""; + debugPrint("first: $firstParameter"); + debugPrint("second: $secondParameter"); + if (firstParameter == "fingerprint") { + debugPrint("received final buffer: $secondParameter"); + _primaryThumbprint = secondParameter; + debugPrint("{ _secondaryThumbprint: $_secondaryThumbprint }"); + debugPrint("{ id: $id, _primaryThumbprint: $_primaryThumbprint, _secondaryThumbprint: $_secondaryThumbprint }"); + await _registerDevice(); + } + } + + /* + // Allocate buffer for parsed data + int backspacesCounter = 0; + for (var byte in data) { + if (byte == 8 || byte == 127) { + backspacesCounter++; + } + } + Uint8List buffer = Uint8List(data.length - backspacesCounter); + int bufferIndex = buffer.length; + + // Apply backspace control character + backspacesCounter = 0; + for (int i = data.length - 1; i >= 0; i--) { + if (data[i] == 8 || data[i] == 127) { + backspacesCounter++; + } else { + if (backspacesCounter > 0) { + backspacesCounter--; + } else { + buffer[--bufferIndex] = data[i]; + } + } + } + + // Create message if there is new line character + String dataString = String.fromCharCodes(buffer); + int index = buffer.indexOf(13); + if (~index != 0) { + _messageBuffer = dataString.substring(index); + } else { + _messageBuffer = (backspacesCounter > 0 + ? _messageBuffer.substring(0, _messageBuffer.length - backspacesCounter) + : _messageBuffer + dataString); + } + */ + } + + Future sendData(String output) async { + if (_connection == null) return; + bool nameAvailable = await _cloudServiceAPI.checkNameAvailability(output); + // String output = "0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ";// !§%&()=?#!?"; + // String output = "myDevice123";// !§%&()=?#!?"; + if (!nameAvailable) return; + id = output; + _connection!.output.add(Uint8List.fromList(const AsciiEncoder().convert("$output \r\n"))); + await _connection!.output.allSent; + debugPrint("sent: $output"); + } + + Future _registerDevice() async { + bool registered = false; + registered = await _cloudServiceAPI.createDevice(id, _primaryThumbprint, ""); + + String statusText = + registered ? "das Gerät wurde erfolgreich registriert" : "das Gerät konnte nicht registriert werden"; + Fluttertoast.showToast( + msg: statusText, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 2, + backgroundColor: Colors.grey[200], + textColor: Colors.black, + fontSize: 16.0); + } +} + +/*setState(() { + _discoveryResults[_discoveryResults.indexOf( + result)] = + BluetoothDiscoveryResult( + device: BluetoothDevice( + name: device.name ?? '', + address: address, + type: device.type, + isConnected: connected, + ), + rssi: result.rssi); + }); + */ diff --git a/lib/objects/cloud_service_api.dart b/lib/objects/cloud_service_api.dart index d702888..d065440 100644 --- a/lib/objects/cloud_service_api.dart +++ b/lib/objects/cloud_service_api.dart @@ -1,32 +1,117 @@ import 'dart:convert'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:http/http.dart'; import 'dart:developer'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:http/http.dart'; + +import '../schemas/device_info_model.dart'; +import '../schemas/device_model.dart'; + Future> readJson() async { - final data = await rootBundle.loadString('config/credentials.json'); - var data_ = json.decode(data) as Map; - log("loaded json input $data_"); - return data_; + final String data = await rootBundle.loadString('config/credentials.json'); + final Map dataMap = json.decode(data); + log("loaded json input $dataMap"); + return dataMap; } -/*Future checkNameAvailability(String input) async { +class CloudServiceAPI { + static late final Map credentials; + static late String _basicAuth; + static late String _address; + static late String _username; + static late String _password; + static late Map _headers; + static bool _initState = false; + + CloudServiceAPI() { + if (!_initState) { + loadConfig(); + _initState = true; + } + } + + Future loadConfig() async { + credentials = await readJson(); + _username = credentials['username']; + _password = credentials['password']; + _address = credentials['address']; + _basicAuth = 'Basic ${base64.encode(utf8.encode('$_username:$_password'))}'; + _headers = { + 'authorization': _basicAuth, + 'content-type': 'application/json', + 'accept': 'application/json', + }; + log("init Config $_headers"); + } + + Future reloadConfig() async { + _basicAuth = 'Basic ${base64.encode(utf8.encode('$_username:$_password'))}'; + _headers = { + 'authorization': _basicAuth, + 'content-type': 'application/json', + 'accept': 'application/json', + }; + } + + Future> getDevices() async { + Uri url = Uri.https(_address, '/api/devices'); + Response responnse = await get(url, headers: _headers); + dynamic jsonObject = json.decode(responnse.body); + Iterable remoteObjectsIterable = Iterable.castFrom(jsonObject); + Iterable remoteObjectsMap = remoteObjectsIterable.map((data) => DeviceModel.fromJson(data)); + List remoteObjectsList = remoteObjectsMap.toList(); + return remoteObjectsList; + } + + Future getDeviceInfo(String deviceID) async { + Uri url = Uri.https(_address, '/api/devices/$deviceID'); + Response response = await get(url, headers: _headers); + dynamic jsonObject = json.decode(response.body); + DeviceInfoModel deviceInfoModel = DeviceInfoModel.fromJson(jsonObject); + return deviceInfoModel; + } + + Future> getInformation() async { + Uri url = Uri.https(_address, '/api/app'); + Response response = await get(url, headers: _headers); + return json.decode(response.body) as Map; + } + + Future createDevice(String id, String primaryThumbprint, String secondaryThumbprint) async { + Uri url = Uri.https(_address, '/api/devices'); + Response response = await post(url, + headers: _headers, + body: jsonEncode({ + 'id': id, + 'primaryThumbprint': primaryThumbprint, + 'secondaryThumbprint': secondaryThumbprint + })); + if (response.statusCode == 200) { + return true; + } + debugPrint('Error createDevice: ${response.statusCode.toString()}'); + return false; + } + + Future checkNameAvailability(String inputID) async { // await cloudServiceAPI.loadConfig(); - List devices = await cloudServiceAPI.getDevices(); - for (Map selected in devices) { - if (selected["id"] == input) { - await showNameAvailabilityStatus(true); - return; + List devices = await getDevices(); + for (DeviceModel selected in devices) { + if (selected.id == inputID) { + debugPrint("ID 1: ${selected.id}"); + debugPrint("ID 2: $inputID"); + await showNameAvailabilityStatus(false); + return false; } } - await showNameAvailabilityStatus(false); + await showNameAvailabilityStatus(true); + return true; } Future showNameAvailabilityStatus(bool status) async { - String statusText = status - ? "die eingegebene ID ist verfügbar" - : "die eingegebene ID ist nicht verfügbar"; + String statusText = status ? "die eingegebene ID ist verfügbar" : "die eingegebene ID ist nicht verfügbar"; Fluttertoast.showToast( msg: statusText, toastLength: Toast.LENGTH_SHORT, @@ -35,97 +120,32 @@ Future> readJson() async { backgroundColor: Colors.grey[200], textColor: Colors.black, fontSize: 16.0); - }*/ - -class CloudServiceAPI { - static late final Map credentials; - static late Future loadJson; - static late String basicAuth; - static late String address; - static late String username; - static late String password; - static late Map headers; - static bool initState = false; - - CloudServiceAPI(){ - if(!initState) { - loadJson = loadConfig(); - initState = true; - } } - Future loadConfig() async{ - credentials = await readJson(); - username = credentials['username']; - password = credentials['password']; - address = credentials['address']; - basicAuth = 'Basic ${base64.encode(utf8.encode('$username:$password'))}'; - headers = { - 'authorization': basicAuth, - 'content-type': 'application/json', - 'accept': 'application/json', - }; - log("init Config $headers"); + String get address { + return _address; } - void reloadConfig() { - basicAuth = 'Basic ${base64.encode(utf8.encode('$username:$password'))}'; - headers = { - 'authorization': basicAuth, - 'content-type': 'application/json', - 'accept': 'application/json', - }; + + String get username { + return _username; } - Future getDevices() async { - Uri url = Uri.https(address, '/api/devices'); - Response r = await get(url, headers: headers); - return json.decode(r.body) as List; + + String get password { + return _password; } - Future> getDeviceInfo(deviceID) async { - Uri url = Uri.https(address, '/api/devices/$deviceID'); - Response r = await get(url, headers: headers); - return json.decode(r.body) as Map; - } - Future> getInformation() async { - Uri url = Uri.https(address, '/api/app'); - Response r = await get(url, headers: headers); - return json.decode(r.body) as Map; - } - Future createDevice(id, primaryThumbprint, secondaryThumbprint) async{ - Uri url = Uri.https(address, '/api/devices'); - Response r = await post( - url, - headers: headers, - body: jsonEncode({ - 'id': id, - 'primaryThumbprint' : primaryThumbprint, - 'secondaryThumbprint' : secondaryThumbprint - }) - ); - if (r.statusCode == 200){ - return true; - } - debugPrint('Error createDevice: ${r.statusCode.toString()}'); - return false; - } - String getAddress(){ - return address; - } - String getUsername(){ - return username; - } - String getPassword(){ - return password; - } - void setAddress(String input){ - address = input; + + set address(String input) { + _address = input; reloadConfig(); } - void setUsername(String input){ - username = input; + + set username(String input) { + _username = input; reloadConfig(); } - void setPassword(String input){ - password = input; + + set password(String input) { + _password = input; reloadConfig(); } -} \ No newline at end of file +} diff --git a/lib/objects/create_material_color.dart b/lib/objects/create_material_color.dart index 36f0e29..b659897 100644 --- a/lib/objects/create_material_color.dart +++ b/lib/objects/create_material_color.dart @@ -25,4 +25,3 @@ class CustomColor { return MaterialColor(color.value, swatch); } } - diff --git a/lib/schemas/device_info_model.dart b/lib/schemas/device_info_model.dart new file mode 100644 index 0000000..40c5831 --- /dev/null +++ b/lib/schemas/device_info_model.dart @@ -0,0 +1,45 @@ + + +class DeviceInfoModel { + final String id; + final String endpoint; + final String status; + final String connectionState; + final String lastActivityTime; + final String primaryThumbprint; + final String secondaryThumbprint; + + const DeviceInfoModel({ + required this.id, + required this.endpoint, + required this.status, + required this.connectionState, + required this.lastActivityTime, + required this.primaryThumbprint, + required this.secondaryThumbprint, + }); + + factory DeviceInfoModel.fromJson(Map parsedJson) { + return DeviceInfoModel( + id: parsedJson['id'].toString(), + endpoint: parsedJson['endpoint'], + status: parsedJson['status'], + connectionState: parsedJson['connectionState'], + lastActivityTime: parsedJson['lastActivityTime'], + primaryThumbprint: parsedJson['primaryThumbprint'], + secondaryThumbprint: parsedJson['secondaryThumbprint']); + } + + @override + String toString() { + return "{" + "id: $id, " + "endpoint: $endpoint, " + "status: $status, " + "connectionState: $connectionState, " + "lastActivityTime: $lastActivityTime, " + "primaryThumbprint: $primaryThumbprint, " + "secondaryThumbprint: $secondaryThumbprint" + "}"; + } +} diff --git a/lib/schemas/device_model.dart b/lib/schemas/device_model.dart new file mode 100644 index 0000000..64ae955 --- /dev/null +++ b/lib/schemas/device_model.dart @@ -0,0 +1,36 @@ + + +class DeviceModel { + final String id; + final String endpoint; + final String status; + final String connectionState; + final String lastActivityTime; + + const DeviceModel( + {required this.id, + required this.endpoint, + required this.status, + required this.connectionState, + required this.lastActivityTime}); + + factory DeviceModel.fromJson(Map parsedJson) { + return DeviceModel( + id: parsedJson['id'].toString(), + endpoint: parsedJson['endpoint'], + status: parsedJson['status'], + connectionState: parsedJson['connectionState'], + lastActivityTime: parsedJson['lastActivityTime']); + } + + @override + String toString() { + return "{" + "id: $id, " + "endpoint: $endpoint, " + "status: $status, " + "connectionState: $connectionState, " + "lastActivityTime: $lastActivityTime, " + "}"; + } +} diff --git a/lib/screens/bluetooth_device_list_entry.dart b/lib/screens/bluetooth_device_list_entry.dart deleted file mode 100644 index 7135d44..0000000 --- a/lib/screens/bluetooth_device_list_entry.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; - -class BluetoothDeviceListEntry extends ListTile { - BluetoothDeviceListEntry({super.key, - required BluetoothDevice device, - int? rssi, - GestureTapCallback? onTap, - GestureLongPressCallback? onLongPress, - bool enabled = true, - }) : super( - onTap: onTap, - onLongPress: onLongPress, - enabled: enabled, - leading: Icon(device.isConnected ? Icons.bluetooth_connected : Icons.bluetooth), - title: Text(device.name ?? "~ UNKNOWN_DEVICE ~"), - subtitle: Text(device.address.toString()), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - rssi != null - ? Container( - margin: const EdgeInsets.all(8.0), - child: DefaultTextStyle( - style: _computeTextStyle(rssi), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(rssi.toString()), - const Text('dBm'), - ], - ), - ), - ) - : const SizedBox(width: 0, height: 0), - device.isBonded - ? const Icon(Icons.link) - : const SizedBox(width: 0, height: 0), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.settings) - ), - ], - ), - ); - - static TextStyle _computeTextStyle(int rssi) { - /**/ if (rssi >= -35) { - return TextStyle(color: Colors.greenAccent[700]); - } else if (rssi >= -45) { - return TextStyle( - color: Color.lerp( - Colors.greenAccent[700], Colors.lightGreen, -(rssi + 35) / 10)); - } else if (rssi >= -55) { - return TextStyle( - color: Color.lerp( - Colors.lightGreen, Colors.lime[600], -(rssi + 45) / 10)); - } else if (rssi >= -65) { - return TextStyle( - color: Color.lerp(Colors.lime[600], Colors.amber, -(rssi + 55) / 10)); - } else if (rssi >= -75) { - return TextStyle( - color: Color.lerp( - Colors.amber, Colors.deepOrangeAccent, -(rssi + 65) / 10)); - } else if (rssi >= -85) { - return TextStyle( - color: Color.lerp( - Colors.deepOrangeAccent, Colors.redAccent, -(rssi + 75) / 10)); - } else { - return const TextStyle(color: Colors.redAccent); - } - } -} \ No newline at end of file diff --git a/lib/screens/bluetooth_device_settings.dart b/lib/screens/bluetooth_device_settings.dart index fb738c2..1c0ba49 100644 --- a/lib/screens/bluetooth_device_settings.dart +++ b/lib/screens/bluetooth_device_settings.dart @@ -1,107 +1,121 @@ -import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:flutter/material.dart'; -class BluetoothDeviceSettings extends StatefulWidget { - final BluetoothDevice device; +import '../objects/bluetooth_object.dart'; +import '../widgets/single_section.dart'; - const BluetoothDeviceSettings({super.key, required this.device}); +class BluetoothDeviceSettings extends StatefulWidget { + final BluetoothObject bluetoothObject; + + const BluetoothDeviceSettings({super.key, required this.bluetoothObject}); @override - State createState() => _BluetoothDeviceSettings(); + State createState() => BluetoothDeviceSettingsState(); } -class _BluetoothDeviceSettings extends State { +class BluetoothDeviceSettingsState extends State { + late BluetoothObject _bluetoothObject; + + String _messageBuffer = ""; + + @override + void initState() { + super.initState(); + _bluetoothObject = widget.bluetoothObject; + } + + String _textInput = "0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + String _textOuput = ""; + + ButtonStyle buttonStyle = ElevatedButton.styleFrom( + foregroundColor: Colors.black, + backgroundColor: const Color(0xFFFDE100), // Text Color (Foreground color) + ); + @override Widget build(BuildContext context) { - debugPrint(widget.device.address); return Scaffold( appBar: AppBar( - title: const Text("Bluetooth Devices"), + title: const Text("Device Settings"), ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, + body: Center( + child: ListView(children: [ + Column(children: [ + SingleSection( + title: "Device Information", children: [ - Table( - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - TableRow( - decoration: const BoxDecoration( - border: Border( - bottom: - BorderSide(width: 1.5, color: Colors.grey), - ), - ), - children: [ - const TableCell(child: Text("Address")), - TableCell( - child: Container( - alignment: Alignment.centerLeft, - height: 50, - child: Text( - widget.device.address - ) - ) - ), - ]), - TableRow( - decoration: const BoxDecoration( - border: Border( - bottom: - BorderSide(width: 1.5, color: Colors.grey), - ), - ), - children: [ - TableCell( - child: Container( - alignment: Alignment.centerLeft, - height: 50, - child: const Text( - "Name" - ) - ) - ), - TableCell( - child: Container( - alignment: Alignment.centerLeft, - height: 50, - child: Text( - widget.device.name.toString() - ) - ) - ), - ]), - TableRow( - decoration: const BoxDecoration( - border: Border( - bottom: - BorderSide(width: 1.5, color: Colors.grey), - ), - ), - children: [ - TableCell( - child: Container( - alignment: Alignment.centerLeft, - height: 50, - child: const Text( - "Connected" - ) - ) - ), - TableCell( - child: Container( - alignment: Alignment.centerLeft, - height: 50, - child: Text( - widget.device.isConnected.toString() - ) - ) - ), - ]), - ], + ListTile( + title: const Text("Device Name"), + subtitle: Text(_bluetoothObject.name), ), - ]))); + ListTile( + title: const Text("Device Adress"), + subtitle: Text(_bluetoothObject.address), + ), + ListTile( + title: const Text("Device Connection State"), + subtitle: + Text(_bluetoothObject.isConnected ? "ConnectionState.DISCONECTED" : "ConnectionState.CONNECED"), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + // const EdgeInsets defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0); + Expanded( + child: ElevatedButton( + onPressed: () async { + if (!_bluetoothObject.isConnected) { + await _bluetoothObject.connectDevice(); + } + }, + style: buttonStyle, + child: const Text("Connect"), + )), + const SizedBox(width: 16.0), + Expanded( + child: ElevatedButton( + onPressed: () async { + if (_bluetoothObject.isConnected) { + await _bluetoothObject.disconnectDevice(); + } + }, + style: buttonStyle, + child: const Text("Disconnect"), + )) + ])), + const Divider(), + ElevatedButton( + onPressed: () async { + _bluetoothObject.sendData(_textInput); + }, + style: buttonStyle, + child: const Text("Send Data"), + ), + ElevatedButton( + onPressed: () async { + setState(() { + _textOuput = ""; + }); + }, + style: buttonStyle, + child: const Text("Clear Output"), + ), + Text(_textOuput), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + child: TextField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Enter the device name', + ), + onChanged: (String newValue) { + _textInput = newValue; + }, + ), + ), + ]), + ]), + )); } } diff --git a/lib/screens/bluetooth_screen.dart b/lib/screens/bluetooth_screen.dart index 54a0dd9..449556f 100644 --- a/lib/screens/bluetooth_screen.dart +++ b/lib/screens/bluetooth_screen.dart @@ -1,16 +1,15 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_provisioning_for_iot/objects/cloud_service_api.dart'; -import 'package:flutter_provisioning_for_iot/screens/bluetooth_device_list_entry.dart'; import 'package:app_settings/app_settings.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:permission_handler/permission_handler.dart'; +import '../objects/bluetooth_device_entry.dart'; +import '../objects/bluetooth_object.dart'; +import '../screens/bluetooth_device_settings.dart'; +import '../widgets/custom_list_tile.dart'; +import '../widgets/single_section.dart'; class BluetoothScreen extends StatefulWidget { final bool start = true; @@ -23,36 +22,34 @@ class BluetoothScreen extends StatefulWidget { // debugPrint("r: ${r.device.name} ${r.device.address} ${r.rssi}"); class _BluetoothScreen extends State { + final List _discoveryResults = []; - CloudServiceAPI cloudServiceAPI = CloudServiceAPI(); - //BluetoothManager bluetoothManager = BluetoothManager(); + late BluetoothObject? _activeObject; - late Stream _stream; - late StreamSubscription _streamSubscription; - late Stream _connectionStream; - late StreamSubscription _connectionStreamSubscription; - BluetoothDevice? _bluetoothDevice; - BluetoothConnection? _bluetoothConnection; - final List _discoveryResults = - List.empty(growable: true); - String _messageBuffer = ""; - bool isDiscovering = false; - bool isConnecting = false; - bool isDisconnecting = false; - bool isConnected = false; - String textInput = "0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - String textOuput = ""; + late Stream? _stream; + late StreamSubscription? _streamSubscription; + + bool _isDiscovering = false; + bool _enabledPermissions = false; + bool _enabledBluetooth = false; ButtonStyle buttonStyle = ElevatedButton.styleFrom( foregroundColor: Colors.black, backgroundColor: const Color(0xFFFDE100), // Text Color (Foreground color) ); + bool get _hasActiveObject { + return _activeObject == null ? false : true; + } + @override void initState() { + _enablePermissions(); + _enableBluetooth(); super.initState(); - isDiscovering = widget.start; - if (isDiscovering) { + _activeObject = null; + _streamSubscription = null; + if (widget.start) { _startDiscovery(); } } @@ -60,522 +57,216 @@ class _BluetoothScreen extends State { @override void dispose() { // Avoid memory leak (`setState` after dispose) and cancel discovery - _streamSubscription.cancel(); - if (isConnected) { - isDisconnecting = true; - _bluetoothConnection?.dispose(); - _bluetoothConnection = null; - } + _streamSubscription?.cancel(); + _activeObject?.disconnectDevice(); super.dispose(); + debugPrint("called dispose"); } - Future _disconnectDevice(BluetoothDevice device) async { - if (_bluetoothConnection != null) { - await _bluetoothConnection!.finish(); - _bluetoothConnection = null; - } - } - - Future _connectDevice(BluetoothDevice device) async { - String address = device.address; - if (_bluetoothConnection != null) { - await _bluetoothConnection!.finish(); - _bluetoothConnection = null; - } - _bluetoothConnection = await BluetoothConnection.toAddress(address); - debugPrint("Connected to the device"); + Future _enablePermissions() async { + debugPrint("Test"); + PermissionStatus bluetoothScan = await Permission.bluetoothScan.request(); + PermissionStatus bluetoothConnect = await Permission.bluetoothConnect.request(); + bool granted = bluetoothScan.isGranted && bluetoothConnect.isGranted; setState(() { - isConnecting = false; - isDisconnecting = false; + _enabledPermissions = granted; }); - - _connectionStream = _bluetoothConnection!.input!; - _connectionStreamSubscription = _connectionStream.listen(_connectionOnListen); - _connectionStreamSubscription.onDone(_connectionOnDone); } - void _connectionOnDone() { - /* - // Example: Detect which side closed the connection - // There should be `isDisconnecting` flag to show are we are (locally) - // in middle of disconnecting process, should be set before calling - // `dispose`, `finish` or `close`, which all causes to disconnect. - // If we except the disconnection, `onDone` should be fired as result. - // If we didn't except this (no flag set), it means closing by remote. - */ - if (isDisconnecting) { - debugPrint("Disconnecting locally!"); - } else { - debugPrint("Disconnected remotely!"); + Future _enableBluetooth() async { + if (!_enabledPermissions) { + return; } - if (mounted) { - setState(() {}); - } - } - - void _connectionOnListen(Uint8List data) { - debugPrint("received: $data"); - debugPrint("received decoded: ${const Utf8Decoder().convert(data)}"); + bool? enabled = await FlutterBluetoothSerial.instance.requestEnable(); + enabled ??= false; setState(() { - textOuput += const Utf8Decoder().convert(data); + _enabledBluetooth = enabled!; }); - // Allocate buffer for parsed data - int backspacesCounter = 0; - for (var byte in data) { - if (byte == 8 || byte == 127) { - backspacesCounter++; - } - } - Uint8List buffer = Uint8List(data.length - backspacesCounter); - int bufferIndex = buffer.length; - - // Apply backspace control character - backspacesCounter = 0; - for (int i = data.length - 1; i >= 0; i--) { - if (data[i] == 8 || data[i] == 127) { - backspacesCounter++; - } else { - if (backspacesCounter > 0) { - backspacesCounter--; - } else { - buffer[--bufferIndex] = data[i]; - } - } - } - - // Create message if there is new line character - String dataString = String.fromCharCodes(buffer); - int index = buffer.indexOf(13); - if (~index != 0) { - setState(() { - _messageBuffer = dataString.substring(index); - }); - } else { - _messageBuffer = (backspacesCounter > 0 - ? _messageBuffer.substring( - 0, _messageBuffer.length - backspacesCounter) - : _messageBuffer + dataString); - } } Future _startDiscovery() async { + if (!_enabledPermissions) { + return; + } + setState(() => _isDiscovering = true); _stream = FlutterBluetoothSerial.instance.startDiscovery(); - _streamSubscription = _stream.listen(_discoveryOnListen); - _streamSubscription.onDone(_discoveryOnDone); + _streamSubscription = _stream!.listen(_discoveryOnListen); + _streamSubscription!.onDone(_discoveryOnDone); } - void _discoveryOnDone() { - setState(() { - isDiscovering = false; - //debugPrint(isDiscovering as String?); - }); + Future _discoveryOnDone() async { + setState(() => _isDiscovering = false); } - void _discoveryOnListen(BluetoothDiscoveryResult event) { + Future _cancelDiscovery() async { + await _streamSubscription?.cancel(); + setState(() => _isDiscovering = false); + } + + Future _discoveryOnListen(BluetoothDiscoveryResult event) async { setState(() { - final int existingIndex = _discoveryResults.indexWhere( - (element) => element.device.address == event.device.address); + final int existingIndex = + _discoveryResults.indexWhere((element) => element.device.address == event.device.address); if (existingIndex >= 0) { - _discoveryResults[existingIndex] = event; + _discoveryResults[existingIndex] = BluetoothObject(event); } else { - _discoveryResults.add(event); - } - debugPrint("event: ${event.device.address} ${event.device.name}"); - - String deviceAddress = "64:BC:58:61:56:B0"; - if (event.device.address == deviceAddress) { - _bluetoothDevice = event.device; + _discoveryResults.add(BluetoothObject(event)); } }); } Future _restartDiscovery() async { - setState(() { - _discoveryResults.clear(); - isDiscovering = true; - }); - _startDiscovery(); + _discoveryResults.clear(); + if (_hasActiveObject) { + _discoveryResults.add(_activeObject!); + } + if (_streamSubscription != null) { + await _cancelDiscovery(); + } + await _startDiscovery(); } - Future _sendData() async { - //String output = "0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ";// !§%&()=?#!?"; - String output = textInput; - _bluetoothConnection!.output.add(Uint8List.fromList(const AsciiEncoder().convert("$output \r\n"))); - await _bluetoothConnection!.output.allSent; - debugPrint("sent: $output"); + Future _toggleConnection(BluetoothObject bluetoothObject) async { + await _cancelDiscovery(); + if (_hasActiveObject) { + _activeObject!.disconnectDevice(); + _activeObject = bluetoothObject; + } else { + _activeObject = bluetoothObject; + } + _activeObject!.isConnected ? await bluetoothObject.disconnectDevice() : await bluetoothObject.connectDevice(); } - @override - Widget build(BuildContext context) { - //bool isDark = true; - return Theme( - data: ThemeData.dark(), //isDark ? ThemeData.dark() : ThemeData.light(), - child: Scaffold( - appBar: AppBar( - title: isDiscovering - ? const Text('Bluetooth Devices (searching...)') - : const Text('Bluetooth Devices'), - actions: [ - isDiscovering - ? FittedBox( - child: Container( - margin: const EdgeInsets.all(16.0), - child: const CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ), - ) - : IconButton( - icon: const Icon(Icons.replay), - onPressed: _restartDiscovery, - ) - ], - ), + //stty -F /dev/service 19200 parenb -parodd -cstopb cs8 + //cat /dev/service | xargs -n 1 /home/script/automaticcloud.sh - body: // Center( - /*child:*/ SingleChildScrollView( - physics: const ScrollPhysics(), - child: Column( - children: [ - _SingleSection( - title: "Setup", - children: [ - const _CustomListTile( - title: "Please Enable Bluetooth", - icon: Icons.info_outline_rounded, - ), - _CustomListTile( - title: "Bluetooth Settings", - icon: Icons.bluetooth_connected, - onTap: () async { - await AppSettings.openBluetoothSettings(); - }, - ), - ], - ), - const Divider(), - Text(textOuput), - TextFormField( - decoration: const InputDecoration( - labelText: "BluetoothText" - ), - initialValue: textInput, - keyboardType: TextInputType.text, - onChanged: (String newValue) { - textInput = newValue; - }, - ), - ElevatedButton( - onPressed: () async { - await Permission.bluetoothConnect.request(); - await Permission.bluetoothScan.request(); - if (await Permission.bluetoothScan.request().isGranted) { - // Either the permission was already granted before or the user just granted it. - debugPrint("Location Permission is granted"); - } else { - debugPrint("Location Permission is denied."); - } - }, - style: buttonStyle, - child: const Text("Request Permissions"), - - ), - ElevatedButton( - onPressed: () async { - setState(() { - textOuput = ""; - }); - }, - style: buttonStyle, - child: const Text("Clear Output"), - ), - ElevatedButton( - onPressed: () async { - _restartDiscovery(); - }, - style: buttonStyle, - child: const Text("Restart Scan"), - ), - ElevatedButton( - onPressed: () async { - _sendData(); - }, - style: buttonStyle, - child: const Text("Send Data"), - ), - const Divider(), - const _SingleSection( - title: "Bluetooth Devices", - children: [], - ), - const Divider(), - ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: _discoveryResults.length, - itemBuilder: (BuildContext context, index) { - BluetoothDiscoveryResult result = _discoveryResults[index]; - final device = result.device; - final address = device.address; - return BluetoothDeviceListEntry( - device: device, - rssi: result.rssi, - onTap: () { - debugPrint(_bluetoothConnection?.isConnected.toString()); - if (_bluetoothConnection?.isConnected ?? false) { - _disconnectDevice(device); - } else { - _connectDevice(device); - } - - //Navigator.of(context).pop(result.device); - }, - onLongPress: () async { - try { - bool bonded = false; - if (device.isBonded) { - debugPrint('Unbonding from ${device.address}...'); - await FlutterBluetoothSerial.instance - .removeDeviceBondWithAddress(address); - debugPrint( - 'Unbonding from ${device.address} has succed'); - } else { - debugPrint('Bonding with ${device.address}...'); - bonded = (await FlutterBluetoothSerial.instance - .bondDeviceAtAddress(address))!; - debugPrint( - 'Bonding with ${device.address} has ${bonded ? 'succed' : 'failed'}.'); - } - setState(() { - _discoveryResults[_discoveryResults.indexOf(result)] = - BluetoothDiscoveryResult( - device: BluetoothDevice( - name: device.name ?? '', - address: address, - type: device.type, - bondState: bonded - ? BluetoothBondState.bonded - : BluetoothBondState.none, - ), - rssi: result.rssi); - }); - } catch (ex) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Error occured while bonding'), - content: Text(ex.toString()), - actions: [ - TextButton( - child: const Text("Close"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - }, - ); - }, - ), - ], - ), - ), - //), - ), - ); - } -/* @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text("Bluetooth Devices"), - ), - body: RefreshIndicator( - onRefresh: () { - debugPrint("refreshed"); - return Future(() => null); - }, - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - //child: Padding( - //padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide(width: 1.5, color: Colors.grey), + title: _isDiscovering ? const Text('Bluetooth Devices (searching...)') : const Text('Bluetooth Devices'), + actions: [ + _isDiscovering + ? FittedBox( + child: Container( + margin: const EdgeInsets.all(16.0), + child: const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ), ), - ), - child: Column( + ) + : IconButton( + icon: const Icon(Icons.replay), + onPressed: _restartDiscovery, + ) + ], + ), + body: Center( + child: ListView(children: [ + SingleChildScrollView( + physics: const ScrollPhysics(), + child: Column( + children: [ + SingleSection( + title: "Setup", children: [ - const Text( - "General", - textAlign: TextAlign.start, + CustomListTile( + title: _enabledBluetooth ? "Bluetooth Enabled" : "Please Enable Bluetooth (click me)", + icon: _enabledBluetooth ? Icons.check_circle_outline_rounded : Icons.info_outline_rounded, + onTap: () async { + await _enableBluetooth(); + }, ), - Row( - children: [ - _CustomListTile( - title: "Dark Mode", - icon: CupertinoIcons.moon, - trailing: - CupertinoSwitch(value: false, onChanged: (value) {})), - ] + CustomListTile( + title: _enabledPermissions ? "Permissions Granted" : "Please Grant Permissions (click me)", + icon: _enabledPermissions ? Icons.check_circle_outline_rounded : Icons.info_outline_rounded, + onTap: () async { + await _enablePermissions(); + }, ), - TextField( - controller: textFieldValueHolder, - decoration: const InputDecoration( - border: OutlineInputBorder(), - hintText: 'Enter the name of your new device', - ), - ), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - var textFieldValue = textFieldValueHolder.value.text; - checkNameAvailability(textFieldValue); - setState(() { - inputName = textFieldValue; - }); - }, - style: buttonStyle, - child: const Text("check name"), - ), - ), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - //_startDiscovery(); - initScan(); - }, - style: buttonStyle, - child: const Text("discover devices"), - ), - ), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - //_startDiscovery(); - _sendData(); - }, - style: buttonStyle, - child: const Text("send data"), - ), - ), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - //_startDiscovery(); - dispose(); - }, - style: buttonStyle, - child: const Text("dispose"), - ), + CustomListTile( + title: "App Settings (if permissions denied)", + icon: Icons.settings, + onTap: () async { + await AppSettings.openAppSettings(); + }, ), ], ), - ), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide(width: 1.5, color: Colors.grey), - ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + // const EdgeInsets defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0); + Expanded( + child: ElevatedButton( + onPressed: () async { + /*await Permission.bluetoothConnect.request(); + await Permission.bluetoothScan.request(); + if (await Permission.bluetoothScan.request().isGranted) { + // Either the permission was already granted before or the user just granted it. + debugPrint("Location Permission is granted"); + } else { + debugPrint("Location Permission is denied."); + }*/ + }, + style: buttonStyle, + child: const Text("Get BT Permissions"), + ), + ), + const SizedBox(width: 16.0), + Expanded( + child: ElevatedButton( + onPressed: () async { + _restartDiscovery(); + }, + style: buttonStyle, + child: const Text("Restart Scan"), + ), + ) + ]), ), - child: Row(children: [ - const Text( - "Toggle Scan", - style: TextStyle(fontWeight: FontWeight.bold), - ), - const Expanded(child: Text("")), - Switch( - value: widgetScanState, - onChanged: (bool toggleState) { - scanState = toggleState; - log("widget state: $scanState"); - setState(() { - widgetScanState = toggleState; - }); - }) - ]), - ), - // BluetoothDiscovery(start: initScan, deviceID: inputName), - ], - ), - //), - ), - ), - ); - }*/ -} - -class _CustomListTile extends StatelessWidget { - final String title; - final IconData icon; - final VoidCallback? onTap; - //final Widget? trailing; - - const _CustomListTile({ - Key? key, - required this.title, - required this.icon, - this.onTap, - //this.trailing, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(title), - leading: Icon(icon), - onTap: onTap, - //trailing: trailing, - ); - } -} - -class _SingleSection extends StatelessWidget { - final String? title; - final List children; - - const _SingleSection({ - Key? key, - this.title, - required this.children, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (title != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - title!, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + const Divider(), + SingleSection( + title: "Bluetooth Devices", + children: [ + const CustomListTile( + title: "short Press for connect / disconnect", + icon: Icons.info_outline_rounded, + ), + const CustomListTile( + title: "long press for pair / unpair", + icon: Icons.info_outline_rounded, + ), + const Divider(), + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: _discoveryResults.length, + itemBuilder: (BuildContext context, index) { + BluetoothObject bluetoothObject = _discoveryResults[index]; + final device = bluetoothObject.device; + return BluetoothDeviceEntry( + settingsPage: BluetoothDeviceSettings(bluetoothObject: bluetoothObject), + context: context, + device: device, + bluetoothObject: bluetoothObject, + onTap: () async { + await _toggleConnection(bluetoothObject); + }, + onLongPress: () async { + await bluetoothObject.bondDevice(); + }, + ); + }, + ), + ], + ) + ], ), ), - Column( - children: children, - ), - ], + ]), + ), + //drawer: const Sidebar(), ); } } diff --git a/lib/screens/bluetooth_screen_serialtemplate.dart b/lib/screens/bluetooth_screen_serialtemplate.dart deleted file mode 100644 index 302bcc7..0000000 --- a/lib/screens/bluetooth_screen_serialtemplate.dart +++ /dev/null @@ -1,163 +0,0 @@ - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; - -import './bluetooth_device_list_entry.dart'; - -class BluetoothScreen extends StatefulWidget { - /// If true, discovery starts on page start, otherwise user must press action button. - final bool start; - - const BluetoothScreen({this.start = true}); - - @override - _BluetoothScreen createState() => new _BluetoothScreen(); -} - -class _BluetoothScreen extends State { - StreamSubscription? _streamSubscription; - List results = - List.empty(growable: true); - bool isDiscovering = false; - - _BluetoothScreen(); - - @override - void initState() { - super.initState(); - - isDiscovering = widget.start; - if (isDiscovering) { - _startDiscovery(); - } - } - - void _restartDiscovery() { - setState(() { - results.clear(); - isDiscovering = true; - }); - - _startDiscovery(); - } - - void _startDiscovery() { - _streamSubscription = - FlutterBluetoothSerial.instance.startDiscovery().listen((r) { - setState(() { - final existingIndex = results.indexWhere( - (element) => element.device.address == r.device.address); - if (existingIndex >= 0) - results[existingIndex] = r; - else - results.add(r); - }); - }); - - _streamSubscription!.onDone(() { - setState(() { - isDiscovering = false; - }); - }); - } - - // @TODO . One day there should be `_pairDevice` on long tap on something... ;) - - @override - void dispose() { - // Avoid memory leak (`setState` after dispose) and cancel discovery - _streamSubscription?.cancel(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: isDiscovering - ? Text('Discovering devices') - : Text('Discovered devices'), - actions: [ - isDiscovering - ? FittedBox( - child: Container( - margin: new EdgeInsets.all(16.0), - child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ), - ) - : IconButton( - icon: Icon(Icons.replay), - onPressed: _restartDiscovery, - ) - ], - ), - body: ListView.builder( - itemCount: results.length, - itemBuilder: (BuildContext context, index) { - BluetoothDiscoveryResult result = results[index]; - final device = result.device; - final address = device.address; - return BluetoothDeviceListEntry( - device: device, - rssi: result.rssi, - onTap: () { - Navigator.of(context).pop(result.device); - }, - onLongPress: () async { - try { - bool bonded = false; - if (device.isBonded) { - print('Unbonding from ${device.address}...'); - await FlutterBluetoothSerial.instance - .removeDeviceBondWithAddress(address); - print('Unbonding from ${device.address} has succed'); - } else { - print('Bonding with ${device.address}...'); - bonded = (await FlutterBluetoothSerial.instance - .bondDeviceAtAddress(address))!; - print( - 'Bonding with ${device.address} has ${bonded ? 'succed' : 'failed'}.'); - } - setState(() { - results[results.indexOf(result)] = BluetoothDiscoveryResult( - device: BluetoothDevice( - name: device.name ?? '', - address: address, - type: device.type, - bondState: bonded - ? BluetoothBondState.bonded - : BluetoothBondState.none, - ), - rssi: result.rssi); - }); - } catch (ex) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Error occured while bonding'), - content: Text("${ex.toString()}"), - actions: [ - new TextButton( - child: new Text("Close"), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - } - }, - ); - }, - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/cloud_service_ui.dart b/lib/screens/cloud_service_ui.dart index 76825d5..36b7c9a 100644 --- a/lib/screens/cloud_service_ui.dart +++ b/lib/screens/cloud_service_ui.dart @@ -1,28 +1,28 @@ import 'dart:convert'; import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - -import 'package:flutter_provisioning_for_iot/objects/cloud_service_api.dart'; -import 'package:flutter_provisioning_for_iot/widgets/sidebar.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../objects/cloud_service_api.dart'; +import '../widgets/sidebar.dart'; + +Future> readJson() async { + final String data = await rootBundle.loadString('config/credentials.json'); + final Map dataMap = json.decode(data); + log("loaded json input $dataMap"); + return dataMap; +} + class CloudService extends StatefulWidget { const CloudService({Key? key}) : super(key: key); @override State createState() => _CloudService(); - -} -Future> readJson() async { - final data = await rootBundle.loadString('config/credentials.json'); - var data_ = json.decode(data) as Map; - debugPrint(data_.toString()); - return data_; } -class _CloudService extends State{ - +class _CloudService extends State { late final Map credentials; CloudServiceAPI cloudServiceAPI = CloudServiceAPI(); static SharedPreferences? preferencesInstance; @@ -35,18 +35,27 @@ class _CloudService extends State{ ), body: Center( child: TextButton( - onPressed: () async{ - var respond1 = await cloudServiceAPI.getDevices(); - debugPrint('Devices: ${respond1.toString()}'); - var respond2 = await cloudServiceAPI.getInformation(); - debugPrint('Information: ${respond2.toString()}'); - var respond3 = await cloudServiceAPI.createDevice('1', 'asdas', 'sdwe1'); - debugPrint('CreateDevice: ${respond3.toString()}'); - }, child: const Text("Example"), + onPressed: () async { + // List respond1 = await cloudServiceAPI.getDevices(); + // debugPrint('Devices: ${respond1[0].toString()}'); + // debugPrint('Devices: ${respond1[0].toString()}'); + + //dynamic respond2 = await cloudServiceAPI.getInformation(); + //debugPrint('Information: ${respond2.toString()}'); + + // dynamic respond3 = await cloudServiceAPI.createDevice('1', 'asdas', 'sdwe1'); + // debugPrint('CreateDevice: ${respond3.toString()}');*/ + + //dynamic respond4 = await cloudServiceAPI.getDeviceInfo("PFC200V3-430EB3"); + //debugPrint('Information: ${respond4.toString()}'); + + //bool respond5 = await cloudServiceAPI.createDevice("PFC200V3-430EB2", "60987668030DF277AD7706D7C1FA683F37230D202A80EE5135B05EF928E3BF88", ""); + //debugPrint('Information: ${respond5.toString()}'); + }, + child: const Text("Example"), ), ), drawer: const Sidebar(), ); } - -} \ No newline at end of file +} diff --git a/lib/screens/main_page.dart b/lib/screens/main_page.dart deleted file mode 100644 index 1573ce3..0000000 --- a/lib/screens/main_page.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_provisioning_for_iot/objects/cloud_service_api.dart'; -import 'package:flutter_provisioning_for_iot/widgets/sidebar.dart'; - -import '../objects/create_material_color.dart'; - -class MainPage extends StatelessWidget { - const MainPage({super.key}); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Provisioning for IOT', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: CustomColor.createMaterialColor(const Color(0xff263f8c)), - ), - home: const MyHomePage(title: 'Provisioning for IOT'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - CloudServiceAPI cloudServiceAPI = CloudServiceAPI(); - void _incrementCounter() { - setState(() { - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - drawer: const Sidebar(),// This trailing comma makes auto-formatting nicer for build methods. - ); - } -} diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart new file mode 100644 index 0000000..9d47f57 --- /dev/null +++ b/lib/screens/main_screen.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + +import '../objects/create_material_color.dart'; +import '../widgets/sidebar.dart'; + +class MainScreen extends StatelessWidget { + const MainScreen({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + const String appName = "Provisioning for IOT"; + + return MaterialApp( + title: appName, + theme: ThemeData( + primarySwatch: CustomColor.createMaterialColor(const Color(0xff263f8c)), + brightness: Brightness.light, + ), + home: const HomeScreen(title: appName), + //debugShowCheckedModeBanner: false, + ); + } +} + +class HomeScreen extends StatefulWidget { + final String title; + + const HomeScreen({super.key, required this.title}); + + @override + State createState() => _HomeScreen(); +} + +class _HomeScreen extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Die HFU präsentiert:", + ), + Text( + "Cloud Provisioning for IOT Devices", + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox( + width: 10.0, + height: 50.0, + ), + const Image(image: AssetImage('assets/device/PFC200.jpg')), + ], + ), + ), + drawer: const Sidebar(), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/lib/screens/registered_devices_screen.dart b/lib/screens/registered_devices_screen.dart new file mode 100644 index 0000000..6fa13ad --- /dev/null +++ b/lib/screens/registered_devices_screen.dart @@ -0,0 +1,118 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_provisioning_for_iot/schemas/device_info_model.dart'; + +import '../objects/cloud_service_api.dart'; +// import 'package:shared_preferences/shared_preferences.dart'; + +import '../schemas/device_model.dart'; +import '../widgets/custom_list_tile.dart'; +import '../widgets/sidebar.dart'; +import '../widgets/single_section.dart'; + +Future> readJson() async { + final data = await rootBundle.loadString('config/credentials.json'); + var data_ = json.decode(data) as Map; + debugPrint(data_.toString()); + return data_; +} + +class RegisteredDevicesScreen extends StatefulWidget { + const RegisteredDevicesScreen({Key? key}) : super(key: key); + + @override + State createState() => _RegisteredDevicesScreen(); +} + +class _RegisteredDevicesScreen extends State { + late List? _registeredInfoDevices; + + // late final Map _credentials; + final CloudServiceAPI _cloudServiceAPI = CloudServiceAPI(); + + // static SharedPreferences? preferencesInstance; + bool _isFetching = false; + + Future _fetchRegisteredDevices() async { + setState(() { + _isFetching = true; + }); + _registeredInfoDevices == null + ? _registeredInfoDevices = List.empty(growable: true) + : _registeredInfoDevices!.clear(); + List? registeredDevices = await _cloudServiceAPI.getDevices(); + for (DeviceModel deviceModel in registeredDevices) { + String deviceID = deviceModel.id; + DeviceInfoModel deviceInfoModel = await _cloudServiceAPI.getDeviceInfo(deviceID); + setState(() { + _registeredInfoDevices!.add(deviceInfoModel); + }); + } + setState(() { + _isFetching = false; + }); + } + + @override + void initState() { + _registeredInfoDevices = null; + _fetchRegisteredDevices(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: _isFetching ? const Text('Registered Devices (searching...)') : const Text('Registered Devices'), + actions: [ + _isFetching + ? FittedBox( + child: Container( + margin: const EdgeInsets.all(16.0), + child: const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ), + ) + : IconButton( + icon: const Icon(Icons.replay), + onPressed: _fetchRegisteredDevices, + ) + ], + ), + body: Center( + child: ListView(children: [ + SingleChildScrollView( + physics: const ScrollPhysics(), + child: Column( + children: [ + SingleSection( + title: "Registered Bluetooth Devices", + children: [ + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: _registeredInfoDevices == null ? 0 : _registeredInfoDevices!.length, + itemBuilder: (BuildContext context, index) { + DeviceInfoModel entry = _registeredInfoDevices![index]; + //DeviceInfoModel entryInfo = await _cloudServiceAPI.getDeviceInfo(entry.id); + return CustomListTile( + title: entry.id, + icon: Icons.devices, + ); + }, + ), + ], + ) + ], + ), + ), + ]), + ), + drawer: const Sidebar(), + ); + } +} diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 82220e5..d06e859 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -2,24 +2,33 @@ import 'package:flutter/material.dart'; import 'package:flutter_provisioning_for_iot/objects/cloud_service_api.dart'; import '../widgets/sidebar.dart'; +import '../widgets/single_section.dart'; class Settings extends StatefulWidget { const Settings({Key? key}) : super(key: key); static CloudServiceAPI cloudService = CloudServiceAPI(); - static String address = cloudService.getAddress(); - static String username = cloudService.getUsername(); - static String password = cloudService.getPassword(); + static String address = cloudService.address; + static String username = cloudService.username; + static String password = cloudService.password; @override State createState() => _SettingsState(); /// Initializes the Settings with the correct Values from the last session - static Future initSettings() async { - } + static Future initSettings() async {} } class _SettingsState extends State { + bool _passwordVisible = false; + + @override + void initState() { + debugPrint("PASSWORD ${Settings.password}"); + super.initState(); + _passwordVisible = false; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -29,40 +38,80 @@ class _SettingsState extends State { title: const Text("Settings"), ), body: Center( - child: Column( - children: [ - TextFormField( - decoration: const InputDecoration( - labelText: "Address for the Device Manager"), - initialValue: Settings.address, - keyboardType: TextInputType.text, - onChanged: (String newValue) { - Settings.address = newValue; - Settings.cloudService.setAddress(newValue); - }, - ), - TextFormField( - decoration: const InputDecoration( - labelText: "Username"), - keyboardType: TextInputType.text, - initialValue: Settings.username, - onChanged: (String newValue) { - Settings.username = newValue; - Settings.cloudService.setUsername(newValue); - }, - ), - TextFormField( - decoration: const InputDecoration( - labelText: "Password"), - keyboardType: TextInputType.text, - initialValue: Settings.password, - onChanged: (String newValue) { - Settings.password = newValue; - Settings.cloudService.setPassword(newValue); - }, - ) - ], - ), + child: ListView(children: [ + SingleChildScrollView( + physics: const ScrollPhysics(), + child: Column(children: [ + SingleSection( + title: "Server Settings", + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Enter the server url (no https:// and paths)', + labelText: "Address for the Device Manager", + ), + initialValue: Settings.address, + keyboardType: TextInputType.text, + onChanged: (String newValue) { + Settings.address = newValue; + Settings.cloudService.address = newValue; + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Enter the server username', + labelText: "Username", + ), + initialValue: Settings.username, + keyboardType: TextInputType.text, + onChanged: (String newValue) { + Settings.username = newValue; + Settings.cloudService.username = newValue; + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: TextFormField( + obscureText: _passwordVisible ? false : true, + decoration: InputDecoration( + border: const OutlineInputBorder(), + hintText: 'Enter the server password', + labelText: "Password", + suffixIcon: IconButton( + icon: Icon(_passwordVisible ? Icons.visibility : Icons.visibility_off), + onPressed: () { + setState(() { + _passwordVisible = !_passwordVisible; + }); + }, + ), + filled: true, + ), + initialValue: Settings.password, + keyboardType: TextInputType.text, + onChanged: (String newValue) { + Settings.password = newValue; + Settings.cloudService.password = newValue; + }, + ), + ), + ], + ), + const SingleSection( + title: "Theme Settings", + children: [], + ) + ]), + ) + ]), ), drawer: const Sidebar()); } diff --git a/lib/widgets/bluetooth_discovery.dart b/lib/widgets/bluetooth_discovery.dart deleted file mode 100644 index 870eb3e..0000000 --- a/lib/widgets/bluetooth_discovery.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; -import 'package:flutter_provisioning_for_iot/screens/bluetooth_device_settings.dart'; - -class BluetoothDiscovery extends StatefulWidget { - final bool start; - final String deviceID; - - const BluetoothDiscovery({ - super.key, - required this.start, - required this.deviceID - }); - - @override - State createState() => _BluetoothDiscovery(); -} - -class _BluetoothDiscovery extends State { - - StreamSubscription? _streamSubscription; - List results = List.empty(growable: true); - bool isDiscovering = false; - - @override - Widget build(BuildContext context) { - debugPrint("test: ${widget.deviceID}"); - debugPrint("bool: ${widget.start}"); - if(widget.start) { - _startDiscovery(); - } - return RefreshIndicator( - onRefresh: () { - debugPrint("refreshed"); - return Future(() => null); - }, - child: ListView.builder( - scrollDirection: Axis.vertical, - shrinkWrap: true, - itemCount: results.length, - itemExtent: 50.0, - itemBuilder: - (BuildContext context, int index) { - var device = results.elementAt(index).device; - var deviceAddress = device.address; - var deviceName = device.name; - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Icon(Icons.bluetooth_disabled_rounded), - TextButton( - onPressed: () { - debugPrint(deviceAddress); - }, - onLongPress: () { - debugPrint("no need to press for this long..."); - }, - child: Text("$deviceAddress" - "${(deviceName != "" ? "" : " | $deviceName")}")), - const Expanded(child: Text("")), - IconButton( - icon: const Icon(Icons.settings), - onPressed: () { - debugPrint("this is a warning!"); - debugPrint(device.address); - Navigator.push(context, MaterialPageRoute(builder: (context) => BluetoothDeviceSettings(device: device))); - }, - ), - ], - ); - }, - ) - ); - } - - @override - void initState() { - super.initState(); - - isDiscovering = widget.start; - if (isDiscovering) { - _startDiscovery(); - } - } - - void _restartDiscovery() { - setState(() { - results.clear(); - isDiscovering = true; - }); - - _startDiscovery(); - } - - void _startDiscovery() { - _streamSubscription = - FlutterBluetoothSerial.instance.startDiscovery().listen((r) { - setState(() { - final existingIndex = results.indexWhere( - (element) => element.device.address == r.device.address); - if (existingIndex >= 0) { - results[existingIndex] = r; - } else { - results.add(r); - } - }); - }); - - debugPrint("results: $results"); - - _streamSubscription!.onDone(() { - setState(() { - isDiscovering = false; - }); - }); - } - - @override - void dispose() { - // Avoid memory leak (`setState` after dispose) and cancel discovery - _streamSubscription?.cancel(); - - super.dispose(); - } - - void initScan() { - _streamSubscription = - FlutterBluetoothSerial.instance.startDiscovery().listen((r) { - debugPrint("r: ${r.device.name} ${r.device.address}"); - setState(() { - final existingIndex = results.indexWhere( - (element) => element.device.address == r.device.address); - if (existingIndex >= 0) { - results[existingIndex] = r; - } else { - results.add(r); - } - }); - if (r.device.address == "38:F3:2E:41:82:74") { - BluetoothConnection.toAddress(r.device.address).then((connection) { - debugPrint('Connected to the device'); - //connection = _connection; - //setState(() { - //isConnecting = false; - //isDisconnecting = false; - }); - } - }); - - _streamSubscription!.onDone(() { - setState(() { - isDiscovering = false; - }); - }); - } -} diff --git a/lib/widgets/custom_list_tile.dart b/lib/widgets/custom_list_tile.dart new file mode 100644 index 0000000..ec38f58 --- /dev/null +++ b/lib/widgets/custom_list_tile.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class CustomListTile extends StatelessWidget { + final String title; + final IconData icon; + final VoidCallback? onTap; + + //final Widget? trailing; + + const CustomListTile({ + Key? key, + required this.title, + required this.icon, + this.onTap, + //this.trailing, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(title), + leading: Icon(icon), + onTap: onTap, + //trailing: trailing, + ); + } +} diff --git a/lib/widgets/sidebar.dart b/lib/widgets/sidebar.dart index c7d97ad..2bebf8e 100644 --- a/lib/widgets/sidebar.dart +++ b/lib/widgets/sidebar.dart @@ -1,54 +1,62 @@ import 'package:flutter/material.dart'; -import 'package:flutter_provisioning_for_iot/screens/bluetooth_screen.dart'; -import 'package:flutter_provisioning_for_iot/screens/cloud_service_ui.dart'; -import '../screens/main_page.dart'; + +import '../screens/bluetooth_screen.dart'; +import '../screens/cloud_service_ui.dart'; +import '../screens/main_screen.dart'; +import '../screens/registered_devices_screen.dart'; import '../screens/settings.dart'; class Sidebar extends StatelessWidget { const Sidebar({Key? key}) : super(key: key); - @override Widget build(BuildContext context) { return Drawer( + //backgroundColor: Colors.black , child: ListView( + padding: EdgeInsets.zero, children: [ const DrawerHeader( - decoration: BoxDecoration( - color: Colors.white - ), + decoration: BoxDecoration(color: Colors.white), child: Center( - child: Image( - image: AssetImage('assets/logo/m&m_logo.png') - ), + child: Image(image: AssetImage('assets/logo/m&m_logo.png')), ), ), ListTile( - title: const Text("Main Page"), + leading: const Icon(Icons.home), + title: const Text("Home"), onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const MainPage())); + Navigator.push(context, MaterialPageRoute(builder: (context) => const MainScreen())); }, ), ListTile( - title: const Text("Settings"), - onTap: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => const Settings())); - }, - ), - ListTile( - title: const Text("Bluetooth Test"), + leading: const Icon(Icons.bluetooth_searching), + title: const Text("Connect Device"), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) => const BluetoothScreen())); }, ), ListTile( + leading: const Icon(Icons.devices), + title: const Text("Registered Devices"), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => const RegisteredDevicesScreen())); + }, + ), + ListTile( + leading: const Icon(Icons.settings), + title: const Text("Settings"), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => const Settings())); + }, + ), + ListTile( + leading: const Icon(Icons.cloud), title: const Text("Cloud Service"), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) => const CloudService())); }, - ) - + ), ], ), ); diff --git a/lib/widgets/single_section.dart b/lib/widgets/single_section.dart new file mode 100644 index 0000000..27091fd --- /dev/null +++ b/lib/widgets/single_section.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class SingleSection extends StatelessWidget { + final String? title; + final List children; + + const SingleSection({ + Key? key, + this.title, + required this.children, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + title!, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + Column( + children: children, + ), + ], + ); + } +} diff --git a/lib/widgets/switch_widget.dart b/lib/widgets/switch_widget.dart index 544a846..083720c 100644 --- a/lib/widgets/switch_widget.dart +++ b/lib/widgets/switch_widget.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; class SwitchWidget extends StatefulWidget { - const SwitchWidget({super.key}); @override @@ -9,7 +8,6 @@ class SwitchWidget extends StatefulWidget { } class _SwitchWidget extends State { - bool switchControl = false; var textHolder = 'Switch is OFF'; @@ -41,4 +39,4 @@ class _SwitchWidget extends State { inactiveTrackColor: Colors.grey, ); } -} \ No newline at end of file +} diff --git a/pubspec.yaml b/pubspec.yaml index a5af96b..f2a2cc5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,7 @@ flutter: assets: - config/credentials.json - assets/logo/m&m_logo.png + - assets/device/PFC200.jpg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test/widget_test.dart b/test/widget_test.dart index f386e92..7e9ed70 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -6,7 +6,7 @@ // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; -import 'package:flutter_provisioning_for_iot/screens/main_page.dart'; +import 'package:flutter_provisioning_for_iot/screens/main_screen.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_provisioning_for_iot/main.dart';