import 'dart:async'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:isar/isar.dart'; import 'package:path_provider/path_provider.dart'; import 'package:stadtbote/messages/pop_content_view_message.dart'; import 'package:stadtbote/model/enqueued_message.dart'; import 'package:stadtbote/model/message.dart'; import 'package:stadtbote/model/ranking.dart'; import 'package:stadtbote/model/status.dart'; import 'package:stadtbote/model/task_info.dart'; import 'package:stadtbote/popups/accept_jobs_popup.dart'; import 'package:stadtbote/tools/colored_safe_area.dart'; import 'package:stadtbote/model/session_status.dart'; import 'package:stadtbote/tools/connection_status.dart'; import 'package:stadtbote/tools/message_service.dart'; import 'package:stadtbote/tools/messages/job_received_message.dart'; import 'package:stadtbote/tools/messages/update_badges_message.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 main() async { WidgetsFlutterBinding.ensureInitialized(); final dir = await getApplicationDocumentsDirectory(); MyApp.isarInstance = await Isar.open([ SessionStatusSchema, StatusSchema, JobSchema, TaskInfoSchema, MessageSchema, RankingSchema, EnqueuedMessageSchema, ], directory: dir.path); runZonedGuarded(() {}, (error, stackTrace) { _traceUncaughtExceptions(error, stackTrace); }); runApp(MyApp()); } void _traceUncaughtExceptions(Object error, StackTrace stackTrace) { Util.logException('Ein Fehler ist aufgetreten: $error'); Util.logException('Stack-Trace: $stackTrace'); } class MyApp extends StatelessWidget { MyApp({super.key}) { MqttCore.setup(); _startLocationService(); } static late Isar isarInstance; static Position? currentPosition; static bool suppressAcceptDialog = false; @override Widget build(BuildContext context) { return MaterialApp( title: 'Stadtbote', theme: ThemeData( primarySwatch: Colors.blue, ), home: const Main(), ); } _startLocationService() async { var result = await _handleLocationPermissions(); if (result) { LocationSettings locationSettings = const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 0, ); Geolocator.getPositionStream(locationSettings: locationSettings).listen((Position? position) { MyApp.currentPosition = position; }); } } Future _handleLocationPermissions() async { // Test if location services are enabled. var serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { // Location services are not enabled don't continue // accessing the position and request users of the // App to enable the location services. return false; //return Future.error('Location services are disabled.'); } var permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { // Permissions are denied, next time you could try // requesting permissions again (this is also where // Android's shouldShowRequestPermissionRationale // returned true. According to Android guidelines // your App should show an explanatory UI now. //return Future.error('Location permissions are denied'); return false; } } if (permission == LocationPermission.deniedForever) { //return Future.error('Location permissions are permanently denied, we cannot request permissions.'); return false; } return true; } } class Main extends StatefulWidget { const Main({super.key}); @override State
createState() => _MainState(); } class _MainState extends State
with SingleTickerProviderStateMixin { late ContentViewWidget _currentContent; late WidgetStack 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 _connectionLostValueNotifier = ValueNotifier(true); late AnimationController _animationController; late Animation _animation; bool _dialogOpened = false; @override void initState() { super.initState(); _animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000)); _animationController.repeat(reverse: true); _animation = Tween(begin: 0.5, end: 1.0).animate(_animationController); 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(1); }; _rightButtonVisible = headerInfo.rightButtonVisible; _rightButtonOnPressed = headerInfo.rightButtonOnPressed; if (headerInfo.rightButtonEnabled) { _rightIconButton = await headerInfo.rightButtonImage as Widget; } _initCompleted = true; setState(() {}); }); } @override Widget build(BuildContext context) { if (!_initCompleted) { return Util.getWaitWidget(context); } return ColoredSafeArea( key: GlobalKey(), child: WillPopScope( onWillPop: () async { if (contentStack.isNotEmpty) { _popContentView(1); return false; } else { return true; } }, 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( valueListenable: _connectionLostValueNotifier, builder: (context, value, child) { return Visibility( visible: value, child: Column( children: [ const SizedBox(height: 1), Container( width: MediaQuery.of(context).size.width, height: 40, color: Constants.colorPrimary, child: Center( child: FadeTransition( opacity: _animation, child: const Text( "*** OFFLINE ***", style: TextStyle( color: Colors.white, fontSize: 25.0, ), ), )), ), ], ), ); }, ), Expanded(child: _currentContent), ], ), ), ), ); } void _initMessages() { MessageService().subscribe(Constants.messageTopicPushContentView, this, (ContentViewWidget widget) { _pushContentView(widget); }); MessageService().subscribe(Constants.messageTopicPopContentView, this, (PopContentViewMessage message) { _popContentView(message.count); }); MessageService().subscribe(Constants.messageTopicReplaceContentView, this, (ContentViewWidget widget) { _replaceContentView(widget); }); MessageService().subscribe(Constants.messageTopicHideRightButton, this, (hideButton) { _hideRightButton(hideButton); }); MessageService().subscribe(Constants.messageTopicConnectionStatusChanged, this, (ConnectionStatus connectionStatus) { _connectionStatusChanged(connectionStatus); }); MessageService().subscribe(Constants.messageTopicJobReceived, this, (JobReceivedMessage jobReceivedMessage) { _jobReceived(jobReceivedMessage.job); }); } 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(int count) { Future.delayed(const Duration(seconds: 0), () async { for (int i = 0; i < count - 1; i++) { contentStack.pop(); } 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; } void _jobReceived(Job job) async { if (!MyApp.suppressAcceptDialog) { if (job.state == 0) { if (!_dialogOpened) { _dialogOpened = true; await showAcceptJobsPopup(context, await MyApp.isarInstance.jobs.filter().stateEqualTo(0).findAll(), () { _dialogOpened = false; MessageService().publish(Constants.messageTopicUpdateBadges, arguments: UpdateBadgesMessage()); }); } } } } }