Skip to content

Commit 2a64f33

Browse files
committed
wip
1 parent 9e60b6e commit 2a64f33

File tree

9 files changed

+164
-1
lines changed

9 files changed

+164
-1
lines changed

android/app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,23 @@
2727
android:name="io.flutter.embedding.android.NormalTheme"
2828
android:resource="@style/NormalTheme"
2929
/>
30+
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
3031
<intent-filter>
3132
<action android:name="android.intent.action.MAIN"/>
3233
<category android:name="android.intent.category.LAUNCHER"/>
3334
</intent-filter>
35+
<!-- App Links for deep link support -->
36+
<intent-filter android:autoVerify="true">
37+
<action android:name="android.intent.action.VIEW" />
38+
<category android:name="android.intent.category.DEFAULT" />
39+
<category android:name="android.intent.category.BROWSABLE" />
40+
<data android:scheme="https" />
41+
<data android:host="keevault.pm" />
42+
<data android:host="app-beta.kee.pm" />
43+
<data android:host="app-dev.kee.pm" />
44+
<!--TODO: specific strings for each build stage/varient AND accept only requests that match expected URL patterns ... -->
45+
<data android:fragmentPrefix="dest=" /> <!-- only for API 35+ .. .what happens to older devices? -->
46+
</intent-filter>
3447
</activity>
3548
<activity
3649
android:name=".AutofillActivity"

ios/Runner/Info.plist

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>CFBundleURLTypes</key>
6+
<array>
7+
<dict>
8+
<key>CFBundleURLSchemes</key>
9+
<array>
10+
<string>https</string>
11+
</array>
12+
13+
<key>CFBundleURLName</key>
14+
<string>yourIdentifier</string>
15+
</dict>
16+
</array>
17+
<key>FlutterDeepLinkingEnabled</key>
18+
<false/>
19+
below should be in entitlements
20+
<key>com.apple.developer.associated-domains</key>
21+
<array>
22+
<string>applinks:keevault.pm</string>
23+
<string>TODO: flavours</string>
24+
<string>applinks:app-dev.kee.pm</string>
25+
</array>
526
<key>CADisableMinimumFrameDurationOnPhone</key>
627
<true/>
728
<key>CFBundleDevelopmentRegion</key>

lib/config/route_handlers.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,21 @@ import '../widgets/blocking_overlay.dart';
1313
import '../widgets/change_email_prefs.dart';
1414
import '../widgets/change_password.dart';
1515
import '../widgets/root.dart' as kv_root;
16+
import 'package:keevault/widgets/root.dart' as kv_root;
17+
import 'package:keevault/screens/deep_link_echo_screen.dart';
1618

