Compare commits
27 Commits
87462fa205
...
master
Author | SHA1 | Date | |
---|---|---|---|
d428be6504 | |||
608ef75f9f | |||
4474eb0aa9 | |||
8be01e92a8 | |||
b64e1525ae | |||
27ffb07256 | |||
9fbc19ff7e | |||
a2326118d2 | |||
169ebeb30a | |||
7eb09390ad | |||
84c838b15e | |||
32c88fa857 | |||
c00d2fac05 | |||
507b321311 | |||
2a38b8ae2b | |||
c8278656cf | |||
e748df092c | |||
4ecf44bafa | |||
bffc603918 | |||
628d52f01f | |||
ef8c5e9039 | |||
ec8d3d466b | |||
4b384b378a | |||
799d1cb335 | |||
911c2738aa | |||
99cc98f853 | |||
4bf05fb655 |
@ -2,7 +2,7 @@
|
||||
<application
|
||||
android:label="Bubatzkarte"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@ -41,4 +41,8 @@
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
</manifest>
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 15 KiB |
BIN
assets/background/banner.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
assets/background/tile.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
30
assets/ca/lets-encrypt-r3.pem
Normal file
@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
|
||||
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
|
||||
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
|
||||
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
|
||||
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
|
||||
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
|
||||
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
|
||||
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
|
||||
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
|
||||
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
|
||||
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
|
||||
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
|
||||
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
|
||||
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
|
||||
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
|
||||
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
|
||||
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
|
||||
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
|
||||
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
|
||||
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
|
||||
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
|
||||
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
|
||||
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
|
||||
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
|
||||
nLRbwHOoq7hHwg==
|
||||
-----END CERTIFICATE-----
|
BIN
assets/splash/splash512.png
Normal file
After Width: | Height: | Size: 44 KiB |
1
devtools_options.yaml
Normal file
@ -0,0 +1 @@
|
||||
extensions:
|
@ -1,5 +1,5 @@
|
||||
flutter_launcher_icons:
|
||||
android: "launcher_icon"
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "assets/splash/splash.png"
|
||||
min_sdk_android: 16
|
28
lib/extensions/dateOfTimeExtensions.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension TimeExtensions on DateTime {
|
||||
DateTime forwardTo(TimeOfDay timeOfDay) {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
DateTime result;
|
||||
|
||||
final targetTime = DateTime(now.year, now.month, now.day, timeOfDay.hour, timeOfDay.minute);
|
||||
if (targetTime.isBefore(now) || targetTime.isAtSameMomentAs(now)) {
|
||||
result = today.add(Duration(days: 1));
|
||||
} else {
|
||||
result = today;
|
||||
}
|
||||
|
||||
return result.add(Duration(hours: timeOfDay.hour, minutes: timeOfDay.minute));
|
||||
}
|
||||
|
||||
bool isBetweenTimeOfDay(TimeOfDay start, TimeOfDay end) {
|
||||
final now = this;
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
final startTime = today.add(Duration(hours: start.hour, minutes: start.minute));
|
||||
final endTime = today.add(Duration(hours: end.hour, minutes: end.minute));
|
||||
|
||||
return now.isAfter(startTime) && now.isBefore(endTime);
|
||||
}
|
||||
}
|
8
lib/extensions/positionLatLngExtension.dart
Normal file
@ -0,0 +1,8 @@
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
extension LatLngPositionExtension on Position {
|
||||
LatLng latlng() {
|
||||
return LatLng(latitude, longitude);
|
||||
}
|
||||
}
|
@ -1,14 +1,23 @@
|
||||
import 'package:app/state/legalStatusState.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app/state/timeStatusState.dart';
|
||||
import 'package:app/state/mapState.dart';
|
||||
import 'package:app/view/home.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() {
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
|
||||
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<LegalStatusState>(create: (context) => LegalStatusState()),
|
||||
ChangeNotifierProvider<TimeWarningState>(create: (context) => TimeWarningState()),
|
||||
ChangeNotifierProvider<MapState>(create: (context) => MapState()),
|
||||
],
|
||||
builder: (context, child) => const MyApp(),
|
||||
|
@ -1,61 +0,0 @@
|
||||
import 'package:app/model/legelState.dart';
|
||||
import 'package:app/model/stateInfo.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LegalStatusState extends ChangeNotifier {
|
||||
LegalState _legalState = LegalState.unknown;
|
||||
|
||||
LegalState get getLegalState => _legalState;
|
||||
|
||||
StateInfo get info {
|
||||
switch(_legalState) {
|
||||
case LegalState.allowed:
|
||||
return StateInfo(
|
||||
icon: Icons.check,
|
||||
color: Colors.lightGreen,
|
||||
title: "Passt",
|
||||
subtitle: "Alle kriterien erfüllt",
|
||||
description: "asd"
|
||||
);
|
||||
|
||||
case LegalState.disallowedTime:
|
||||
return StateInfo(
|
||||
icon: Icons.timer_outlined,
|
||||
color: Colors.amber,
|
||||
title: "Zu früh",
|
||||
subtitle: "Bubazen ist nur zwischn 20 und 6 uhr Abends/ Nachts erlaubt!",
|
||||
description: "asd"
|
||||
);
|
||||
|
||||
case LegalState.disallowedRegion:
|
||||
return StateInfo(
|
||||
icon: Icons.location_off_outlined,
|
||||
color: Colors.red,
|
||||
title: "Zu nah an einem geschützen Gebäude",
|
||||
subtitle: "asd",
|
||||
description: "asd"
|
||||
);
|
||||
|
||||
case LegalState.unknown:
|
||||
default:
|
||||
return StateInfo(
|
||||
icon: Icons.question_mark_outlined,
|
||||
color: Colors.grey,
|
||||
title: "Status nicht bekannt",
|
||||
subtitle: "asd",
|
||||
description: "asd"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
set setInSightOfDisallowedAreas(bool isInSight) {
|
||||
_legalState = LegalState.disallowedRegion;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
set setIsInDisallowedTimeRanges(bool isInDisallowedTime) {
|
||||
_legalState = LegalState.disallowedTime;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@ -1,19 +1,38 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class MapState extends ChangeNotifier {
|
||||
bool _isLocationLock = false;
|
||||
bool _isCurrentlyFetchin = true;
|
||||
bool _isLocationLive = false;
|
||||
LatLng? _currentLocation;
|
||||
LatLng? _activeMarker;
|
||||
final MapController _mapController = MapController();
|
||||
final Geolocator _geolocator = Geolocator();
|
||||
|
||||
bool get followLocation => _isLocationLock;
|
||||
bool get isCurrentlyFetching => _isCurrentlyFetchin;
|
||||
bool get isLocationLive => _isLocationLive;
|
||||
LatLng? get getCurrentLocation => _currentLocation;
|
||||
LatLng? get getActiveMarker => _activeMarker;
|
||||
MapController get getMapController => _mapController;
|
||||
Geolocator get getGeolocator => _geolocator;
|
||||
|
||||
toggleLocationLock() {
|
||||
_isLocationLock = !_isLocationLock;
|
||||
void setCurrentLocation(LatLng? currentLocation) {
|
||||
_currentLocation = currentLocation;
|
||||
if (currentLocation != null) _mapController.move(currentLocation, 16);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
set setNetworkActivity(bool active) {
|
||||
_isCurrentlyFetchin = active;
|
||||
void setActiveMarker(LatLng? marker) {
|
||||
_activeMarker = marker;
|
||||
if (marker != null) {
|
||||
_isLocationLive = false;
|
||||
_mapController.move(marker, 16);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setLocationLive(bool live) {
|
||||
_isLocationLive = live;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
11
lib/state/timeStatusState.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TimeWarningState extends ChangeNotifier {
|
||||
bool _show = true;
|
||||
|
||||
bool get show => _show;
|
||||
void hide() {
|
||||
_show = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@ -1,15 +1,42 @@
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AppInfoView extends StatelessWidget {
|
||||
const AppInfoView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const AlertDialog(
|
||||
title: Text("Achtung"),
|
||||
content: Flexible(
|
||||
child: Text("bla"),
|
||||
return AlertDialog(
|
||||
title: const Text("Information"),
|
||||
content: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Diese App zeigt die Konsumverbotszonen für Cannabis."),
|
||||
Text("Keinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!"),
|
||||
SizedBox(height: 10),
|
||||
Text("Prüfe selbst die Gesetzlichen Bestimmungen vor dem Besitz oder Konsum von Cannabis!", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 10),
|
||||
Text("Die Daten beruhen auf OpenStreetMap, bearbeitet durch bubatzkarte.de."),
|
||||
Text("Es besteht keinerlei Kooperation mit OpenStreetMap oder bubatzkarte.de.")
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text("bubatzkarte.de öffnen"),
|
||||
onPressed: () => launchUrl(Uri.parse("https://bubatzkarte.de/")),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text("openstreetmap.org öffnen"),
|
||||
onPressed: () => launchUrl(Uri.parse("https://www.openstreetmap.org/")),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text("Schließen"),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:app/extensions/obtainProviderExtension.dart';
|
||||
import 'package:app/extensions/positionLatLngExtension.dart';
|
||||
import 'package:app/state/mapState.dart';
|
||||
import 'package:app/util/loadingContainer.dart';
|
||||
import 'package:app/state/timeStatusState.dart';
|
||||
import 'package:app/util/watchState.dart';
|
||||
import 'package:app/view/appInfo.dart';
|
||||
import 'package:app/view/status.dart';
|
||||
import 'package:app/view/locationSearch.dart';
|
||||
import 'package:app/view/timeWarning.dart';
|
||||
import 'package:app/view/map.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
const HomeView({super.key});
|
||||
@ -16,6 +21,101 @@ class HomeView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomeView> {
|
||||
bool locationEnabled = false;
|
||||
bool locationLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
Geolocator.getServiceStatusStream().listen((status) {
|
||||
setState(() {
|
||||
locationEnabled = status == ServiceStatus.enabled;
|
||||
});
|
||||
});
|
||||
|
||||
requestLiveLocation(false, true, true);
|
||||
}
|
||||
|
||||
Future<void> requestLiveLocation(bool ask, bool orLast, bool loadingIndicator) async {
|
||||
Position? position;
|
||||
MapState mapState = context.obtainState<MapState>();
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
log(permission.toString());
|
||||
|
||||
if(
|
||||
permission == LocationPermission.denied
|
||||
|| permission == LocationPermission.deniedForever
|
||||
|| permission == LocationPermission.unableToDetermine
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Standort"),
|
||||
content: const Text("Dein Standort konnte nicht bestimmt werden.\nAktiviere für die Standortbestimmung deinen Standort und gib der App die entsprechenden Berechtigungen."),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text("Okay"),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
loadingIndicator
|
||||
&& permission != LocationPermission.deniedForever
|
||||
&& permission != LocationPermission.denied
|
||||
) {
|
||||
setState(() {
|
||||
locationLoading = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
|
||||
if (ask && permission != LocationPermission.deniedForever) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
}
|
||||
if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
|
||||
if (orLast) position = await Geolocator.getLastKnownPosition();
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
|
||||
if (position != null) {
|
||||
setState(() => locationEnabled = true);
|
||||
mapState.setCurrentLocation(position.latlng());
|
||||
}
|
||||
setState(() => locationLoading = false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
position = await Geolocator.getCurrentPosition();
|
||||
setState(() {
|
||||
locationEnabled = true;
|
||||
});
|
||||
mapState.setCurrentLocation(position.latlng());
|
||||
mapState.setLocationLive(true);
|
||||
Geolocator.getServiceStatusStream().first.then((_) => setState(() {
|
||||
mapState.setLocationLive(false);
|
||||
}));
|
||||
} catch (_) {
|
||||
if (orLast) {
|
||||
position = await Geolocator.getLastKnownPosition();
|
||||
if (position != null) {
|
||||
setState(() => locationEnabled = true);
|
||||
mapState.setCurrentLocation(position.latlng());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(() => locationLoading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -25,8 +125,13 @@ class _HomePageState extends State<HomeView> {
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
|
||||
onPressed: () async {
|
||||
MapState mapState = context.obtainState<MapState>();
|
||||
LatLng? latLng = await showSearch<LatLng?>(
|
||||
context: context,
|
||||
delegate: LocationSearchDelegate(),
|
||||
);
|
||||
mapState.setActiveMarker(latLng);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
@ -41,26 +146,31 @@ class _HomePageState extends State<HomeView> {
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: WatchState<MapState>((context, state) => Icon(state.followLocation ? Icons.my_location : Icons.location_disabled)),
|
||||
onPressed: () => context.obtainState<MapState>().toggleLocationLock(),
|
||||
child: WatchState<MapState>((context, state) {
|
||||
if (locationLoading) {
|
||||
return const Padding(padding: EdgeInsets.all(15), child: CircularProgressIndicator(strokeWidth: 3));
|
||||
}
|
||||
if (locationEnabled) {
|
||||
return Icon(state.isLocationLive ? Icons.my_location : Icons.location_searching);
|
||||
}
|
||||
return const Icon(Icons.location_disabled);
|
||||
}),
|
||||
onPressed: () => requestLiveLocation(true, false, true),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
WatchState<TimeWarningState>((context, state) {
|
||||
if(!state.show || DateTime.now().hour >= 20 || DateTime.now().hour < 7) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return const SizedBox(
|
||||
height: 100,
|
||||
child: StatusView(),
|
||||
),
|
||||
Expanded(
|
||||
child: Consumer<MapState>(
|
||||
builder: (context, state, child) {
|
||||
return LoadingContainer(
|
||||
loading: child == null,
|
||||
fetching: state.isCurrentlyFetching,
|
||||
child: const MapView(),
|
||||
child: TimeWarningView(),
|
||||
);
|
||||
},
|
||||
child: const MapView(),
|
||||
),
|
||||
}),
|
||||
const Expanded(
|
||||
child: MapView(),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
70
lib/view/locationSearch.dart
Normal file
@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:osm_nominatim/osm_nominatim.dart';
|
||||
|
||||
class LocationSearchDelegate extends SearchDelegate<LatLng?> {
|
||||
@override
|
||||
List<Widget>? buildActions(BuildContext context) {
|
||||
return [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () => query = '',
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? buildLeading(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => close(context, null),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
return FutureBuilder<List<Place>>(
|
||||
future: query.isEmpty ? Future.value([]) : Nominatim.searchByName(
|
||||
query: query,
|
||||
limit: 10,
|
||||
language: 'de',
|
||||
// https://gist.github.com/graydon/11198540#file-country-bounding-boxes-py-L45
|
||||
viewBox: ViewBox(54.983104153, 15.0169958839, 47.3024876979, 5.98865807458)
|
||||
),
|
||||
builder: (BuildContext context, AsyncSnapshot<List<Place>> snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.waiting:
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
case ConnectionState.active:
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.done:
|
||||
if ((snapshot.data?.length ?? 0) == 0) {
|
||||
return const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: 50),
|
||||
Text("Keine Sucherergebnisse gefunden")
|
||||
],
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: snapshot.data!.length,
|
||||
itemBuilder: (context, index) {
|
||||
Place place = snapshot.data![index];
|
||||
return ListTile(
|
||||
title: Text(place.displayName),
|
||||
onTap: () => close(context, LatLng(place.lat, place.lon)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return buildResults(context);
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
import 'package:app/extensions/obtainProviderExtension.dart';
|
||||
import 'package:app/state/mapState.dart';
|
||||
import 'package:app/util/watchState.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:http/http.dart';
|
||||
@ -15,10 +18,14 @@ class _MapViewState extends State<MapView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlutterMap(
|
||||
options: const MapOptions(
|
||||
initialCenter: LatLng(50.354, 7.5845),
|
||||
mapController: context.obtainState<MapState>().getMapController,
|
||||
options: MapOptions(
|
||||
onPositionChanged: (position, hasGesture) => {
|
||||
if (hasGesture) context.obtainState<MapState>().setLocationLive(false)
|
||||
},
|
||||
initialCenter: const LatLng(50.354, 7.5845),
|
||||
initialZoom: 16,
|
||||
interactionOptions: InteractionOptions(
|
||||
interactionOptions: const InteractionOptions(
|
||||
flags: InteractiveFlag.all & ~InteractiveFlag.rotate
|
||||
)
|
||||
),
|
||||
@ -31,9 +38,51 @@ class _MapViewState extends State<MapView> {
|
||||
tileProvider: NetworkTileProvider(
|
||||
httpClient: RetryClient(
|
||||
Client(),
|
||||
when: (response) => response.statusCode == 502)
|
||||
when: (response) => response.statusCode >= 400 && response.statusCode != 404)
|
||||
),
|
||||
),
|
||||
WatchState<MapState>((context, state) => state.getActiveMarker != null ? MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: state.getActiveMarker!,
|
||||
child: const Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.red,
|
||||
size: 48,
|
||||
)
|
||||
)
|
||||
],
|
||||
) : const SizedBox.shrink()),
|
||||
WatchState<MapState>((context, state) => MarkerLayer(
|
||||
markers: [
|
||||
if (state.getActiveMarker != null) Marker(
|
||||
point: state.getActiveMarker!,
|
||||
child: const Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.red,
|
||||
size: 48,
|
||||
)
|
||||
),
|
||||
if (state.getCurrentLocation != null) Marker(
|
||||
point: state.getCurrentLocation!,
|
||||
child: const Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.circle,
|
||||
color: Colors.white,
|
||||
size: 23,
|
||||
),
|
||||
Icon(
|
||||
Icons.circle,
|
||||
color: Color.fromARGB(255, 0, 200, 0),
|
||||
size: 16,
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
]
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
import 'package:app/util/watchState.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../state/legalStatusState.dart';
|
||||
|
||||
class StatusView extends StatelessWidget {
|
||||
const StatusView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WatchState<LegalStatusState>((context, state) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: state.info.color
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: Icon(
|
||||
state.info.icon,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
const VerticalDivider(
|
||||
endIndent: 20,
|
||||
indent: 20,
|
||||
color: Colors.white,
|
||||
thickness: 3,
|
||||
width: 30,
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
state.info.title,
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
Text(state.info.subtitle),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.info_outline),
|
||||
onPressed: () {
|
||||
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
57
lib/view/timeWarning.dart
Normal file
@ -0,0 +1,57 @@
|
||||
import 'package:app/extensions/obtainProviderExtension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../state/timeStatusState.dart';
|
||||
|
||||
class TimeWarningView extends StatelessWidget {
|
||||
const TimeWarningView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Center(
|
||||
child: Icon(
|
||||
Icons.warning_amber,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
const VerticalDivider(
|
||||
endIndent: 20,
|
||||
indent: 20,
|
||||
color: Colors.white,
|
||||
thickness: 3,
|
||||
width: 40,
|
||||
),
|
||||
const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"In einer Fußgängerzone?",
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"hier ist der Konsum nur\nzwischen 20 und 7 Uhr gestattet",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: IconButton(
|
||||
onPressed: context.obtainState<TimeWarningState>().hide,
|
||||
icon: const Icon(Icons.close)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
162
pubspec.lock
@ -121,6 +121,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -163,11 +171,67 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_timer_countdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_timer_countdown
|
||||
sha256: "0c73e1593ad7949c007752199a17e7ed50bb581568743dbc32f061f49873219e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.7"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: "694ec58afe97787b5b72b8a0ab78c1a9244811c3c10e72c4362ef3c0ceb005cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.0"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: f15d1536cd01b1399578f1da1eb5d566e7a718db6a3648f2c24d2e2f859f0692
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "009a21c4bc2761e58dccf07c24f219adaebe0ff707abdfd40b0a763d4003fab9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.2"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: "49d8f846ebeb5e2b6641fe477a7e97e5dd73f03cbfef3fd5c42177b7300fb0ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -320,6 +384,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
osm_nominatim:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: osm_nominatim
|
||||
sha256: "037f1af3abee92cf34e33b562cec1acbb0210234b1bdf3d8975bdef3849e6287"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -336,6 +408,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -381,6 +461,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -445,6 +533,78 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.5"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.5"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.3"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -495,4 +655,4 @@ packages:
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.3.2 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
flutter: ">=3.19.0"
|
||||
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+1
|
||||
version: 1.1.1+6
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.2 <4.0.0'
|
||||
@ -41,6 +41,10 @@ dependencies:
|
||||
flutter_map: ^6.1.0
|
||||
latlong2: ^0.9.0
|
||||
http: ^1.2.1
|
||||
flutter_timer_countdown: ^1.0.7
|
||||
geolocator: ^11.0.0
|
||||
osm_nominatim: ^3.0.0
|
||||
url_launcher: ^6.2.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -66,6 +70,7 @@ flutter:
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/ca/
|
||||
- assets/splash/splash.png
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
|
@ -39,6 +39,10 @@
|
||||
|
||||
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
||||
|
||||
|
||||
|
||||
|
||||
<style id="splash-screen-style">
|
||||
html {
|
||||
height: 100%
|
||||
|