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.
Files changed (188) hide show
  1. pumaguard/presets.py +1 -0
  2. pumaguard/pumaguard-ui/.last_build_id +1 -1
  3. pumaguard/pumaguard-ui/assets/NOTICES +621 -71
  4. pumaguard/pumaguard-ui/assets/fonts/MaterialIcons-Regular.otf +0 -0
  5. pumaguard/pumaguard-ui/flutter_bootstrap.js +1 -1
  6. pumaguard/pumaguard-ui/main.dart.js +28869 -28787
  7. pumaguard/web_routes/dhcp.py +311 -54
  8. pumaguard/web_routes/diagnostics.py +6 -0
  9. pumaguard/web_routes/settings.py +13 -0
  10. pumaguard/web_ui.py +29 -0
  11. {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/METADATA +1 -1
  12. pumaguard-21.post83.dist-info/RECORD +254 -0
  13. pumaguard-ui/.gitignore +48 -0
  14. pumaguard-ui/.metadata +45 -0
  15. pumaguard-ui/API_REFERENCE.md +717 -0
  16. pumaguard-ui/LICENSE +201 -0
  17. pumaguard-ui/Makefile +36 -0
  18. pumaguard-ui/README.md +371 -0
  19. pumaguard-ui/UI_DEVELOPMENT_CONTEXT.md +427 -0
  20. pumaguard-ui/analysis_options.yaml +28 -0
  21. pumaguard-ui/android/.gitignore +14 -0
  22. pumaguard-ui/android/app/build.gradle.kts +44 -0
  23. pumaguard-ui/android/app/src/debug/AndroidManifest.xml +7 -0
  24. pumaguard-ui/android/app/src/main/AndroidManifest.xml +45 -0
  25. pumaguard-ui/android/app/src/main/kotlin/com/example/pumaguard_ui/MainActivity.kt +5 -0
  26. pumaguard-ui/android/app/src/main/res/drawable/launch_background.xml +12 -0
  27. pumaguard-ui/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  28. pumaguard-ui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  29. pumaguard-ui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  30. pumaguard-ui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  31. pumaguard-ui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  32. pumaguard-ui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  33. pumaguard-ui/android/app/src/main/res/values/styles.xml +18 -0
  34. pumaguard-ui/android/app/src/main/res/values-night/styles.xml +18 -0
  35. pumaguard-ui/android/app/src/profile/AndroidManifest.xml +7 -0
  36. pumaguard-ui/android/build.gradle.kts +24 -0
  37. pumaguard-ui/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  38. pumaguard-ui/android/gradle.properties +2 -0
  39. pumaguard-ui/android/settings.gradle.kts +26 -0
  40. pumaguard-ui/fonts/README.md +38 -0
  41. pumaguard-ui/fonts/Roboto-Bold.ttf +0 -0
  42. pumaguard-ui/fonts/Roboto-Light.ttf +0 -0
  43. pumaguard-ui/fonts/Roboto-Medium.ttf +0 -0
  44. pumaguard-ui/fonts/Roboto-Regular.ttf +0 -0
  45. pumaguard-ui/fonts/RobotoMono-Bold.ttf +0 -0
  46. pumaguard-ui/fonts/RobotoMono-Medium.ttf +0 -0
  47. pumaguard-ui/fonts/RobotoMono-Regular.ttf +0 -0
  48. pumaguard-ui/fonts/download_fonts.sh +76 -0
  49. pumaguard-ui/ios/.gitignore +34 -0
  50. pumaguard-ui/ios/Flutter/AppFrameworkInfo.plist +26 -0
  51. pumaguard-ui/ios/Flutter/Debug.xcconfig +1 -0
  52. pumaguard-ui/ios/Flutter/Release.xcconfig +1 -0
  53. pumaguard-ui/ios/Runner/AppDelegate.swift +13 -0
  54. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  55. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  56. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  57. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  58. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  59. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  60. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  61. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  62. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  63. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  64. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  65. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  66. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  67. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  68. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  69. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  70. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
  71. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  72. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  73. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  74. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
  75. pumaguard-ui/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
  76. pumaguard-ui/ios/Runner/Base.lproj/Main.storyboard +26 -0
  77. pumaguard-ui/ios/Runner/Info.plist +49 -0
  78. pumaguard-ui/ios/Runner/Runner-Bridging-Header.h +1 -0
  79. pumaguard-ui/ios/Runner.xcodeproj/project.pbxproj +616 -0
  80. pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  81. pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  82. pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  83. pumaguard-ui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
  84. pumaguard-ui/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  85. pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  86. pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  87. pumaguard-ui/ios/RunnerTests/RunnerTests.swift +12 -0
  88. pumaguard-ui/lib/main.dart +56 -0
  89. pumaguard-ui/lib/models/camera.dart +45 -0
  90. pumaguard-ui/lib/models/plug.dart +45 -0
  91. pumaguard-ui/lib/models/settings.dart +112 -0
  92. pumaguard-ui/lib/models/status.dart +58 -0
  93. pumaguard-ui/lib/screens/directories_screen.dart +319 -0
  94. pumaguard-ui/lib/screens/home_screen.dart +545 -0
  95. pumaguard-ui/lib/screens/image_browser_screen.dart +1248 -0
  96. pumaguard-ui/lib/screens/server_discovery_screen.dart +390 -0
  97. pumaguard-ui/lib/screens/settings_screen.dart +1162 -0
  98. pumaguard-ui/lib/screens/wifi_settings_screen.dart +671 -0
  99. pumaguard-ui/lib/services/api_service.dart +717 -0
  100. pumaguard-ui/lib/services/camera_events_service.dart +195 -0
  101. pumaguard-ui/lib/services/mdns_service.dart +4 -0
  102. pumaguard-ui/lib/services/mdns_service_impl.dart +282 -0
  103. pumaguard-ui/lib/services/mdns_service_io.dart +1 -0
  104. pumaguard-ui/lib/services/mdns_service_web.dart +106 -0
  105. pumaguard-ui/lib/utils/download_helper.dart +2 -0
  106. pumaguard-ui/lib/utils/download_helper_stub.dart +6 -0
  107. pumaguard-ui/lib/utils/download_helper_web.dart +14 -0
  108. pumaguard-ui/lib/utils/platform_url.dart +10 -0
  109. pumaguard-ui/lib/utils/platform_url_stub.dart +11 -0
  110. pumaguard-ui/lib/utils/platform_url_web.dart +16 -0
  111. pumaguard-ui/linux/.gitignore +1 -0
  112. pumaguard-ui/linux/CMakeLists.txt +128 -0
  113. pumaguard-ui/linux/flutter/CMakeLists.txt +88 -0
  114. pumaguard-ui/linux/flutter/generated_plugin_registrant.cc +15 -0
  115. pumaguard-ui/linux/flutter/generated_plugin_registrant.h +15 -0
  116. pumaguard-ui/linux/flutter/generated_plugins.cmake +24 -0
  117. pumaguard-ui/linux/runner/CMakeLists.txt +26 -0
  118. pumaguard-ui/linux/runner/main.cc +6 -0
  119. pumaguard-ui/linux/runner/my_application.cc +148 -0
  120. pumaguard-ui/linux/runner/my_application.h +21 -0
  121. pumaguard-ui/macos/.gitignore +7 -0
  122. pumaguard-ui/macos/Flutter/Flutter-Debug.xcconfig +1 -0
  123. pumaguard-ui/macos/Flutter/Flutter-Release.xcconfig +1 -0
  124. pumaguard-ui/macos/Flutter/GeneratedPluginRegistrant.swift +16 -0
  125. pumaguard-ui/macos/Runner/AppDelegate.swift +13 -0
  126. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  127. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  128. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  129. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  130. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  131. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  132. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  133. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  134. pumaguard-ui/macos/Runner/Base.lproj/MainMenu.xib +343 -0
  135. pumaguard-ui/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  136. pumaguard-ui/macos/Runner/Configs/Debug.xcconfig +2 -0
  137. pumaguard-ui/macos/Runner/Configs/Release.xcconfig +2 -0
  138. pumaguard-ui/macos/Runner/Configs/Warnings.xcconfig +13 -0
  139. pumaguard-ui/macos/Runner/DebugProfile.entitlements +12 -0
  140. pumaguard-ui/macos/Runner/Info.plist +32 -0
  141. pumaguard-ui/macos/Runner/MainFlutterWindow.swift +15 -0
  142. pumaguard-ui/macos/Runner/Release.entitlements +8 -0
  143. pumaguard-ui/macos/Runner.xcodeproj/project.pbxproj +705 -0
  144. pumaguard-ui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  145. pumaguard-ui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  146. pumaguard-ui/macos/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  147. pumaguard-ui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  148. pumaguard-ui/macos/RunnerTests/RunnerTests.swift +12 -0
  149. pumaguard-ui/pubspec.lock +882 -0
  150. pumaguard-ui/pubspec.yaml +125 -0
  151. pumaguard-ui/test/models/camera_test.dart +515 -0
  152. pumaguard-ui/test/models/plug_test.dart +499 -0
  153. pumaguard-ui/test/models/settings_test.dart +903 -0
  154. pumaguard-ui/test/models/status_test.dart +707 -0
  155. pumaguard-ui/test/screens/image_browser_grouping_test.dart +555 -0
  156. pumaguard-ui/test/services/api_service_cameras_test.dart +580 -0
  157. pumaguard-ui/test/services/api_service_image_browser_test.dart +512 -0
  158. pumaguard-ui/test/widget_test.dart.skip +38 -0
  159. pumaguard-ui/web/favicon.png +0 -0
  160. pumaguard-ui/web/icons/Icon-192.png +0 -0
  161. pumaguard-ui/web/icons/Icon-512.png +0 -0
  162. pumaguard-ui/web/icons/Icon-maskable-192.png +0 -0
  163. pumaguard-ui/web/icons/Icon-maskable-512.png +0 -0
  164. pumaguard-ui/web/index.html +38 -0
  165. pumaguard-ui/web/manifest.json +35 -0
  166. pumaguard-ui/windows/.gitignore +17 -0
  167. pumaguard-ui/windows/CMakeLists.txt +108 -0
  168. pumaguard-ui/windows/flutter/CMakeLists.txt +109 -0
  169. pumaguard-ui/windows/flutter/generated_plugin_registrant.cc +14 -0
  170. pumaguard-ui/windows/flutter/generated_plugin_registrant.h +15 -0
  171. pumaguard-ui/windows/flutter/generated_plugins.cmake +24 -0
  172. pumaguard-ui/windows/runner/CMakeLists.txt +40 -0
  173. pumaguard-ui/windows/runner/Runner.rc +121 -0
  174. pumaguard-ui/windows/runner/flutter_window.cpp +71 -0
  175. pumaguard-ui/windows/runner/flutter_window.h +33 -0
  176. pumaguard-ui/windows/runner/main.cpp +43 -0
  177. pumaguard-ui/windows/runner/resource.h +16 -0
  178. pumaguard-ui/windows/runner/resources/app_icon.ico +0 -0
  179. pumaguard-ui/windows/runner/runner.exe.manifest +14 -0
  180. pumaguard-ui/windows/runner/utils.cpp +65 -0
  181. pumaguard-ui/windows/runner/utils.h +19 -0
  182. pumaguard-ui/windows/runner/win32_window.cpp +288 -0
  183. pumaguard-ui/windows/runner/win32_window.h +102 -0
  184. pumaguard-21.post27.dist-info/RECORD +0 -83
  185. {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/WHEEL +0 -0
  186. {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/entry_points.txt +0 -0
  187. {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/licenses/LICENSE +0 -0
  188. {pumaguard-21.post27.dist-info → pumaguard-21.post83.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,390 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:provider/provider.dart';
3
+ import '../services/api_service.dart';
4
+ import '../services/mdns_service.dart';
5
+
6
+ class ServerDiscoveryScreen extends StatefulWidget {
7
+ const ServerDiscoveryScreen({super.key});
8
+
9
+ @override
10
+ State<ServerDiscoveryScreen> createState() => _ServerDiscoveryScreenState();
11
+ }
12
+
13
+ class _ServerDiscoveryScreenState extends State<ServerDiscoveryScreen> {
14
+ final MdnsService _mdnsService = MdnsService();
15
+ final TextEditingController _hostnameController = TextEditingController();
16
+ bool _isDiscovering = false;
17
+ String? _statusMessage;
18
+ String? _manualUrl;
19
+
20
+ @override
21
+ void initState() {
22
+ super.initState();
23
+ _startDiscovery();
24
+ }
25
+
26
+ @override
27
+ void dispose() {
28
+ _mdnsService.dispose();
29
+ _hostnameController.dispose();
30
+ super.dispose();
31
+ }
32
+
33
+ Future<void> _startDiscovery() async {
34
+ setState(() {
35
+ _isDiscovering = true;
36
+ _statusMessage = 'Searching for Pumaguard servers...';
37
+ });
38
+
39
+ try {
40
+ await _mdnsService.discoverServers();
41
+ setState(() {
42
+ _isDiscovering = false;
43
+ _statusMessage = _mdnsService.servers.isEmpty
44
+ ? 'No servers found. Make sure mDNS is enabled on the server.'
45
+ : 'Found ${_mdnsService.servers.length} server(s)';
46
+ });
47
+ } catch (e) {
48
+ setState(() {
49
+ _isDiscovering = false;
50
+ _statusMessage = 'Discovery failed: $e';
51
+ });
52
+ }
53
+ }
54
+
55
+ Future<void> _connectToServer(PumaguardServer server) async {
56
+ final apiService = context.read<ApiService>();
57
+
58
+ // Show connecting dialog
59
+ if (!mounted) return;
60
+ showDialog(
61
+ context: context,
62
+ barrierDismissible: false,
63
+ builder: (context) => const AlertDialog(
64
+ content: Row(
65
+ children: [
66
+ CircularProgressIndicator(),
67
+ SizedBox(width: 16),
68
+ Text('Connecting...'),
69
+ ],
70
+ ),
71
+ ),
72
+ );
73
+
74
+ try {
75
+ // Update API base URL
76
+ apiService.setBaseUrl(server.baseUrl);
77
+
78
+ // Test connection
79
+ await apiService.getStatus();
80
+
81
+ if (!mounted) return;
82
+ Navigator.of(context).pop(); // Close connecting dialog
83
+ Navigator.of(context).pop(); // Return to previous screen
84
+
85
+ ScaffoldMessenger.of(context).showSnackBar(
86
+ SnackBar(
87
+ content: Text('Connected to ${server.name}'),
88
+ backgroundColor: Colors.green,
89
+ ),
90
+ );
91
+ } catch (e) {
92
+ if (!mounted) return;
93
+ Navigator.of(context).pop(); // Close connecting dialog
94
+
95
+ showDialog(
96
+ context: context,
97
+ builder: (context) => AlertDialog(
98
+ title: const Text('Connection Failed'),
99
+ content: Text('Could not connect to ${server.name}: $e'),
100
+ actions: [
101
+ TextButton(
102
+ onPressed: () => Navigator.of(context).pop(),
103
+ child: const Text('OK'),
104
+ ),
105
+ ],
106
+ ),
107
+ );
108
+ }
109
+ }
110
+
111
+ Future<void> _resolveAndConnect() async {
112
+ final hostname = _hostnameController.text.trim();
113
+ if (hostname.isEmpty) {
114
+ ScaffoldMessenger.of(
115
+ context,
116
+ ).showSnackBar(const SnackBar(content: Text('Please enter a hostname')));
117
+ return;
118
+ }
119
+
120
+ setState(() {
121
+ _statusMessage = 'Resolving $hostname...';
122
+ });
123
+
124
+ try {
125
+ final ip = await _mdnsService.resolveLocalHostname(hostname);
126
+ if (ip == null) {
127
+ setState(() {
128
+ _statusMessage = 'Could not resolve $hostname';
129
+ });
130
+ return;
131
+ }
132
+
133
+ final server = PumaguardServer(
134
+ name: hostname.replaceAll('.local', ''),
135
+ hostname: hostname,
136
+ ip: ip,
137
+ port: 5000, // Default port
138
+ properties: {},
139
+ );
140
+
141
+ await _connectToServer(server);
142
+ } catch (e) {
143
+ setState(() {
144
+ _statusMessage = 'Error: $e';
145
+ });
146
+ }
147
+ }
148
+
149
+ Future<void> _connectManual() async {
150
+ if (_manualUrl == null || _manualUrl!.isEmpty) {
151
+ ScaffoldMessenger.of(
152
+ context,
153
+ ).showSnackBar(const SnackBar(content: Text('Please enter a URL')));
154
+ return;
155
+ }
156
+
157
+ final apiService = context.read<ApiService>();
158
+
159
+ showDialog(
160
+ context: context,
161
+ barrierDismissible: false,
162
+ builder: (context) => const AlertDialog(
163
+ content: Row(
164
+ children: [
165
+ CircularProgressIndicator(),
166
+ SizedBox(width: 16),
167
+ Text('Connecting...'),
168
+ ],
169
+ ),
170
+ ),
171
+ );
172
+
173
+ try {
174
+ apiService.setBaseUrl(_manualUrl!);
175
+ await apiService.getStatus();
176
+
177
+ if (!mounted) return;
178
+ Navigator.of(context).pop(); // Close connecting dialog
179
+ Navigator.of(context).pop(); // Return to previous screen
180
+
181
+ ScaffoldMessenger.of(context).showSnackBar(
182
+ const SnackBar(
183
+ content: Text('Connected successfully'),
184
+ backgroundColor: Colors.green,
185
+ ),
186
+ );
187
+ } catch (e) {
188
+ if (!mounted) return;
189
+ Navigator.of(context).pop(); // Close connecting dialog
190
+
191
+ showDialog(
192
+ context: context,
193
+ builder: (context) => AlertDialog(
194
+ title: const Text('Connection Failed'),
195
+ content: Text('Could not connect: $e'),
196
+ actions: [
197
+ TextButton(
198
+ onPressed: () => Navigator.of(context).pop(),
199
+ child: const Text('OK'),
200
+ ),
201
+ ],
202
+ ),
203
+ );
204
+ }
205
+ }
206
+
207
+ @override
208
+ Widget build(BuildContext context) {
209
+ return Scaffold(
210
+ appBar: AppBar(
211
+ title: const Text('Server Discovery'),
212
+ actions: [
213
+ IconButton(
214
+ icon: const Icon(Icons.refresh),
215
+ onPressed: _isDiscovering ? null : _startDiscovery,
216
+ tooltip: 'Refresh',
217
+ ),
218
+ ],
219
+ ),
220
+ body: SingleChildScrollView(
221
+ padding: const EdgeInsets.all(16.0),
222
+ child: Column(
223
+ crossAxisAlignment: CrossAxisAlignment.stretch,
224
+ children: [
225
+ // Status message
226
+ if (_statusMessage != null)
227
+ Card(
228
+ child: Padding(
229
+ padding: const EdgeInsets.all(16.0),
230
+ child: Row(
231
+ children: [
232
+ if (_isDiscovering)
233
+ const Padding(
234
+ padding: EdgeInsets.only(right: 16.0),
235
+ child: SizedBox(
236
+ width: 20,
237
+ height: 20,
238
+ child: CircularProgressIndicator(strokeWidth: 2),
239
+ ),
240
+ ),
241
+ Expanded(
242
+ child: Text(
243
+ _statusMessage!,
244
+ style: Theme.of(context).textTheme.bodyMedium,
245
+ ),
246
+ ),
247
+ ],
248
+ ),
249
+ ),
250
+ ),
251
+ const SizedBox(height: 16),
252
+
253
+ // Discovered servers
254
+ if (_mdnsService.servers.isNotEmpty) ...[
255
+ Text(
256
+ 'Discovered Servers',
257
+ style: Theme.of(context).textTheme.titleLarge,
258
+ ),
259
+ const SizedBox(height: 8),
260
+ ...(_mdnsService.servers.map((server) {
261
+ return Card(
262
+ child: ListTile(
263
+ leading: const Icon(Icons.computer, size: 40),
264
+ title: Text(
265
+ server.name,
266
+ style: const TextStyle(fontWeight: FontWeight.bold),
267
+ ),
268
+ subtitle: Column(
269
+ crossAxisAlignment: CrossAxisAlignment.start,
270
+ children: [
271
+ Text('IP: ${server.ip}:${server.port}'),
272
+ Text('Hostname: ${server.hostname}'),
273
+ if (server.properties['version'] != null)
274
+ Text('Version: ${server.properties['version']}'),
275
+ ],
276
+ ),
277
+ trailing: ElevatedButton(
278
+ onPressed: () => _connectToServer(server),
279
+ child: const Text('Connect'),
280
+ ),
281
+ isThreeLine: true,
282
+ ),
283
+ );
284
+ })),
285
+ const SizedBox(height: 24),
286
+ ],
287
+
288
+ // Manual hostname entry
289
+ Text(
290
+ 'Connect by Hostname',
291
+ style: Theme.of(context).textTheme.titleLarge,
292
+ ),
293
+ const SizedBox(height: 8),
294
+ Card(
295
+ child: Padding(
296
+ padding: const EdgeInsets.all(16.0),
297
+ child: Column(
298
+ crossAxisAlignment: CrossAxisAlignment.stretch,
299
+ children: [
300
+ TextField(
301
+ controller: _hostnameController,
302
+ decoration: const InputDecoration(
303
+ labelText: 'Hostname',
304
+ hintText: 'pumaguard.local',
305
+ helperText: 'Enter .local hostname or IP address',
306
+ border: OutlineInputBorder(),
307
+ ),
308
+ ),
309
+ const SizedBox(height: 12),
310
+ ElevatedButton.icon(
311
+ onPressed: _resolveAndConnect,
312
+ icon: const Icon(Icons.search),
313
+ label: const Text('Resolve & Connect'),
314
+ ),
315
+ ],
316
+ ),
317
+ ),
318
+ ),
319
+ const SizedBox(height: 24),
320
+
321
+ // Manual URL entry
322
+ Text(
323
+ 'Manual Connection',
324
+ style: Theme.of(context).textTheme.titleLarge,
325
+ ),
326
+ const SizedBox(height: 8),
327
+ Card(
328
+ child: Padding(
329
+ padding: const EdgeInsets.all(16.0),
330
+ child: Column(
331
+ crossAxisAlignment: CrossAxisAlignment.stretch,
332
+ children: [
333
+ TextField(
334
+ decoration: const InputDecoration(
335
+ labelText: 'Server URL',
336
+ hintText: 'http://192.168.1.100:5000',
337
+ helperText: 'Enter full URL with port',
338
+ border: OutlineInputBorder(),
339
+ ),
340
+ onChanged: (value) => _manualUrl = value,
341
+ ),
342
+ const SizedBox(height: 12),
343
+ ElevatedButton.icon(
344
+ onPressed: _connectManual,
345
+ icon: const Icon(Icons.link),
346
+ label: const Text('Connect'),
347
+ ),
348
+ ],
349
+ ),
350
+ ),
351
+ ),
352
+ const SizedBox(height: 24),
353
+
354
+ // Help text
355
+ Card(
356
+ color: Colors.blue.shade50,
357
+ child: Padding(
358
+ padding: const EdgeInsets.all(16.0),
359
+ child: Column(
360
+ crossAxisAlignment: CrossAxisAlignment.start,
361
+ children: [
362
+ Row(
363
+ children: [
364
+ Icon(Icons.info_outline, color: Colors.blue.shade700),
365
+ const SizedBox(width: 8),
366
+ Text(
367
+ 'Help',
368
+ style: Theme.of(context).textTheme.titleMedium
369
+ ?.copyWith(color: Colors.blue.shade700),
370
+ ),
371
+ ],
372
+ ),
373
+ const SizedBox(height: 8),
374
+ const Text(
375
+ '• mDNS discovery works on local networks\n'
376
+ '• Make sure the server has mDNS enabled\n'
377
+ '• Default hostname is "pumaguard.local"\n'
378
+ '• If discovery fails, use manual connection',
379
+ style: TextStyle(fontSize: 13),
380
+ ),
381
+ ],
382
+ ),
383
+ ),
384
+ ),
385
+ ],
386
+ ),
387
+ ),
388
+ );
389
+ }
390
+ }