19+
var deepLinkEchoHandler = Handler(
20+
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
21+
// Accepts params via Fluro route, but expects navigation with arguments.
22+
final args = ModalRoute.of(context!)?.settings.arguments;
23+
if (args is Map<String, dynamic>) {
24+
final parsedParams = args['params'] as Map<String, String>? ?? {};
25+
final rawUrl = args['rawUrl'] as String?;
26+
return DeepLinkEchoScreen(params: parsedParams, rawUrl: rawUrl);
27+
}
28+
return DeepLinkEchoScreen(params: {}, rawUrl: null);
29+
},
30+
);
1731
var rootHandler = Handler(
1832
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
1933
return const kv_root.RootWidget();

lib/config/routes.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Routes {
1616
static String changeEmailPrefs = '/change_email_prefs';
1717
static String changeSubscription = '/change_subscription';
1818
static String createAccount = '/create_account/:email';
19+
static String deepLinkEcho = '/deep_link_echo';
1920

2021
static void configureRoutes(FluroRouter router) {
2122
router.notFoundHandler = Handler(
@@ -36,5 +37,6 @@ class Routes {
3637
router.define(changeEmailPrefs, handler: changeEmailPrefsHandler);
3738
router.define(changeSubscription, handler: changeSubscriptionHandler);
3839
router.define(createAccount, handler: createAccountHandler);
40+
router.define(deepLinkEcho, handler: deepLinkEchoHandler);
3941
}
4042
}

lib/main.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import 'package:flutter/services.dart';
55
import 'package:keevault/logging/logger.dart';
66
import 'package:keevault/payment_service.dart';
77
import 'package:keevault/widgets/dialog_utils.dart';
8+
import 'package:app_links/app_links.dart';
9+
import 'package:keevault/utils/deep_link_utils.dart';
10+
import 'package:keevault/config/routes.dart';
811
import 'package:logger/logger.dart';
912
import 'package:matomo_tracker/matomo_tracker.dart' hide Level;
1013
import 'package:public_suffix/public_suffix.dart';
@@ -38,6 +41,26 @@ void main() async {
3841
DefaultSuffixRules.initFromString(suffixList);
3942
l.i('Initialized PSL');
4043
runApp(KeeVaultApp(navigatorKey: navigatorKey));
44+
45+
// Deep link handling
46+
final appLinks = AppLinks();
47+
appLinks.uriLinkStream.listen((Uri uri) {
48+
final fragment = uri.fragment;
49+
final host = uri.host;
50+
final validHosts = ['keevault.pm', 'app-beta.kee.pm', 'app-dev.kee.pm'];
51+
if (validHosts.contains(host) && fragment.isNotEmpty) {
52+
final params = parseKeeVaultFragment(fragment);
53+
if (params != null) {
54+
navigatorKey.currentState?.pushNamed(
55+
Routes.deepLinkEcho,
56+
arguments: {'params': params, 'rawUrl': uri.toString()},
57+
);
58+
return;
59+
}
60+
}
61+
// If not valid, do nothing (let browser handle)
62+
//TODO: above is bullshit?
63+
});
4164
},
4265
(dynamic error, StackTrace stackTrace) {
4366
if (error is KeeLoginFailedMITMException) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import 'package:flutter/material.dart';
2+
3+
class DeepLinkEchoScreen extends StatelessWidget {
4+
final Map<String, String> params;
5+
final String? rawUrl;
6+
7+
const DeepLinkEchoScreen({Key? key, required this.params, this.rawUrl}) : super(key: key);
8+
9+
@override
10+
Widget build(BuildContext context) {
11+
return Scaffold(
12+
appBar: AppBar(title: const Text('Deep Link Echo')),
13+
body: Padding(
14+
padding: const EdgeInsets.all(16.0),
15+
child: Column(
16+
crossAxisAlignment: CrossAxisAlignment.start,
17+
children: [
18+
if (rawUrl != null) ...[
19+
Text('Raw URL:', style: Theme.of(context).textTheme.titleMedium),
20+
SelectableText(rawUrl!),
21+
const SizedBox(height: 16),
22+
],
23+
Text('Parsed Parameters:', style: Theme.of(context).textTheme.titleMedium),
24+
...params.entries.map((e) => Text('${e.key}: ${e.value}')),
25+
],
26+
),
27+
),
28+
);
29+
}
30+
}

lib/utils/deep_link_utils.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'dart:core';
2+
3+
/// Parses a KeeVault deep link fragment like:
4+
/// #dest=resetPasswordConfirm,resetAuthToken=<token>,resetEmail=<emailaddress>
5+
/// Returns a map of parameters if dest matches expected value, else null.
6+
Map<String, String>? parseKeeVaultFragment(String fragment, {String expectedDest = 'resetPasswordConfirm'}) {
7+
if (!fragment.startsWith('dest=')) return null;
8+
final parts = fragment.substring(5).split(',');
9+
if (parts.isEmpty) return null;
10+
final dest = parts[0];
11+
if (dest != expectedDest) return null;
12+
final params = <String, String>{'dest': dest};
13+
for (var i = 1; i < parts.length; i++) {
14+
final kv = parts[i].split('=');
15+
if (kv.length == 2) {
16+
params[kv[0]] = kv[1];
17+
}
18+
}
19+
return params;
20+
}

pubspec.lock

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,38 @@ packages:
4141
url: "https://pub.dev"
4242
source: hosted
4343
version: "2.0.3"
44+
app_links:
45+
dependency: "direct main"
46+
description:
47+
name: app_links
48+
sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
49+
url: "https://pub.dev"
50+
source: hosted
51+
version: "6.4.0"
52+
app_links_linux:
53+
dependency: transitive
54+
description:
55+
name: app_links_linux
56+
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
57+
url: "https://pub.dev"
58+
source: hosted
59+
version: "1.0.3"
60+
app_links_platform_interface:
61+
dependency: transitive
62+
description:
63+
name: app_links_platform_interface
64+
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
65+
url: "https://pub.dev"
66+
source: hosted
67+
version: "2.0.2"
68+
app_links_web:
69+
dependency: transitive
70+
description:
71+
name: app_links_web
72+
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
73+
url: "https://pub.dev"
74+
source: hosted
75+
version: "1.0.4"
4476
archive:
4577
dependency: "direct main"
4678
description:
@@ -491,6 +523,14 @@ packages:
491523
url: "https://pub.dev"
492524
source: hosted
493525
version: "2.1.3"
526+
gtk:
527+
dependency: transitive
528+
description:
529+
name: gtk
530+
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
531+
url: "https://pub.dev"
532+
source: hosted
533+
version: "2.1.0"
494534
html:
495535
dependency: transitive
496536
description:

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ dependencies:
8080
# The following adds the Cupertino Icons font to your application.
8181
# Use with the CupertinoIcons class for iOS style icons.
8282
cupertino_icons: ^1.0.8
83-
83+
app_links: ^6.4.0
8484
flutter_persistent_queue:
8585
git:
8686
url: https://github.com/kee-org/flutter_persistent_queue

0 commit comments

Comments
 (0)