mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-06-29 17:49:59 +02:00
Merge pull request #166 from FoggedLens/feature/node-deeplink
Feature/node deeplink
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="deflockapp" android:host="profiles"/>
|
||||
<data android:scheme="deflockapp" android:host="node"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import '../models/osm_node.dart';
|
||||
import '../models/suspected_location.dart';
|
||||
import '../models/search_result.dart';
|
||||
import '../services/changelog_service.dart';
|
||||
import '../services/deep_link_service.dart';
|
||||
import 'coordinators/sheet_coordinator.dart';
|
||||
import 'coordinators/navigation_coordinator.dart';
|
||||
import 'coordinators/map_interaction_handler.dart';
|
||||
@@ -57,10 +58,12 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
_sheetCoordinator = SheetCoordinator();
|
||||
_navigationCoordinator = NavigationCoordinator();
|
||||
_mapInteractionHandler = MapInteractionHandler();
|
||||
DeepLinkService().onNodeDeepLink = _handleNodeDeepLink;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
DeepLinkService().onNodeDeepLink = null;
|
||||
_mapController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -286,6 +289,25 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleNodeDeepLink(OsmNode node) {
|
||||
try {
|
||||
_mapController.animateTo(
|
||||
dest: node.coord,
|
||||
zoom: 16.0,
|
||||
duration: const Duration(milliseconds: 600),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[HomeScreen] Could not animate to deep link node: $e');
|
||||
}
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 700), () {
|
||||
if (mounted) {
|
||||
openNodeTagSheet(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void openNodeTagSheet(OsmNode node) {
|
||||
// Handle the map interaction (centering and follow-me disable)
|
||||
_mapInteractionHandler.handleNodeTap(
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../models/node_profile.dart';
|
||||
import '../models/operator_profile.dart';
|
||||
import '../models/osm_node.dart';
|
||||
import 'http_client.dart';
|
||||
import 'profile_import_service.dart';
|
||||
import 'operator_profile_import_service.dart';
|
||||
import '../screens/profile_editor.dart';
|
||||
@@ -16,6 +20,9 @@ class DeepLinkService {
|
||||
|
||||
late AppLinks _appLinks;
|
||||
StreamSubscription<Uri>? _linkSubscription;
|
||||
|
||||
/// Callback for HomeScreen to receive node deep links
|
||||
void Function(OsmNode node)? onNodeDeepLink;
|
||||
|
||||
/// Initialize deep link handling (sets up stream listener only)
|
||||
Future<void> init() async {
|
||||
@@ -45,6 +52,9 @@ class DeepLinkService {
|
||||
case 'profiles':
|
||||
_handleProfilesLink(uri);
|
||||
break;
|
||||
case 'node':
|
||||
_handleNodeLink(uri);
|
||||
break;
|
||||
case 'auth':
|
||||
// OAuth links are handled by flutter_web_auth_2
|
||||
debugPrint('[DeepLinkService] OAuth link handled by flutter_web_auth_2');
|
||||
@@ -71,6 +81,59 @@ class DeepLinkService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle node deep link: `deflockapp://node?id=<nodeId>`
|
||||
Future<void> _handleNodeLink(Uri uri) async {
|
||||
final idStr = uri.queryParameters['id'];
|
||||
final nodeId = int.tryParse(idStr ?? '');
|
||||
if (nodeId == null) {
|
||||
_showError('Invalid node link: missing or invalid ID');
|
||||
return;
|
||||
}
|
||||
|
||||
final node = await _fetchNodeById(nodeId);
|
||||
if (node == null) {
|
||||
_showError('Node $nodeId not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (onNodeDeepLink != null) {
|
||||
onNodeDeepLink!(node);
|
||||
} else {
|
||||
debugPrint('[DeepLinkService] No node deep link handler registered');
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch an OSM node by ID from the OpenStreetMap API
|
||||
Future<OsmNode?> _fetchNodeById(int nodeId) async {
|
||||
try {
|
||||
final url = Uri.parse('https://api.openstreetmap.org/api/0.6/node/$nodeId.json');
|
||||
final client = UserAgentClient();
|
||||
final response = await client.get(url);
|
||||
if (response.statusCode != 200) return null;
|
||||
|
||||
final json = jsonDecode(response.body);
|
||||
final elements = json['elements'] as List?;
|
||||
if (elements == null || elements.isEmpty) return null;
|
||||
|
||||
final e = elements[0];
|
||||
final tags = <String, String>{};
|
||||
if (e['tags'] != null) {
|
||||
(e['tags'] as Map<String, dynamic>).forEach((k, v) {
|
||||
tags[k] = v.toString();
|
||||
});
|
||||
}
|
||||
|
||||
return OsmNode(
|
||||
id: e['id'] as int,
|
||||
coord: LatLng((e['lat'] as num).toDouble(), (e['lon'] as num).toDouble()),
|
||||
tags: tags,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[DeepLinkService] Failed to fetch node $nodeId: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle profile-related deep links
|
||||
void _handleProfilesLink(Uri uri) {
|
||||
final segments = uri.pathSegments;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('Node deep link URL parsing', () {
|
||||
test('extracts node ID from valid link', () {
|
||||
final uri = Uri.parse('deflockapp://node?id=1234567890');
|
||||
expect(uri.host, 'node');
|
||||
expect(uri.queryParameters['id'], '1234567890');
|
||||
});
|
||||
|
||||
test('returns null for missing id param', () {
|
||||
final uri = Uri.parse('deflockapp://node');
|
||||
expect(uri.queryParameters['id'], isNull);
|
||||
});
|
||||
|
||||
test('returns null for empty id param', () {
|
||||
final uri = Uri.parse('deflockapp://node?id=');
|
||||
expect(uri.queryParameters['id'], '');
|
||||
});
|
||||
|
||||
test('returns null for non-numeric id', () {
|
||||
final uri = Uri.parse('deflockapp://node?id=abc');
|
||||
expect(int.tryParse(uri.queryParameters['id'] ?? ''), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user