Files
stadtbote-app/lib/main.dart
T
2024-02-03 10:19:38 +01:00

341 lines
11 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_broadcast_receiver/flutter_broadcast_receiver.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:stadtbote/model/message.dart';
import 'package:stadtbote/model/task_info.dart';
import 'package:stadtbote/tools/colored_safe_area.dart';
import 'package:stadtbote/model/status.dart';
import 'package:stadtbote/tools/connection_status.dart';
import 'tools/mqtt_core.dart';
import 'content_views/main_page.dart';
import 'content_views/utils/content_view_widget.dart';
import 'content_views/utils/widget_stack.dart';
import 'model/job.dart';
import 'tools/constants.dart';
import 'tools/util.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final dir = await getApplicationDocumentsDirectory();
MyApp.isarInstance = await Isar.open([
StatusSchema,
JobSchema,
TaskInfoSchema,
MessageSchema,
], directory: dir.path);
runZonedGuarded(() {
}, (error, stackTrace) {
_traceUncaughtExceptions(error, stackTrace);
});
MqttCore.setup();
runApp(const MyApp());
}
void _traceUncaughtExceptions(Object error, StackTrace stackTrace) {
Util.logException('Ein Fehler ist aufgetreten: $error');
Util.logException('Stack-Trace: $stackTrace');
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
static late Isar isarInstance;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Stadtbote',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Main(),
);
}
}
class Main extends StatefulWidget {
const Main({super.key});
@override
State<Main> createState() => _MainState();
}
class _MainState extends State<Main> {
late ContentViewWidget _currentContent;
late WidgetStack<ContentViewWidget> contentStack = WidgetStack();
late Widget _currentTitleWidget;
late Widget _leftIconButton;
late Widget _rightIconButton;
late bool _leftButtonVisible = false;
late bool _rightButtonVisible = false;
late Function _rightButtonOnPressed;
late Function _leftButtonOnPressed;
bool _initCompleted = false;
final ValueNotifier<bool> _connectionLostValueNotifier = ValueNotifier<bool>(true);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
_initMessages();
_currentContent = MainPage();
var headerInfo = await _currentContent.getHeaderInfo(context);
_currentTitleWidget = headerInfo.titleWidget;
_leftButtonVisible = false;
_leftIconButton = const Icon(Icons.arrow_back, size: 20.0, color: Colors.white);
_leftButtonOnPressed = () {
_popContentView();
};
_rightButtonVisible = headerInfo.rightButtonVisible;
_rightButtonOnPressed = headerInfo.rightButtonOnPressed;
if (headerInfo.rightButtonEnabled) {
_rightIconButton = await headerInfo.rightButtonImage as Widget;
}
_initCompleted = true;
setState(() {});
});
}
@override
void dispose() {
super.dispose();
//_subscription.cancel();
}
@override
Widget build(BuildContext context) {
if (!_initCompleted) {
return Util.getWaitWidget(context);
}
return ColoredSafeArea(
key: GlobalKey(),
child: PopScope(
canPop: contentStack.isNotEmpty,
onPopInvoked: (didPop) => {
_popContentView()
},
child: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(100),
child: ColoredSafeArea(
key: const Key("appBar"),
child: SizedBox(
height: 100,
child: Padding(
padding: const EdgeInsets.all(8),
child: Stack(
alignment: Alignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: _leftButtonVisible,
child: Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: FittedBox(
child: GestureDetector(
onTap: () async {
_leftButtonOnPressed();
},
child: CircleAvatar(
radius: 15,
backgroundColor: Constants.colorPrimary,
child: Padding(padding: const EdgeInsets.all(1.0), child: _leftIconButton),
),
),
),
),
),
Visibility(
visible: _rightButtonVisible,
child: Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
),
child: FittedBox(
child: GestureDetector(
onTap: () async {
_rightButtonOnPressed();
},
child: CircleAvatar(
backgroundColor: Constants.colorPrimary,
radius: 15,
child: Padding(padding: const EdgeInsets.all(1.0), child: _rightIconButton),
),
),
),
),
),
],
),
SizedBox(
height: 100,
width: 300,
child: Center(
heightFactor: 1,
child: _currentTitleWidget),
),
],
),
),
)),
),
body: Column(
children: [
ValueListenableBuilder<bool>(
valueListenable: _connectionLostValueNotifier,
builder: (context, value, child) {
return Visibility(
visible: value,
child: Container(
width: MediaQuery.of(context).size.width,
height: 30,
color: Colors.red,
child: const Center(
child: Text("*** OFFLINE ***",
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
),),
),
),
);
},
),
Expanded(child: _currentContent),
],
),// This trailing comma makes auto-formatting nicer for build methods.
),
),
);
}
void _initMessages() {
BroadcastReceiver().subscribe<ContentViewWidget>(Constants.messageTopicPushContentView, (ContentViewWidget widget) {
_pushContentView(widget);
});
BroadcastReceiver().subscribe<ContentViewWidget>(Constants.messageTopicPopContentView, (ContentViewWidget widget) {
_popContentView();
});
BroadcastReceiver().subscribe<ContentViewWidget>(Constants.messageTopicReplaceContentView, (ContentViewWidget widget) {
_replaceContentView(widget);
});
BroadcastReceiver().subscribe<bool>(Constants.messageTopicHideRightButton, (hideButton) {
_hideRightButton(hideButton);
});
BroadcastReceiver().subscribe<ConnectionStatus>(Constants.messageTopicConnectionStatusChanged, (ConnectionStatus connectionStatus) {
_connectionStatusChanged(connectionStatus);
});
}
void _replaceContentView(ContentViewWidget widget) async {
Future.delayed(const Duration(seconds: 0), () async {
contentStack.clear();
setState(() {
_currentContent = widget;
});
var headerInfo = await _currentContent.getHeaderInfo(context);
_currentTitleWidget = headerInfo.titleWidget;
_rightButtonVisible = headerInfo.rightButtonVisible;
_rightButtonOnPressed = headerInfo.rightButtonOnPressed;
if (headerInfo.rightButtonEnabled) {
_rightIconButton = await headerInfo.rightButtonImage as Widget;
}
_leftButtonVisible = false;
setState(() {});
});
}
void _popContentView() {
Future.delayed(const Duration(seconds: 0), () async {
var previousContentView = contentStack.pop();
_currentContent = previousContentView;
var headerInfo = await _currentContent.getHeaderInfo(context);
_currentTitleWidget = headerInfo.titleWidget;
_rightButtonVisible = headerInfo.rightButtonVisible;
_rightButtonOnPressed = headerInfo.rightButtonOnPressed;
if (headerInfo.rightButtonEnabled) {
_rightIconButton = await headerInfo.rightButtonImage as Widget;
}
if (contentStack.isEmpty) {
_leftButtonVisible = false;
}
setState(() {});
});
}
void _pushContentView(ContentViewWidget widget) {
Future.delayed(const Duration(seconds: 0), () async {
contentStack.push(_currentContent);
_currentContent = widget;
var headerInfo = await _currentContent.getHeaderInfo(context);
_currentTitleWidget = headerInfo.titleWidget;
_rightButtonVisible = headerInfo.rightButtonVisible;
_rightButtonOnPressed = headerInfo.rightButtonOnPressed;
if (headerInfo.rightButtonEnabled) {
_rightIconButton = await headerInfo.rightButtonImage as Widget;
}
_leftButtonVisible = true;
setState(() {});
});
}
void _hideRightButton(bool hideButton) {
setState(() {
_rightButtonVisible = !hideButton;
});
}
void _connectionStatusChanged(ConnectionStatus connectionStatus) {
_connectionLostValueNotifier.value = connectionStatus.status == ConnectionStatusEnum.notConnected;
}
}