438 lines
11 KiB
Dart
438 lines
11 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:audioplayers/audioplayers.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:stadtbote/main.dart';
|
|
import 'package:stadtbote/model/job.dart';
|
|
import 'package:stadtbote/model/log_entry.dart';
|
|
import 'package:stadtbote/tools/constants.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
import '../Tools/colored_safe_area.dart';
|
|
|
|
class Util {
|
|
static Future<bool> preferencesContainsKey(String key) async {
|
|
var prefs = await SharedPreferences.getInstance();
|
|
return prefs.containsKey(key);
|
|
}
|
|
|
|
static void preferencesSaveString(String key, String value) async {
|
|
var prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(key, value);
|
|
}
|
|
|
|
static Future<String?> preferencesGetString(String key, String? defaultValue) async {
|
|
if (await preferencesContainsKey(key)) {
|
|
var prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString(key);
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
static void preferencesSaveInt(String key, int value) async {
|
|
var prefs = await SharedPreferences.getInstance();
|
|
await prefs.setInt(key, value);
|
|
}
|
|
|
|
static Future<int?> preferencesGetInt(String key, int? defaultValue) async {
|
|
if (await preferencesContainsKey(key)) {
|
|
var prefs = await SharedPreferences.getInstance();
|
|
return prefs.getInt(key);
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
static void preferencesSaveBool(String key, bool value) async {
|
|
var prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(key, value);
|
|
}
|
|
|
|
static Future<bool> preferencesGetBool(String key, bool defaultValue) async {
|
|
if (await preferencesContainsKey(key)) {
|
|
var prefs = await SharedPreferences.getInstance();
|
|
return prefs.getBool(key) as bool;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
static Future<String> getAppUuid() async {
|
|
var currentUuid = await preferencesGetString('uuid', null);
|
|
|
|
if (currentUuid == null) {
|
|
currentUuid ??= const Uuid().v4();
|
|
|
|
preferencesSaveString("uuid", currentUuid);
|
|
}
|
|
|
|
return currentUuid;
|
|
}
|
|
|
|
static Future<int> getAppVersion() async {
|
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
|
|
|
return int.parse(packageInfo.version.replaceAll(".0.0", ""));
|
|
}
|
|
|
|
static _printLongText(String text) {
|
|
final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk
|
|
pattern.allMatches(text).forEach((match) => debugPrint(match.group(0)));
|
|
}
|
|
|
|
static void _persistLogMessage(String text, LogEntrySeverityEnum severity) async {
|
|
var logEntry = LogEntry();
|
|
logEntry.time = DateTime.now();
|
|
logEntry.text = text;
|
|
logEntry.severity = severity;
|
|
|
|
await MyApp.isarInstance.writeTxn(() async {
|
|
await MyApp.isarInstance.logEntrys.put(logEntry);
|
|
});
|
|
}
|
|
|
|
static logInfo(String message) {
|
|
_printLongText(message);
|
|
|
|
_persistLogMessage(message, LogEntrySeverityEnum.info);
|
|
}
|
|
|
|
static logDebug(String message) {
|
|
_printLongText(message);
|
|
|
|
_persistLogMessage(message, LogEntrySeverityEnum.debug);
|
|
}
|
|
|
|
static logException(String message) {
|
|
_printLongText(message);
|
|
|
|
_persistLogMessage(message, LogEntrySeverityEnum.error);
|
|
}
|
|
|
|
static int fastHash(String string) {
|
|
var hash = 0xcbf29ce484222325;
|
|
|
|
var i = 0;
|
|
while (i < string.length) {
|
|
final codeUnit = string.codeUnitAt(i++);
|
|
hash ^= codeUnit >> 8;
|
|
hash *= 0x100000001b3;
|
|
hash ^= codeUnit & 0xFF;
|
|
hash *= 0x100000001b3;
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
static Widget getWaitWidget(context) {
|
|
return ColoredSafeArea(
|
|
key: const Key('value'),
|
|
child: Container(
|
|
width: 10,
|
|
height: 10,
|
|
color: Colors.white,
|
|
),
|
|
);
|
|
}
|
|
|
|
static Future<Widget> fetchFutureIcon(icon) {
|
|
return Future.delayed(const Duration(seconds: 0), () async {
|
|
return icon;
|
|
});
|
|
}
|
|
|
|
static TextField getDefaultTextField(String hint,
|
|
{TextEditingController? controller,
|
|
FocusNode? focusNode,
|
|
IconButton? iconButton,
|
|
int? maxLines = 1,
|
|
TextInputType? textInputType,
|
|
List<TextInputFormatter>? formatter,
|
|
Function()? onTap,
|
|
bool? autofocus}) {
|
|
iconButton ??= IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: () {
|
|
if (controller != null) {
|
|
controller.text = "";
|
|
}
|
|
},
|
|
);
|
|
|
|
return TextField(
|
|
controller: controller,
|
|
focusNode: focusNode,
|
|
maxLines: maxLines,
|
|
onTap: onTap,
|
|
keyboardType: textInputType,
|
|
inputFormatters: formatter,
|
|
autofocus: (autofocus != null) ? autofocus : false,
|
|
decoration: InputDecoration(
|
|
hintText: hint,
|
|
contentPadding: const EdgeInsets.all(10),
|
|
border: Util.getDefaultBorder(),
|
|
errorBorder: Util.getDefaultBorder(),
|
|
disabledBorder: Util.getDefaultBorder(),
|
|
focusedBorder: Util.getDefaultFocusedBorder(),
|
|
focusedErrorBorder: Util.getDefaultBorder(),
|
|
suffixIcon: iconButton,
|
|
labelText: hint),
|
|
);
|
|
}
|
|
|
|
static OutlineInputBorder getDefaultBorder() {
|
|
return const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)));
|
|
}
|
|
|
|
static OutlineInputBorder getDefaultFocusedBorder() {
|
|
return const OutlineInputBorder(
|
|
borderSide: BorderSide(color: Constants.colorPrimary),
|
|
borderRadius: BorderRadius.all(Radius.circular(20.0)),
|
|
);
|
|
}
|
|
|
|
static Future<File?> capturePhoto() async {
|
|
final imagePicker = ImagePicker();
|
|
final pickedFile = await imagePicker.pickImage(source: ImageSource.camera);
|
|
|
|
if (pickedFile != null) {
|
|
return File(pickedFile.path);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static Future<void> startBrowser(String websiteUrl) async {
|
|
if (!await launchUrl(Uri.parse(websiteUrl))) {
|
|
throw Exception('Could not launch $websiteUrl');
|
|
}
|
|
}
|
|
|
|
static Future<void> playMp3(String name) async {
|
|
final player = AudioPlayer();
|
|
await player.play(AssetSource('sounds/$name'), volume: 1);
|
|
}
|
|
}
|
|
|
|
Widget getDefaultButton(String text, Function() onPressed, {Image? image, double? buttonHeight}) {
|
|
return SizedBox(
|
|
height: buttonHeight ?? 50,
|
|
child: MaterialButton(
|
|
color: const Color.fromARGB(255, 220, 220, 220),
|
|
disabledColor: Colors.indigo[400],
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(
|
|
Radius.circular(15),
|
|
),
|
|
),
|
|
onPressed: () {
|
|
Util.logInfo("Button \"$text\" clicked");
|
|
|
|
onPressed();
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: <Widget>[
|
|
Visibility(
|
|
visible: image != null,
|
|
child: CircleAvatar(
|
|
backgroundColor: Colors.transparent,
|
|
child: image,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
text,
|
|
style: const TextStyle(color: Colors.black, fontSize: 14.0),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String mapVehicleTypeToImageName(int vehType)
|
|
{
|
|
switch (vehType) {
|
|
case 1:
|
|
return "fahrrad.png";
|
|
|
|
case 2:
|
|
return "pkw.png";
|
|
|
|
case 3:
|
|
return "kombi.png";
|
|
|
|
case 4:
|
|
return "kasten.png";
|
|
|
|
case 5:
|
|
return "bus.png";
|
|
|
|
case 6:
|
|
return "grbus.png";
|
|
|
|
case 7:
|
|
return "lkw.png";
|
|
|
|
case 8:
|
|
return "lkw_12.png";
|
|
|
|
case 10:
|
|
return "lkw_8.png";
|
|
|
|
case 11:
|
|
return "lkw_10.png";
|
|
|
|
case 12:
|
|
return "lkw_15.png";
|
|
|
|
case 13:
|
|
return "lkw_20.png";
|
|
|
|
case 999:
|
|
return "service.png";
|
|
}
|
|
|
|
return "unbekannt.png";
|
|
}
|
|
|
|
String mapVehicleTypeToText(int vehType) {
|
|
switch (vehType) {
|
|
case 1:
|
|
return "Fahrrad";
|
|
|
|
case 2:
|
|
return "Pkw";
|
|
|
|
case 3:
|
|
return "Kombi";
|
|
|
|
case 4:
|
|
return "Kasten";
|
|
|
|
case 5:
|
|
return "Bus";
|
|
|
|
case 6:
|
|
return "GrBus";
|
|
|
|
case 7:
|
|
case 8:
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
case 13:
|
|
case 14:
|
|
return "Lkw";
|
|
|
|
case 999:
|
|
return "Service";
|
|
}
|
|
|
|
return "!?????!";
|
|
}
|
|
|
|
String mapTourMode(String mode) {
|
|
var text = "";
|
|
switch (mode) {
|
|
case "pu":
|
|
text += "/ Abholung";
|
|
break;
|
|
|
|
case "del":
|
|
text += "/ Auslieferung";
|
|
break;
|
|
|
|
case "pudel":
|
|
text += "/ Abholung/Auslieferung";
|
|
break;
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
Future<void> showQuestionDialog(BuildContext context, String title, String message, List<Widget> actions) async {
|
|
await showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: Text(title),
|
|
content: Text(message),
|
|
actions: actions,
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
Future<void> showAlertDialog(BuildContext context, String title, String message) async {
|
|
await showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: Text(title),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('OK'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
class DecimalTextInputFormatter extends TextInputFormatter {
|
|
int maxLength;
|
|
|
|
int decimalPlaces;
|
|
|
|
DecimalTextInputFormatter({required this.maxLength, required this.decimalPlaces});
|
|
|
|
@override
|
|
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
|
|
var text = newValue.text;
|
|
String? filteredText = "";
|
|
|
|
if (text != "") {
|
|
RegExp regExp;
|
|
if (decimalPlaces > 0) {
|
|
regExp = RegExp(r'[0-9]+[,]{0,1}[0-9]{0,' + decimalPlaces.toString() + '}');
|
|
} else {
|
|
regExp = RegExp(r'[0-9]{0,' + maxLength.toString() +'}');
|
|
}
|
|
filteredText = regExp.stringMatch(text);
|
|
}
|
|
|
|
return TextEditingValue(text: filteredText ?? oldValue.text);
|
|
}
|
|
}
|
|
|
|
bool isJobInFuture(Job job) {
|
|
return DateTime.parse(job.ordertime).add(Duration(minutes: job.offsWarn)).toUtc().millisecondsSinceEpoch > DateTime.now().toUtc().millisecondsSinceEpoch;
|
|
}
|
|
|
|
Future<bool> taskDialogSecurityException(BuildContext context) async {
|
|
bool close = false;
|
|
|
|
await showQuestionDialog(context, "Achtung", "Wenn Sie den Dialog jetzt schließen gehen erfasste Daten verloren und die Aufgabe oder Station bleibt unerledigt.", [
|
|
TextButton(onPressed: () => {Navigator.of(context).pop(), close = true}, child: const Text('Schließen')),
|
|
TextButton(onPressed: () => {Navigator.of(context).pop(), close = false}, child: const Text('Zurück')),
|
|
]);
|
|
|
|
if (close) {
|
|
Navigator.of(context).pop();
|
|
}
|
|
|
|
return close;
|
|
}
|