Files
votianlt/app/lib/routing_view.dart

142 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'l10n/app_localizations.dart';
import 'package:url_launcher/url_launcher.dart';
import 'widgets/offline_banner.dart';
// Routing view that immediately opens a Google Maps navigation inside a WebView
// It receives an address string and optionally a title.
class RoutingView extends StatefulWidget {
final String address;
final String? title;
final bool isDelivery; // to distinguish pickup/delivery if needed later
const RoutingView({super.key, required this.address, this.title, this.isDelivery = true});
@override
State<RoutingView> createState() => _RoutingViewState();
}
class _RoutingViewState extends State<RoutingView> {
bool _initialized = false;
late final WebViewController _controller;
double _progress = 0.0;
String _buildDirectionsUrl(String rawAddress) {
const apiKey = 'AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE';
final query = Uri.encodeComponent(rawAddress);
// Google Maps Directions URL with API key appended.
return 'https://www.google.com/maps/dir/?api=1&destination=$query&travelmode=driving&key=$apiKey';
}
String? _extractBrowserFallbackUrl(String intentUrl) {
const key = 'S.browser_fallback_url=';
final idx = intentUrl.indexOf(key);
if (idx == -1) return null;
final start = idx + key.length;
final end = intentUrl.indexOf(';', start);
final encoded = end == -1 ? intentUrl.substring(start) : intentUrl.substring(start, end);
try {
return Uri.decodeComponent(encoded);
} catch (_) {
return encoded;
}
}
String? _convertIntentToHttps(String intentUrl) {
const prefix = 'intent://';
if (!intentUrl.startsWith(prefix)) return null;
final after = intentUrl.substring(prefix.length);
final hashIndex = after.indexOf('#');
final pathPart = hashIndex == -1 ? after : after.substring(0, hashIndex);
// default scheme is https unless specified
var scheme = 'https';
final schemeKey = '#Intent;scheme=';
final schemeIdx = intentUrl.indexOf(schemeKey);
if (schemeIdx != -1) {
final start = schemeIdx + schemeKey.length;
final end = intentUrl.indexOf(';', start);
if (end > start) {
scheme = intentUrl.substring(start, end);
}
}
return '$scheme://$pathPart';
}
@override
void initState() {
assert(!_initialized);
super.initState();
final url = _buildDirectionsUrl(widget.address);
_controller =
WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (NavigationRequest request) async {
final uri = Uri.tryParse(request.url);
if (uri == null) {
return NavigationDecision.prevent;
}
// Handle intent:// and other non-http(s) schemes by launching externally or using fallback.
if (uri.scheme == 'intent') {
final fallback = _extractBrowserFallbackUrl(request.url);
if (fallback != null) {
await _controller.loadRequest(Uri.parse(fallback));
} else {
// Try converting to https as a naive fallback
final httpsCandidate = _convertIntentToHttps(request.url);
if (httpsCandidate != null && await canLaunchUrl(Uri.parse(httpsCandidate))) {
await launchUrl(Uri.parse(httpsCandidate), mode: LaunchMode.externalApplication);
} else {
// As a last resort, prevent navigation and show a hint
if (mounted) {
ScaffoldMessenger.maybeOf(context)?.showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).connectionError)));
}
}
}
return NavigationDecision.prevent;
}
if (uri.scheme != 'http' && uri.scheme != 'https' && uri.scheme != 'about' && uri.scheme != 'data') {
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
onProgress: (int progress) {
setState(() {
_progress = progress / 100.0;
});
},
onWebResourceError: (WebResourceError error) {
// Optionally show a snackbar on error
if (mounted) {
ScaffoldMessenger.maybeOf(context)?.showSnackBar(SnackBar(content: Text('${AppLocalizations.of(context).error}: ${error.errorCode}')));
}
},
),
)
..loadRequest(Uri.parse(url));
_initialized = true;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title?.isNotEmpty == true ? widget.title! : (widget.isDelivery ? 'Route zur Zustelladresse' : 'Route zur Abholadresse'))),
body: Column(
children: [
OfflineBanner(),
Expanded(child: Stack(children: [WebViewWidget(key: const ValueKey('routing-webview'), controller: _controller), if (_progress < 1.0) LinearProgressIndicator(value: _progress)])),
],
),
);
}
}