]>
Commit | Line | Data |
---|---|---|
598a93b4 | 1 | import 'dart:async'; |
686d6881 | 2 | import 'package:flutter/foundation.dart'; |
00a48038 | 3 | import 'package:flutter/material.dart'; |
65098e83 | 4 | import 'package:provider/provider.dart'; |
40d61f6e AL |
5 | import 'package:pve_flutter_frontend/widgets/pve_first_welcome_screen.dart'; |
6 | import 'package:shared_preferences/shared_preferences.dart'; | |
4e71c5d8 | 7 | import 'package:proxmox_login_manager/proxmox_login_manager.dart'; |
00a48038 | 8 | import 'package:pve_flutter_frontend/bloc/pve_authentication_bloc.dart'; |
18a71ae9 | 9 | import 'package:pve_flutter_frontend/bloc/pve_cluster_status_bloc.dart'; |
76e4760f | 10 | import 'package:pve_flutter_frontend/bloc/pve_lxc_overview_bloc.dart'; |
c2387333 | 11 | import 'package:pve_flutter_frontend/bloc/pve_node_overview_bloc.dart'; |
49dcddb1 | 12 | import 'package:pve_flutter_frontend/bloc/pve_qemu_overview_bloc.dart'; |
8285412a | 13 | import 'package:pve_flutter_frontend/bloc/pve_resource_bloc.dart'; |
49dcddb1 | 14 | import 'package:pve_flutter_frontend/bloc/pve_task_log_bloc.dart'; |
00a48038 | 15 | import 'package:pve_flutter_frontend/pages/404_page.dart'; |
00a48038 | 16 | import 'package:pve_flutter_frontend/pages/main_layout_slim.dart'; |
fe1ea690 | 17 | import 'package:pve_flutter_frontend/states/pve_cluster_status_state.dart'; |
76e4760f | 18 | import 'package:pve_flutter_frontend/states/pve_lxc_overview_state.dart'; |
c2387333 | 19 | import 'package:pve_flutter_frontend/states/pve_node_overview_state.dart'; |
49dcddb1 | 20 | import 'package:pve_flutter_frontend/states/pve_qemu_overview_state.dart'; |
23a5b986 | 21 | import 'package:pve_flutter_frontend/states/pve_resource_state.dart'; |
49dcddb1 | 22 | import 'package:pve_flutter_frontend/states/pve_task_log_state.dart'; |
23a5b986 | 23 | import 'package:pve_flutter_frontend/widgets/proxmox_stream_listener.dart'; |
65098e83 TM |
24 | import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart' |
25 | as proxclient; | |
00a48038 | 26 | |
1e2d2401 | 27 | import 'package:pve_flutter_frontend/utils/proxmox_layout_builder.dart'; |
00a48038 | 28 | |
acd1a179 | 29 | import 'package:pve_flutter_frontend/bloc/proxmox_global_error_bloc.dart'; |
76e4760f | 30 | import 'package:pve_flutter_frontend/widgets/pve_lxc_overview.dart'; |
c2387333 | 31 | import 'package:pve_flutter_frontend/widgets/pve_node_overview.dart'; |
49dcddb1 | 32 | import 'package:pve_flutter_frontend/widgets/pve_qemu_overview.dart'; |
d27dff8a | 33 | import 'package:pve_flutter_frontend/widgets/pve_splash_screen.dart'; |
9bb60c76 | 34 | import 'package:pve_flutter_frontend/utils/proxmox_colors.dart'; |
d73c2def | 35 | |
1ba800e6 TL |
36 | import 'package:rxdart/streams.dart' show ValueStreamExtensions; |
37 | ||
f42f863c | 38 | void main() async { |
36a7c57d | 39 | WidgetsFlutterBinding.ensureInitialized(); |
00a48038 | 40 | final authBloc = PveAuthenticationBloc(); |
f42f863c | 41 | try { |
598a93b4 | 42 | final loginStorage = await (ProxmoxLoginStorage.fromLocalStorage() |
38f770d0 TL |
43 | as FutureOr<ProxmoxLoginStorage?>); |
44 | final apiClient = await loginStorage!.recoverLatestSession(); | |
f42f863c | 45 | authBloc.events.add(LoggedIn(apiClient)); |
be84e463 TM |
46 | } catch (e) { |
47 | print(e); | |
f42f863c TM |
48 | authBloc.events.add(LoggedOut()); |
49 | } | |
50 | ||
d73c2def | 51 | ProxmoxGlobalErrorBloc(); |
686d6881 TM |
52 | FlutterError.onError = (FlutterErrorDetails details) { |
53 | FlutterError.dumpErrorToConsole(details); | |
54 | if (kReleaseMode) ProxmoxGlobalErrorBloc().addError(details.exception); | |
55 | }; | |
40d61f6e | 56 | SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); |
23a5b986 | 57 | Provider.debugCheckInvalidValueType = null; |
d73c2def | 58 | |
23a5b986 TM |
59 | runApp( |
60 | MultiProvider( | |
61 | providers: [ | |
62 | Provider.value(value: authBloc), | |
63 | Provider<PveResourceBloc>( | |
64 | create: (context) => PveResourceBloc(init: PveResourceState.init()), | |
65 | dispose: (context, bloc) => bloc.dispose(), | |
66 | ), | |
67 | ], | |
68 | child: MyApp( | |
69 | authbloc: authBloc, | |
40d61f6e | 70 | sharedPreferences: sharedPreferences, |
23a5b986 TM |
71 | ), |
72 | ), | |
73 | ); | |
00a48038 TM |
74 | } |
75 | ||
76 | class MyApp extends StatelessWidget { | |
598a93b4 | 77 | final PveAuthenticationBloc? authbloc; |
40d61f6e | 78 | final SharedPreferences sharedPreferences; |
686d6881 | 79 | final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
00a48038 | 80 | |
598a93b4 | 81 | MyApp({Key? key, this.authbloc, required this.sharedPreferences}) |
40d61f6e AL |
82 | : assert(sharedPreferences != null), |
83 | super(key: key); | |
00a48038 TM |
84 | |
85 | @override | |
86 | Widget build(BuildContext context) { | |
4e71c5d8 | 87 | return StreamListener<PveAuthenticationState>( |
598a93b4 | 88 | stream: authbloc!.state, |
23a5b986 TM |
89 | onStateChange: (state) { |
90 | if (state is Authenticated) { | |
bf600e56 | 91 | Provider.of<PveResourceBloc>(context, listen: false) |
23a5b986 TM |
92 | ..apiClient = state.apiClient |
93 | ..events.add(PollResources()); | |
94 | } | |
a98df38d | 95 | if (state is Unauthenticated) { |
bf600e56 TL |
96 | Provider.of<PveResourceBloc>(context, listen: false) |
97 | ..apiClient = null; | |
a98df38d | 98 | } |
23a5b986 | 99 | }, |
acd1a179 | 100 | child: MaterialApp( |
686d6881 | 101 | navigatorKey: navigatorKey, |
acd1a179 | 102 | title: 'Proxmox', |
ff0427c0 | 103 | //themeMode: ThemeMode.dark, // comment in/out to test |
acd1a179 | 104 | theme: ThemeData( |
9bb60c76 TL |
105 | colorScheme: ColorScheme.light( |
106 | brightness: Brightness.light, | |
107 | primary: ProxmoxColors.supportBlue, | |
108 | onPrimary: Colors.white, | |
109 | primaryVariant: ProxmoxColors.blue900, | |
110 | secondary: ProxmoxColors.orange, | |
111 | secondaryVariant: ProxmoxColors.supportLightOrange, | |
112 | surface: ProxmoxColors.supportGreyTint50, | |
113 | onSurface: Colors.black, | |
114 | background: ProxmoxColors.supportGreyTint75, | |
115 | onBackground: Colors.black, | |
116 | ), | |
117 | textButtonTheme: TextButtonThemeData( | |
118 | style: TextButton.styleFrom(primary: ProxmoxColors.grey), | |
119 | ), | |
120 | fontFamily: "Open Sans", | |
121 | primaryTextTheme: TextTheme( | |
122 | headline6: | |
123 | TextStyle(fontFamily: "Open Sans", fontWeight: FontWeight.w700), | |
124 | ), | |
125 | appBarTheme: AppBarTheme( | |
126 | backgroundColor: ProxmoxColors.supportBlue, // primary | |
127 | foregroundColor: Colors.white, // onPrimary | |
128 | ), | |
129 | ), | |
130 | darkTheme: ThemeData( | |
131 | colorScheme: ColorScheme.dark( | |
132 | brightness: Brightness.dark, | |
133 | primary: ProxmoxColors.supportBlue, | |
134 | onPrimary: Colors.white, | |
135 | primaryVariant: ProxmoxColors.blue800, | |
136 | surface: ProxmoxColors.greyTint20, | |
137 | onSurface: Colors.white, | |
138 | secondary: ProxmoxColors.orange, | |
139 | secondaryVariant: ProxmoxColors.supportLightOrange, | |
140 | background: ProxmoxColors.grey, | |
141 | onBackground: ProxmoxColors.supportGreyTint75, | |
142 | ), | |
143 | // flutter has a weird logic where it pulls colors from different | |
144 | // scheme properties depending on light/dark mode, avoid that... | |
145 | appBarTheme: AppBarTheme( | |
146 | backgroundColor: ProxmoxColors.supportBlue, // primary | |
147 | foregroundColor: Colors.white, // onPrimary | |
148 | ), | |
149 | textButtonTheme: TextButtonThemeData( | |
150 | style: TextButton.styleFrom(primary: ProxmoxColors.greyTint80), | |
151 | ), | |
21842517 | 152 | fontFamily: "Open Sans", |
21842517 TM |
153 | primaryTextTheme: TextTheme( |
154 | headline6: | |
155 | TextStyle(fontFamily: "Open Sans", fontWeight: FontWeight.w700), | |
156 | ), | |
9bb60c76 | 157 | scaffoldBackgroundColor: ProxmoxColors.grey, |
21842517 | 158 | ), |
686d6881 TM |
159 | builder: (context, child) { |
160 | return StreamListener( | |
161 | stream: ProxmoxGlobalErrorBloc().onError.distinct(), | |
598a93b4 | 162 | onStateChange: (dynamic error) async { |
1b97345c TM |
163 | if (!ProxmoxGlobalErrorBloc().dialogVisible) { |
164 | ProxmoxGlobalErrorBloc().dialogVisible = true; | |
165 | ||
166 | await showDialog<String>( | |
598a93b4 | 167 | context: navigatorKey.currentState!.overlay!.context, |
1b97345c TM |
168 | builder: (BuildContext context) { |
169 | return StreamBuilder<Object>( | |
170 | stream: ProxmoxGlobalErrorBloc().onError, | |
171 | initialData: error, | |
172 | builder: (context, snapshot) { | |
173 | return AlertDialog( | |
174 | contentPadding: const EdgeInsets.fromLTRB( | |
175 | 24.0, 12.0, 24.0, 16.0), | |
176 | title: Row( | |
177 | mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
178 | children: <Widget>[ | |
179 | Text('Error'), | |
180 | Icon(Icons.warning), | |
181 | ], | |
182 | ), | |
183 | content: SingleChildScrollView( | |
184 | child: Text(snapshot.data?.toString() ?? ''), | |
185 | ), | |
186 | ); | |
187 | }); | |
188 | }, | |
686d6881 | 189 | ); |
1b97345c TM |
190 | ProxmoxGlobalErrorBloc().dialogVisible = false; |
191 | } | |
192 | }, | |
686d6881 TM |
193 | child: child, |
194 | ); | |
195 | }, | |
acd1a179 | 196 | onGenerateRoute: (context) { |
598a93b4 | 197 | if (authbloc!.state.value is Uninitialized) { |
d27dff8a TM |
198 | return MaterialPageRoute( |
199 | builder: (context) => PveSplashScreen(), | |
200 | ); | |
40d61f6e AL |
201 | } |
202 | if (sharedPreferences.getBool('showWelcomeScreen') ?? true) { | |
203 | return MaterialPageRoute( | |
204 | builder: (context) => PveWelcome(), | |
205 | ); | |
d27dff8a TM |
206 | } |
207 | ||
598a93b4 | 208 | if (authbloc!.state.value is Unauthenticated || |
2e763da1 | 209 | context.name == '/login') { |
acd1a179 TM |
210 | return MaterialPageRoute( |
211 | builder: (context) { | |
4e71c5d8 | 212 | return StreamListener<PveAuthenticationState>( |
598a93b4 | 213 | stream: authbloc!.state, |
4e71c5d8 TM |
214 | onStateChange: (state) { |
215 | if (state is Authenticated) { | |
216 | Navigator.of(context).pushReplacementNamed('/'); | |
217 | } | |
218 | }, | |
219 | child: ProxmoxLoginSelector( | |
598a93b4 | 220 | onLogin: (client) => authbloc!.events.add(LoggedIn(client)), |
4e71c5d8 | 221 | ), |
acd1a179 TM |
222 | ); |
223 | }, | |
224 | ); | |
225 | } | |
598a93b4 TL |
226 | if (authbloc!.state.value is Authenticated) { |
227 | final state = authbloc!.state.value as Authenticated; | |
228 | if (PveQemuOverview.routeName.hasMatch(context.name!)) { | |
229 | final match = | |
230 | PveQemuOverview.routeName.firstMatch(context.name!)!; | |
231 | final String nodeID = match.group(1)!; | |
232 | final String guestID = match.group(2)!; | |
49dcddb1 TM |
233 | |
234 | return MaterialPageRoute( | |
235 | fullscreenDialog: false, | |
236 | settings: context, | |
237 | builder: (_) { | |
238 | return MultiProvider( | |
239 | providers: [ | |
240 | Provider<PveQemuOverviewBloc>( | |
241 | create: (context) => PveQemuOverviewBloc( | |
242 | guestID: guestID, | |
243 | apiClient: state.apiClient, | |
244 | init: PveQemuOverviewState.init(nodeID), | |
245 | )..events.add(UpdateQemuStatus()), | |
246 | dispose: (context, bloc) => bloc.dispose(), | |
247 | ), | |
248 | Provider<PveTaskLogBloc>( | |
249 | create: (context) => PveTaskLogBloc( | |
250 | apiClient: state.apiClient, | |
251 | init: PveTaskLogState.init(nodeID)) | |
252 | ..events.add(FilterTasksByGuestID(guestID: guestID)) | |
253 | ..events.add(LoadTasks()), | |
254 | dispose: (context, bloc) => bloc.dispose(), | |
255 | ) | |
256 | ], | |
257 | child: PveQemuOverview( | |
258 | guestID: guestID, | |
259 | ), | |
260 | ); | |
261 | }, | |
262 | ); | |
263 | } | |
598a93b4 TL |
264 | if (PveLxcOverview.routeName.hasMatch(context.name!)) { |
265 | final match = PveLxcOverview.routeName.firstMatch(context.name!)!; | |
266 | final String nodeID = match.group(1)!; | |
267 | final String guestID = match.group(2)!; | |
76e4760f TM |
268 | |
269 | return MaterialPageRoute( | |
270 | fullscreenDialog: false, | |
271 | settings: context, | |
272 | builder: (_) { | |
273 | return MultiProvider( | |
274 | providers: [ | |
275 | Provider<PveLxcOverviewBloc>( | |
276 | create: (context) => PveLxcOverviewBloc( | |
277 | guestID: guestID, | |
278 | apiClient: state.apiClient, | |
279 | init: PveLxcOverviewState.init(nodeID), | |
280 | )..events.add(UpdateLxcStatus()), | |
281 | dispose: (context, bloc) => bloc.dispose(), | |
282 | ), | |
283 | Provider<PveTaskLogBloc>( | |
284 | create: (context) => PveTaskLogBloc( | |
285 | apiClient: state.apiClient, | |
286 | init: PveTaskLogState.init(nodeID)) | |
287 | ..events.add(FilterTasksByGuestID(guestID: guestID)) | |
288 | ..events.add(LoadTasks()), | |
289 | dispose: (context, bloc) => bloc.dispose(), | |
290 | ) | |
291 | ], | |
292 | child: PveLxcOverview( | |
293 | guestID: guestID, | |
294 | ), | |
295 | ); | |
296 | }, | |
297 | ); | |
298 | } | |
c2387333 | 299 | |
598a93b4 TL |
300 | if (PveNodeOverview.routeName.hasMatch(context.name!)) { |
301 | final match = | |
302 | PveNodeOverview.routeName.firstMatch(context.name!)!; | |
303 | final String nodeID = match.group(1)!; | |
c2387333 TM |
304 | return MaterialPageRoute( |
305 | fullscreenDialog: false, | |
306 | settings: context, | |
675ac314 TM |
307 | builder: (context) { |
308 | final rbloc = Provider.of<PveResourceBloc>(context); | |
c2387333 TM |
309 | return MultiProvider( |
310 | providers: [ | |
311 | Provider<PveNodeOverviewBloc>( | |
312 | create: (context) => PveNodeOverviewBloc( | |
313 | apiClient: state.apiClient, | |
314 | nodeID: nodeID, | |
675ac314 TM |
315 | init: PveNodeOverviewState.init( |
316 | rbloc.latestState.isStandalone), | |
c2387333 TM |
317 | )..events.add(UpdateNodeStatus()), |
318 | dispose: (context, bloc) => bloc.dispose(), | |
319 | ), | |
320 | Provider<PveTaskLogBloc>( | |
321 | create: (context) => PveTaskLogBloc( | |
322 | apiClient: state.apiClient, | |
323 | init: PveTaskLogState.init(nodeID)) | |
324 | ..events.add(LoadTasks()), | |
325 | dispose: (context, bloc) => bloc.dispose(), | |
326 | ) | |
327 | ], | |
328 | child: PveNodeOverview( | |
329 | nodeID: nodeID, | |
330 | ), | |
331 | ); | |
332 | }, | |
333 | ); | |
334 | } | |
acd1a179 | 335 | switch (context.name) { |
2e763da1 TM |
336 | case '/': |
337 | return MaterialPageRoute( | |
338 | fullscreenDialog: true, | |
339 | settings: context, | |
340 | builder: (context) => MultiProvider( | |
341 | providers: [ | |
342 | Provider<proxclient.ProxmoxApiClient>.value( | |
343 | value: state.apiClient, | |
344 | ), | |
345 | Provider<PveClusterStatusBloc>( | |
346 | create: (context) => PveClusterStatusBloc( | |
347 | apiClient: state.apiClient, | |
348 | init: PveClusterStatusState.init()) | |
349 | ..events.add(UpdateClusterStatus()), | |
350 | dispose: (context, bloc) => bloc.dispose(), | |
351 | ) | |
352 | ], | |
8df4e60d | 353 | //TODO add a wide layout option here when it's ready |
2e763da1 TM |
354 | child: ProxmoxLayoutBuilder( |
355 | builder: (context, layout) => layout != ProxmoxLayout.slim | |
8df4e60d | 356 | ? MainLayoutSlim() |
2e763da1 TM |
357 | : MainLayoutSlim(), |
358 | ), | |
359 | ), | |
360 | ); | |
361 | break; | |
acd1a179 TM |
362 | default: |
363 | return MaterialPageRoute( | |
364 | settings: context, | |
365 | builder: (context) { | |
366 | return NotFoundPage(); | |
367 | }, | |
368 | ); | |
369 | } | |
65098e83 | 370 | } |
fb150864 TM |
371 | return MaterialPageRoute( |
372 | settings: context, | |
373 | builder: (context) { | |
374 | return NotFoundPage(); | |
375 | }, | |
376 | ); | |
acd1a179 | 377 | }, |
2e763da1 | 378 | initialRoute: '/', |
00a48038 TM |
379 | ), |
380 | ); | |
381 | } | |
382 | } |