pumaguard 21.post27__py3-none-any.whl → 21.post83__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pumaguard/presets.py +1 -0
- pumaguard/pumaguard-ui/.last_build_id +1 -1
- pumaguard/pumaguard-ui/assets/NOTICES +621 -71
- pumaguard/pumaguard-ui/assets/fonts/MaterialIcons-Regular.otf +0 -0
- pumaguard/pumaguard-ui/flutter_bootstrap.js +1 -1
- pumaguard/pumaguard-ui/main.dart.js +28869 -28787
- pumaguard/web_routes/dhcp.py +311 -54
- pumaguard/web_routes/diagnostics.py +6 -0
- pumaguard/web_routes/settings.py +13 -0
- pumaguard/web_ui.py +29 -0
- {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/METADATA +1 -1
- pumaguard-21.post83.dist-info/RECORD +254 -0
- pumaguard-ui/.gitignore +48 -0
- pumaguard-ui/.metadata +45 -0
- pumaguard-ui/API_REFERENCE.md +717 -0
- pumaguard-ui/LICENSE +201 -0
- pumaguard-ui/Makefile +36 -0
- pumaguard-ui/README.md +371 -0
- pumaguard-ui/UI_DEVELOPMENT_CONTEXT.md +427 -0
- pumaguard-ui/analysis_options.yaml +28 -0
- pumaguard-ui/android/.gitignore +14 -0
- pumaguard-ui/android/app/build.gradle.kts +44 -0
- pumaguard-ui/android/app/src/debug/AndroidManifest.xml +7 -0
- pumaguard-ui/android/app/src/main/AndroidManifest.xml +45 -0
- pumaguard-ui/android/app/src/main/kotlin/com/example/pumaguard_ui/MainActivity.kt +5 -0
- pumaguard-ui/android/app/src/main/res/drawable/launch_background.xml +12 -0
- pumaguard-ui/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- pumaguard-ui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- pumaguard-ui/android/app/src/main/res/values/styles.xml +18 -0
- pumaguard-ui/android/app/src/main/res/values-night/styles.xml +18 -0
- pumaguard-ui/android/app/src/profile/AndroidManifest.xml +7 -0
- pumaguard-ui/android/build.gradle.kts +24 -0
- pumaguard-ui/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- pumaguard-ui/android/gradle.properties +2 -0
- pumaguard-ui/android/settings.gradle.kts +26 -0
- pumaguard-ui/fonts/README.md +38 -0
- pumaguard-ui/fonts/Roboto-Bold.ttf +0 -0
- pumaguard-ui/fonts/Roboto-Light.ttf +0 -0
- pumaguard-ui/fonts/Roboto-Medium.ttf +0 -0
- pumaguard-ui/fonts/Roboto-Regular.ttf +0 -0
- pumaguard-ui/fonts/RobotoMono-Bold.ttf +0 -0
- pumaguard-ui/fonts/RobotoMono-Medium.ttf +0 -0
- pumaguard-ui/fonts/RobotoMono-Regular.ttf +0 -0
- pumaguard-ui/fonts/download_fonts.sh +76 -0
- pumaguard-ui/ios/.gitignore +34 -0
- pumaguard-ui/ios/Flutter/AppFrameworkInfo.plist +26 -0
- pumaguard-ui/ios/Flutter/Debug.xcconfig +1 -0
- pumaguard-ui/ios/Flutter/Release.xcconfig +1 -0
- pumaguard-ui/ios/Runner/AppDelegate.swift +13 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
- pumaguard-ui/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
- pumaguard-ui/ios/Runner/Base.lproj/Main.storyboard +26 -0
- pumaguard-ui/ios/Runner/Info.plist +49 -0
- pumaguard-ui/ios/Runner/Runner-Bridging-Header.h +1 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.pbxproj +616 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- pumaguard-ui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- pumaguard-ui/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
- pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- pumaguard-ui/ios/RunnerTests/RunnerTests.swift +12 -0
- pumaguard-ui/lib/main.dart +56 -0
- pumaguard-ui/lib/models/camera.dart +45 -0
- pumaguard-ui/lib/models/plug.dart +45 -0
- pumaguard-ui/lib/models/settings.dart +112 -0
- pumaguard-ui/lib/models/status.dart +58 -0
- pumaguard-ui/lib/screens/directories_screen.dart +319 -0
- pumaguard-ui/lib/screens/home_screen.dart +545 -0
- pumaguard-ui/lib/screens/image_browser_screen.dart +1248 -0
- pumaguard-ui/lib/screens/server_discovery_screen.dart +390 -0
- pumaguard-ui/lib/screens/settings_screen.dart +1162 -0
- pumaguard-ui/lib/screens/wifi_settings_screen.dart +671 -0
- pumaguard-ui/lib/services/api_service.dart +717 -0
- pumaguard-ui/lib/services/camera_events_service.dart +195 -0
- pumaguard-ui/lib/services/mdns_service.dart +4 -0
- pumaguard-ui/lib/services/mdns_service_impl.dart +282 -0
- pumaguard-ui/lib/services/mdns_service_io.dart +1 -0
- pumaguard-ui/lib/services/mdns_service_web.dart +106 -0
- pumaguard-ui/lib/utils/download_helper.dart +2 -0
- pumaguard-ui/lib/utils/download_helper_stub.dart +6 -0
- pumaguard-ui/lib/utils/download_helper_web.dart +14 -0
- pumaguard-ui/lib/utils/platform_url.dart +10 -0
- pumaguard-ui/lib/utils/platform_url_stub.dart +11 -0
- pumaguard-ui/lib/utils/platform_url_web.dart +16 -0
- pumaguard-ui/linux/.gitignore +1 -0
- pumaguard-ui/linux/CMakeLists.txt +128 -0
- pumaguard-ui/linux/flutter/CMakeLists.txt +88 -0
- pumaguard-ui/linux/flutter/generated_plugin_registrant.cc +15 -0
- pumaguard-ui/linux/flutter/generated_plugin_registrant.h +15 -0
- pumaguard-ui/linux/flutter/generated_plugins.cmake +24 -0
- pumaguard-ui/linux/runner/CMakeLists.txt +26 -0
- pumaguard-ui/linux/runner/main.cc +6 -0
- pumaguard-ui/linux/runner/my_application.cc +148 -0
- pumaguard-ui/linux/runner/my_application.h +21 -0
- pumaguard-ui/macos/.gitignore +7 -0
- pumaguard-ui/macos/Flutter/Flutter-Debug.xcconfig +1 -0
- pumaguard-ui/macos/Flutter/Flutter-Release.xcconfig +1 -0
- pumaguard-ui/macos/Flutter/GeneratedPluginRegistrant.swift +16 -0
- pumaguard-ui/macos/Runner/AppDelegate.swift +13 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- pumaguard-ui/macos/Runner/Base.lproj/MainMenu.xib +343 -0
- pumaguard-ui/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- pumaguard-ui/macos/Runner/Configs/Debug.xcconfig +2 -0
- pumaguard-ui/macos/Runner/Configs/Release.xcconfig +2 -0
- pumaguard-ui/macos/Runner/Configs/Warnings.xcconfig +13 -0
- pumaguard-ui/macos/Runner/DebugProfile.entitlements +12 -0
- pumaguard-ui/macos/Runner/Info.plist +32 -0
- pumaguard-ui/macos/Runner/MainFlutterWindow.swift +15 -0
- pumaguard-ui/macos/Runner/Release.entitlements +8 -0
- pumaguard-ui/macos/Runner.xcodeproj/project.pbxproj +705 -0
- pumaguard-ui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- pumaguard-ui/macos/Runner.xcworkspace/contents.xcworkspacedata +7 -0
- pumaguard-ui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pumaguard-ui/macos/RunnerTests/RunnerTests.swift +12 -0
- pumaguard-ui/pubspec.lock +882 -0
- pumaguard-ui/pubspec.yaml +125 -0
- pumaguard-ui/test/models/camera_test.dart +515 -0
- pumaguard-ui/test/models/plug_test.dart +499 -0
- pumaguard-ui/test/models/settings_test.dart +903 -0
- pumaguard-ui/test/models/status_test.dart +707 -0
- pumaguard-ui/test/screens/image_browser_grouping_test.dart +555 -0
- pumaguard-ui/test/services/api_service_cameras_test.dart +580 -0
- pumaguard-ui/test/services/api_service_image_browser_test.dart +512 -0
- pumaguard-ui/test/widget_test.dart.skip +38 -0
- pumaguard-ui/web/favicon.png +0 -0
- pumaguard-ui/web/icons/Icon-192.png +0 -0
- pumaguard-ui/web/icons/Icon-512.png +0 -0
- pumaguard-ui/web/icons/Icon-maskable-192.png +0 -0
- pumaguard-ui/web/icons/Icon-maskable-512.png +0 -0
- pumaguard-ui/web/index.html +38 -0
- pumaguard-ui/web/manifest.json +35 -0
- pumaguard-ui/windows/.gitignore +17 -0
- pumaguard-ui/windows/CMakeLists.txt +108 -0
- pumaguard-ui/windows/flutter/CMakeLists.txt +109 -0
- pumaguard-ui/windows/flutter/generated_plugin_registrant.cc +14 -0
- pumaguard-ui/windows/flutter/generated_plugin_registrant.h +15 -0
- pumaguard-ui/windows/flutter/generated_plugins.cmake +24 -0
- pumaguard-ui/windows/runner/CMakeLists.txt +40 -0
- pumaguard-ui/windows/runner/Runner.rc +121 -0
- pumaguard-ui/windows/runner/flutter_window.cpp +71 -0
- pumaguard-ui/windows/runner/flutter_window.h +33 -0
- pumaguard-ui/windows/runner/main.cpp +43 -0
- pumaguard-ui/windows/runner/resource.h +16 -0
- pumaguard-ui/windows/runner/resources/app_icon.ico +0 -0
- pumaguard-ui/windows/runner/runner.exe.manifest +14 -0
- pumaguard-ui/windows/runner/utils.cpp +65 -0
- pumaguard-ui/windows/runner/utils.h +19 -0
- pumaguard-ui/windows/runner/win32_window.cpp +288 -0
- pumaguard-ui/windows/runner/win32_window.h +102 -0
- pumaguard-21.post27.dist-info/RECORD +0 -83
- {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/WHEEL +0 -0
- {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/entry_points.txt +0 -0
- {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/licenses/LICENSE +0 -0
- {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import '../services/api_service.dart';
|
|
3
|
+
|
|
4
|
+
class WifiSettingsScreen extends StatefulWidget {
|
|
5
|
+
final ApiService apiService;
|
|
6
|
+
|
|
7
|
+
const WifiSettingsScreen({super.key, required this.apiService});
|
|
8
|
+
|
|
9
|
+
@override
|
|
10
|
+
State<WifiSettingsScreen> createState() => _WifiSettingsScreenState();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class _WifiSettingsScreenState extends State<WifiSettingsScreen> {
|
|
14
|
+
bool _isLoading = false;
|
|
15
|
+
bool _isScanning = false;
|
|
16
|
+
String? _errorMessage;
|
|
17
|
+
String? _successMessage;
|
|
18
|
+
|
|
19
|
+
String _currentMode = 'unknown';
|
|
20
|
+
String? _currentSsid;
|
|
21
|
+
bool _isConnected = false;
|
|
22
|
+
|
|
23
|
+
List<WifiNetwork> _networks = [];
|
|
24
|
+
final TextEditingController _passwordController = TextEditingController();
|
|
25
|
+
final TextEditingController _apSsidController = TextEditingController();
|
|
26
|
+
final TextEditingController _apPasswordController = TextEditingController();
|
|
27
|
+
|
|
28
|
+
bool _obscurePassword = true;
|
|
29
|
+
|
|
30
|
+
@override
|
|
31
|
+
void initState() {
|
|
32
|
+
super.initState();
|
|
33
|
+
_loadCurrentMode();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
void dispose() {
|
|
38
|
+
_passwordController.dispose();
|
|
39
|
+
_apSsidController.dispose();
|
|
40
|
+
_apPasswordController.dispose();
|
|
41
|
+
super.dispose();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
Future<void> _loadCurrentMode() async {
|
|
45
|
+
setState(() {
|
|
46
|
+
_isLoading = true;
|
|
47
|
+
_errorMessage = null;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
final response = await widget.apiService.getWifiMode();
|
|
52
|
+
setState(() {
|
|
53
|
+
_currentMode = response['mode'] as String? ?? 'unknown';
|
|
54
|
+
_currentSsid = response['ssid'] as String?;
|
|
55
|
+
_isConnected = response['connected'] as bool? ?? false;
|
|
56
|
+
_isLoading = false;
|
|
57
|
+
});
|
|
58
|
+
} catch (e) {
|
|
59
|
+
setState(() {
|
|
60
|
+
_errorMessage = 'Failed to load WiFi mode: $e';
|
|
61
|
+
_isLoading = false;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Future<void> _scanNetworks() async {
|
|
67
|
+
setState(() {
|
|
68
|
+
_isScanning = true;
|
|
69
|
+
_errorMessage = null;
|
|
70
|
+
_successMessage = null;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
final response = await widget.apiService.scanWifiNetworks();
|
|
75
|
+
final networksJson = response['networks'] as List<dynamic>;
|
|
76
|
+
|
|
77
|
+
setState(() {
|
|
78
|
+
_networks = networksJson
|
|
79
|
+
.map((json) => WifiNetwork.fromJson(json as Map<String, dynamic>))
|
|
80
|
+
.toList();
|
|
81
|
+
_isScanning = false;
|
|
82
|
+
});
|
|
83
|
+
} catch (e) {
|
|
84
|
+
setState(() {
|
|
85
|
+
_errorMessage = 'Failed to scan networks: $e';
|
|
86
|
+
_isScanning = false;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Future<void> _connectToNetwork(
|
|
92
|
+
String ssid,
|
|
93
|
+
String password,
|
|
94
|
+
bool isSecured,
|
|
95
|
+
) async {
|
|
96
|
+
setState(() {
|
|
97
|
+
_isLoading = true;
|
|
98
|
+
_errorMessage = null;
|
|
99
|
+
_successMessage = null;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await widget.apiService.setWifiMode(
|
|
104
|
+
mode: 'client',
|
|
105
|
+
ssid: ssid,
|
|
106
|
+
password: isSecured ? password : null,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
setState(() {
|
|
110
|
+
_successMessage = 'Successfully connected to $ssid';
|
|
111
|
+
_isLoading = false;
|
|
112
|
+
_passwordController.clear();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Reload current mode after a short delay
|
|
116
|
+
await Future.delayed(const Duration(seconds: 2));
|
|
117
|
+
await _loadCurrentMode();
|
|
118
|
+
} catch (e) {
|
|
119
|
+
setState(() {
|
|
120
|
+
_errorMessage = 'Failed to connect: $e';
|
|
121
|
+
_isLoading = false;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Future<void> _enableApMode() async {
|
|
127
|
+
final ssid = _apSsidController.text.trim();
|
|
128
|
+
final password = _apPasswordController.text.trim();
|
|
129
|
+
|
|
130
|
+
if (ssid.isEmpty) {
|
|
131
|
+
setState(() {
|
|
132
|
+
_errorMessage = 'SSID cannot be empty';
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (password.isNotEmpty && password.length < 8) {
|
|
138
|
+
setState(() {
|
|
139
|
+
_errorMessage = 'Password must be at least 8 characters';
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setState(() {
|
|
145
|
+
_isLoading = true;
|
|
146
|
+
_errorMessage = null;
|
|
147
|
+
_successMessage = null;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
await widget.apiService.setWifiMode(
|
|
152
|
+
mode: 'ap',
|
|
153
|
+
ssid: ssid,
|
|
154
|
+
password: password.isEmpty ? null : password,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
setState(() {
|
|
158
|
+
_successMessage = 'Access Point enabled: $ssid';
|
|
159
|
+
_isLoading = false;
|
|
160
|
+
_apSsidController.clear();
|
|
161
|
+
_apPasswordController.clear();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Reload current mode after a short delay
|
|
165
|
+
await Future.delayed(const Duration(seconds: 2));
|
|
166
|
+
await _loadCurrentMode();
|
|
167
|
+
} catch (e) {
|
|
168
|
+
setState(() {
|
|
169
|
+
_errorMessage = 'Failed to enable AP mode: $e';
|
|
170
|
+
_isLoading = false;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void _showPasswordDialog(WifiNetwork network) {
|
|
176
|
+
_passwordController.clear();
|
|
177
|
+
|
|
178
|
+
showDialog(
|
|
179
|
+
context: context,
|
|
180
|
+
builder: (BuildContext context) {
|
|
181
|
+
return StatefulBuilder(
|
|
182
|
+
builder: (context, setDialogState) {
|
|
183
|
+
return AlertDialog(
|
|
184
|
+
title: Text('Connect to ${network.ssid}'),
|
|
185
|
+
content: Column(
|
|
186
|
+
mainAxisSize: MainAxisSize.min,
|
|
187
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
188
|
+
children: [
|
|
189
|
+
Text(
|
|
190
|
+
'Signal: ${network.signal}%',
|
|
191
|
+
style: Theme.of(context).textTheme.bodySmall,
|
|
192
|
+
),
|
|
193
|
+
const SizedBox(height: 16),
|
|
194
|
+
if (network.secured) ...[
|
|
195
|
+
TextField(
|
|
196
|
+
controller: _passwordController,
|
|
197
|
+
obscureText: _obscurePassword,
|
|
198
|
+
decoration: InputDecoration(
|
|
199
|
+
labelText: 'Password',
|
|
200
|
+
border: const OutlineInputBorder(),
|
|
201
|
+
suffixIcon: IconButton(
|
|
202
|
+
icon: Icon(
|
|
203
|
+
_obscurePassword
|
|
204
|
+
? Icons.visibility
|
|
205
|
+
: Icons.visibility_off,
|
|
206
|
+
),
|
|
207
|
+
onPressed: () {
|
|
208
|
+
setDialogState(() {
|
|
209
|
+
_obscurePassword = !_obscurePassword;
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
),
|
|
215
|
+
] else ...[
|
|
216
|
+
Text(
|
|
217
|
+
'This is an open network (no password required)',
|
|
218
|
+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
219
|
+
color: Theme.of(context).colorScheme.secondary,
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
],
|
|
223
|
+
],
|
|
224
|
+
),
|
|
225
|
+
actions: [
|
|
226
|
+
TextButton(
|
|
227
|
+
onPressed: () => Navigator.of(context).pop(),
|
|
228
|
+
child: const Text('Cancel'),
|
|
229
|
+
),
|
|
230
|
+
FilledButton(
|
|
231
|
+
onPressed: () {
|
|
232
|
+
Navigator.of(context).pop();
|
|
233
|
+
_connectToNetwork(
|
|
234
|
+
network.ssid,
|
|
235
|
+
_passwordController.text,
|
|
236
|
+
network.secured,
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
child: const Text('Connect'),
|
|
240
|
+
),
|
|
241
|
+
],
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@override
|
|
250
|
+
Widget build(BuildContext context) {
|
|
251
|
+
return Scaffold(
|
|
252
|
+
appBar: AppBar(title: const Text('WiFi Settings')),
|
|
253
|
+
body: _isLoading && _networks.isEmpty
|
|
254
|
+
? const Center(child: CircularProgressIndicator())
|
|
255
|
+
: SingleChildScrollView(
|
|
256
|
+
padding: const EdgeInsets.all(16),
|
|
257
|
+
child: Column(
|
|
258
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
259
|
+
children: [
|
|
260
|
+
// Error/Success Messages
|
|
261
|
+
if (_errorMessage != null) ...[
|
|
262
|
+
Card(
|
|
263
|
+
color: Theme.of(context).colorScheme.errorContainer,
|
|
264
|
+
child: Padding(
|
|
265
|
+
padding: const EdgeInsets.all(16),
|
|
266
|
+
child: Row(
|
|
267
|
+
children: [
|
|
268
|
+
Icon(
|
|
269
|
+
Icons.error_outline,
|
|
270
|
+
color: Theme.of(context).colorScheme.error,
|
|
271
|
+
),
|
|
272
|
+
const SizedBox(width: 8),
|
|
273
|
+
Expanded(
|
|
274
|
+
child: Text(
|
|
275
|
+
_errorMessage!,
|
|
276
|
+
style: TextStyle(
|
|
277
|
+
color: Theme.of(context).colorScheme.error,
|
|
278
|
+
),
|
|
279
|
+
),
|
|
280
|
+
),
|
|
281
|
+
IconButton(
|
|
282
|
+
icon: const Icon(Icons.close),
|
|
283
|
+
onPressed: () {
|
|
284
|
+
setState(() => _errorMessage = null);
|
|
285
|
+
},
|
|
286
|
+
),
|
|
287
|
+
],
|
|
288
|
+
),
|
|
289
|
+
),
|
|
290
|
+
),
|
|
291
|
+
const SizedBox(height: 16),
|
|
292
|
+
],
|
|
293
|
+
if (_successMessage != null) ...[
|
|
294
|
+
Card(
|
|
295
|
+
color: Colors.green.shade50,
|
|
296
|
+
child: Padding(
|
|
297
|
+
padding: const EdgeInsets.all(16),
|
|
298
|
+
child: Row(
|
|
299
|
+
children: [
|
|
300
|
+
const Icon(Icons.check_circle, color: Colors.green),
|
|
301
|
+
const SizedBox(width: 8),
|
|
302
|
+
Expanded(
|
|
303
|
+
child: Text(
|
|
304
|
+
_successMessage!,
|
|
305
|
+
style: const TextStyle(color: Colors.green),
|
|
306
|
+
),
|
|
307
|
+
),
|
|
308
|
+
IconButton(
|
|
309
|
+
icon: const Icon(Icons.close),
|
|
310
|
+
onPressed: () {
|
|
311
|
+
setState(() => _successMessage = null);
|
|
312
|
+
},
|
|
313
|
+
),
|
|
314
|
+
],
|
|
315
|
+
),
|
|
316
|
+
),
|
|
317
|
+
),
|
|
318
|
+
const SizedBox(height: 16),
|
|
319
|
+
],
|
|
320
|
+
|
|
321
|
+
// Current Status
|
|
322
|
+
Card(
|
|
323
|
+
child: Padding(
|
|
324
|
+
padding: const EdgeInsets.all(16),
|
|
325
|
+
child: Column(
|
|
326
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
327
|
+
children: [
|
|
328
|
+
Row(
|
|
329
|
+
children: [
|
|
330
|
+
Icon(
|
|
331
|
+
_currentMode == 'ap'
|
|
332
|
+
? Icons.wifi_tethering
|
|
333
|
+
: Icons.wifi,
|
|
334
|
+
color: Theme.of(context).colorScheme.primary,
|
|
335
|
+
),
|
|
336
|
+
const SizedBox(width: 8),
|
|
337
|
+
Text(
|
|
338
|
+
'Current Status',
|
|
339
|
+
style: Theme.of(context).textTheme.titleLarge,
|
|
340
|
+
),
|
|
341
|
+
],
|
|
342
|
+
),
|
|
343
|
+
const SizedBox(height: 16),
|
|
344
|
+
_buildStatusRow(
|
|
345
|
+
'Mode',
|
|
346
|
+
_getModeDisplay(_currentMode),
|
|
347
|
+
),
|
|
348
|
+
const SizedBox(height: 8),
|
|
349
|
+
if (_currentSsid != null)
|
|
350
|
+
_buildStatusRow('Network', _currentSsid!),
|
|
351
|
+
const SizedBox(height: 8),
|
|
352
|
+
_buildStatusRow(
|
|
353
|
+
'Status',
|
|
354
|
+
_isConnected ? 'Connected' : 'Disconnected',
|
|
355
|
+
statusColor: _isConnected
|
|
356
|
+
? Colors.green
|
|
357
|
+
: Colors.grey,
|
|
358
|
+
),
|
|
359
|
+
],
|
|
360
|
+
),
|
|
361
|
+
),
|
|
362
|
+
),
|
|
363
|
+
const SizedBox(height: 24),
|
|
364
|
+
|
|
365
|
+
// Mode Selection Tabs
|
|
366
|
+
DefaultTabController(
|
|
367
|
+
length: 2,
|
|
368
|
+
child: Column(
|
|
369
|
+
children: [
|
|
370
|
+
TabBar(
|
|
371
|
+
tabs: const [
|
|
372
|
+
Tab(
|
|
373
|
+
icon: Icon(Icons.wifi),
|
|
374
|
+
text: 'Connect to Network',
|
|
375
|
+
),
|
|
376
|
+
Tab(
|
|
377
|
+
icon: Icon(Icons.wifi_tethering),
|
|
378
|
+
text: 'Create Hotspot',
|
|
379
|
+
),
|
|
380
|
+
],
|
|
381
|
+
),
|
|
382
|
+
SizedBox(
|
|
383
|
+
height: 500,
|
|
384
|
+
child: TabBarView(
|
|
385
|
+
children: [
|
|
386
|
+
_buildClientModeTab(),
|
|
387
|
+
_buildApModeTab(),
|
|
388
|
+
],
|
|
389
|
+
),
|
|
390
|
+
),
|
|
391
|
+
],
|
|
392
|
+
),
|
|
393
|
+
),
|
|
394
|
+
],
|
|
395
|
+
),
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
Widget _buildStatusRow(String label, String value, {Color? statusColor}) {
|
|
401
|
+
return Row(
|
|
402
|
+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
403
|
+
children: [
|
|
404
|
+
Text(
|
|
405
|
+
label,
|
|
406
|
+
style: Theme.of(
|
|
407
|
+
context,
|
|
408
|
+
).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
|
|
409
|
+
),
|
|
410
|
+
Text(
|
|
411
|
+
value,
|
|
412
|
+
style: Theme.of(
|
|
413
|
+
context,
|
|
414
|
+
).textTheme.bodyMedium?.copyWith(color: statusColor),
|
|
415
|
+
),
|
|
416
|
+
],
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
String _getModeDisplay(String mode) {
|
|
421
|
+
switch (mode) {
|
|
422
|
+
case 'ap':
|
|
423
|
+
return 'Access Point (Hotspot)';
|
|
424
|
+
case 'client':
|
|
425
|
+
return 'Client (Connected)';
|
|
426
|
+
default:
|
|
427
|
+
return 'Unknown';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
Widget _buildClientModeTab() {
|
|
432
|
+
return Padding(
|
|
433
|
+
padding: const EdgeInsets.all(16),
|
|
434
|
+
child: Column(
|
|
435
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
436
|
+
children: [
|
|
437
|
+
FilledButton.icon(
|
|
438
|
+
onPressed: _isScanning ? null : _scanNetworks,
|
|
439
|
+
icon: _isScanning
|
|
440
|
+
? const SizedBox(
|
|
441
|
+
width: 16,
|
|
442
|
+
height: 16,
|
|
443
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
444
|
+
)
|
|
445
|
+
: const Icon(Icons.refresh),
|
|
446
|
+
label: Text(_isScanning ? 'Scanning...' : 'Scan for Networks'),
|
|
447
|
+
),
|
|
448
|
+
const SizedBox(height: 16),
|
|
449
|
+
if (_networks.isEmpty && !_isScanning) ...[
|
|
450
|
+
Center(
|
|
451
|
+
child: Column(
|
|
452
|
+
children: [
|
|
453
|
+
Icon(
|
|
454
|
+
Icons.wifi_off,
|
|
455
|
+
size: 64,
|
|
456
|
+
color: Theme.of(context).colorScheme.outline,
|
|
457
|
+
),
|
|
458
|
+
const SizedBox(height: 16),
|
|
459
|
+
Text(
|
|
460
|
+
'No networks found',
|
|
461
|
+
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
462
|
+
color: Theme.of(context).colorScheme.outline,
|
|
463
|
+
),
|
|
464
|
+
),
|
|
465
|
+
const SizedBox(height: 8),
|
|
466
|
+
Text(
|
|
467
|
+
'Tap the button above to scan',
|
|
468
|
+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
469
|
+
color: Theme.of(context).colorScheme.outline,
|
|
470
|
+
),
|
|
471
|
+
),
|
|
472
|
+
],
|
|
473
|
+
),
|
|
474
|
+
),
|
|
475
|
+
] else ...[
|
|
476
|
+
Expanded(
|
|
477
|
+
child: ListView.builder(
|
|
478
|
+
itemCount: _networks.length,
|
|
479
|
+
itemBuilder: (context, index) {
|
|
480
|
+
final network = _networks[index];
|
|
481
|
+
return Card(
|
|
482
|
+
child: ListTile(
|
|
483
|
+
leading: Icon(
|
|
484
|
+
_getSignalIcon(network.signal),
|
|
485
|
+
color: network.connected
|
|
486
|
+
? Colors.green
|
|
487
|
+
: Theme.of(context).colorScheme.primary,
|
|
488
|
+
),
|
|
489
|
+
title: Row(
|
|
490
|
+
children: [
|
|
491
|
+
Expanded(child: Text(network.ssid)),
|
|
492
|
+
if (network.secured)
|
|
493
|
+
Icon(
|
|
494
|
+
Icons.lock,
|
|
495
|
+
size: 16,
|
|
496
|
+
color: Theme.of(context).colorScheme.outline,
|
|
497
|
+
),
|
|
498
|
+
if (network.connected)
|
|
499
|
+
Padding(
|
|
500
|
+
padding: const EdgeInsets.only(left: 8),
|
|
501
|
+
child: Chip(
|
|
502
|
+
label: const Text('Connected'),
|
|
503
|
+
backgroundColor: Colors.green.shade100,
|
|
504
|
+
labelStyle: const TextStyle(
|
|
505
|
+
color: Colors.green,
|
|
506
|
+
fontSize: 12,
|
|
507
|
+
),
|
|
508
|
+
),
|
|
509
|
+
),
|
|
510
|
+
],
|
|
511
|
+
),
|
|
512
|
+
subtitle: Text(
|
|
513
|
+
'Signal: ${network.signal}% • ${network.security}',
|
|
514
|
+
style: Theme.of(context).textTheme.bodySmall,
|
|
515
|
+
),
|
|
516
|
+
trailing: network.connected
|
|
517
|
+
? null
|
|
518
|
+
: IconButton(
|
|
519
|
+
icon: const Icon(Icons.arrow_forward),
|
|
520
|
+
onPressed: () => _showPasswordDialog(network),
|
|
521
|
+
),
|
|
522
|
+
),
|
|
523
|
+
);
|
|
524
|
+
},
|
|
525
|
+
),
|
|
526
|
+
),
|
|
527
|
+
],
|
|
528
|
+
],
|
|
529
|
+
),
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
Widget _buildApModeTab() {
|
|
534
|
+
return Padding(
|
|
535
|
+
padding: const EdgeInsets.all(16),
|
|
536
|
+
child: SingleChildScrollView(
|
|
537
|
+
child: Column(
|
|
538
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
539
|
+
children: [
|
|
540
|
+
Text(
|
|
541
|
+
'Create your own WiFi hotspot',
|
|
542
|
+
style: Theme.of(context).textTheme.titleMedium,
|
|
543
|
+
),
|
|
544
|
+
const SizedBox(height: 8),
|
|
545
|
+
Text(
|
|
546
|
+
'Other devices will be able to connect to this network',
|
|
547
|
+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
548
|
+
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
549
|
+
),
|
|
550
|
+
),
|
|
551
|
+
const SizedBox(height: 24),
|
|
552
|
+
TextField(
|
|
553
|
+
controller: _apSsidController,
|
|
554
|
+
decoration: const InputDecoration(
|
|
555
|
+
labelText: 'Network Name (SSID)',
|
|
556
|
+
hintText: 'pumaguard',
|
|
557
|
+
border: OutlineInputBorder(),
|
|
558
|
+
helperText: 'The name others will see',
|
|
559
|
+
),
|
|
560
|
+
),
|
|
561
|
+
const SizedBox(height: 16),
|
|
562
|
+
TextField(
|
|
563
|
+
controller: _apPasswordController,
|
|
564
|
+
obscureText: _obscurePassword,
|
|
565
|
+
decoration: InputDecoration(
|
|
566
|
+
labelText: 'Password (optional)',
|
|
567
|
+
hintText: 'Leave empty for open network',
|
|
568
|
+
border: const OutlineInputBorder(),
|
|
569
|
+
helperText: 'Minimum 8 characters if set',
|
|
570
|
+
suffixIcon: IconButton(
|
|
571
|
+
icon: Icon(
|
|
572
|
+
_obscurePassword ? Icons.visibility : Icons.visibility_off,
|
|
573
|
+
),
|
|
574
|
+
onPressed: () {
|
|
575
|
+
setState(() {
|
|
576
|
+
_obscurePassword = !_obscurePassword;
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
),
|
|
580
|
+
),
|
|
581
|
+
),
|
|
582
|
+
const SizedBox(height: 24),
|
|
583
|
+
FilledButton.icon(
|
|
584
|
+
onPressed: _isLoading ? null : _enableApMode,
|
|
585
|
+
icon: _isLoading
|
|
586
|
+
? const SizedBox(
|
|
587
|
+
width: 16,
|
|
588
|
+
height: 16,
|
|
589
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
590
|
+
)
|
|
591
|
+
: const Icon(Icons.wifi_tethering),
|
|
592
|
+
label: const Text('Enable Hotspot'),
|
|
593
|
+
),
|
|
594
|
+
const SizedBox(height: 16),
|
|
595
|
+
Card(
|
|
596
|
+
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
597
|
+
child: Padding(
|
|
598
|
+
padding: const EdgeInsets.all(16),
|
|
599
|
+
child: Column(
|
|
600
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
601
|
+
children: [
|
|
602
|
+
Row(
|
|
603
|
+
children: [
|
|
604
|
+
Icon(
|
|
605
|
+
Icons.info_outline,
|
|
606
|
+
size: 20,
|
|
607
|
+
color: Theme.of(context).colorScheme.primary,
|
|
608
|
+
),
|
|
609
|
+
const SizedBox(width: 8),
|
|
610
|
+
Text(
|
|
611
|
+
'Information',
|
|
612
|
+
style: Theme.of(context).textTheme.titleSmall,
|
|
613
|
+
),
|
|
614
|
+
],
|
|
615
|
+
),
|
|
616
|
+
const SizedBox(height: 8),
|
|
617
|
+
Text(
|
|
618
|
+
'• Hotspot mode allows cameras and other devices to connect directly to this device\n'
|
|
619
|
+
'• You will not have internet access in hotspot mode\n'
|
|
620
|
+
'• The device IP will be 192.168.52.1\n'
|
|
621
|
+
'• To switch back, use the "Connect to Network" tab',
|
|
622
|
+
style: Theme.of(context).textTheme.bodySmall,
|
|
623
|
+
),
|
|
624
|
+
],
|
|
625
|
+
),
|
|
626
|
+
),
|
|
627
|
+
),
|
|
628
|
+
],
|
|
629
|
+
),
|
|
630
|
+
),
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
IconData _getSignalIcon(int signal) {
|
|
635
|
+
if (signal >= 75) {
|
|
636
|
+
return Icons.signal_wifi_4_bar;
|
|
637
|
+
} else if (signal >= 50) {
|
|
638
|
+
return Icons.network_wifi_3_bar;
|
|
639
|
+
} else if (signal >= 25) {
|
|
640
|
+
return Icons.network_wifi_2_bar;
|
|
641
|
+
} else {
|
|
642
|
+
return Icons.network_wifi_1_bar;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
class WifiNetwork {
|
|
648
|
+
final String ssid;
|
|
649
|
+
final int signal;
|
|
650
|
+
final String security;
|
|
651
|
+
final bool secured;
|
|
652
|
+
final bool connected;
|
|
653
|
+
|
|
654
|
+
WifiNetwork({
|
|
655
|
+
required this.ssid,
|
|
656
|
+
required this.signal,
|
|
657
|
+
required this.security,
|
|
658
|
+
required this.secured,
|
|
659
|
+
required this.connected,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
factory WifiNetwork.fromJson(Map<String, dynamic> json) {
|
|
663
|
+
return WifiNetwork(
|
|
664
|
+
ssid: json['ssid'] as String,
|
|
665
|
+
signal: json['signal'] as int,
|
|
666
|
+
security: json['security'] as String,
|
|
667
|
+
secured: json['secured'] as bool,
|
|
668
|
+
connected: json['connected'] as bool? ?? false,
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
}
|