]> git.proxmox.com Git - flutter/pve_flutter_frontend.git/blame - lib/main.dart
web console: tell noVNC to hide broken fullscreen-button
[flutter/pve_flutter_frontend.git] / lib / main.dart
CommitLineData
598a93b4 1import 'dart:async';
686d6881 2import 'package:flutter/foundation.dart';
00a48038 3import 'package:flutter/material.dart';
65098e83 4import 'package:provider/provider.dart';
40d61f6e
AL
5import 'package:pve_flutter_frontend/widgets/pve_first_welcome_screen.dart';
6import 'package:shared_preferences/shared_preferences.dart';
4e71c5d8 7import 'package:proxmox_login_manager/proxmox_login_manager.dart';
00a48038 8import 'package:pve_flutter_frontend/bloc/pve_authentication_bloc.dart';
18a71ae9 9import 'package:pve_flutter_frontend/bloc/pve_cluster_status_bloc.dart';
76e4760f 10import 'package:pve_flutter_frontend/bloc/pve_lxc_overview_bloc.dart';
c2387333 11import 'package:pve_flutter_frontend/bloc/pve_node_overview_bloc.dart';
49dcddb1 12import 'package:pve_flutter_frontend/bloc/pve_qemu_overview_bloc.dart';
8285412a 13import 'package:pve_flutter_frontend/bloc/pve_resource_bloc.dart';
49dcddb1 14import 'package:pve_flutter_frontend/bloc/pve_task_log_bloc.dart';
00a48038 15import 'package:pve_flutter_frontend/pages/404_page.dart';
00a48038 16import 'package:pve_flutter_frontend/pages/main_layout_slim.dart';
fe1ea690 17import 'package:pve_flutter_frontend/states/pve_cluster_status_state.dart';
76e4760f 18import 'package:pve_flutter_frontend/states/pve_lxc_overview_state.dart';
c2387333 19import 'package:pve_flutter_frontend/states/pve_node_overview_state.dart';
49dcddb1 20import 'package:pve_flutter_frontend/states/pve_qemu_overview_state.dart';
23a5b986 21import 'package:pve_flutter_frontend/states/pve_resource_state.dart';
49dcddb1 22import 'package:pve_flutter_frontend/states/pve_task_log_state.dart';
23a5b986 23import 'package:pve_flutter_frontend/widgets/proxmox_stream_listener.dart';
65098e83
TM
24import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'
25 as proxclient;
00a48038 26
1e2d2401 27import 'package:pve_flutter_frontend/utils/proxmox_layout_builder.dart';
00a48038 28
acd1a179 29import 'package:pve_flutter_frontend/bloc/proxmox_global_error_bloc.dart';
76e4760f 30import 'package:pve_flutter_frontend/widgets/pve_lxc_overview.dart';
c2387333 31import 'package:pve_flutter_frontend/widgets/pve_node_overview.dart';
49dcddb1 32import 'package:pve_flutter_frontend/widgets/pve_qemu_overview.dart';
d27dff8a 33import 'package:pve_flutter_frontend/widgets/pve_splash_screen.dart';
9bb60c76 34import 'package:pve_flutter_frontend/utils/proxmox_colors.dart';
d73c2def 35
1ba800e6
TL
36import 'package:rxdart/streams.dart' show ValueStreamExtensions;
37
f42f863c 38void 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
76class 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}