feat: support smol:// QR sharing, deep links and in-dialog scanning
This commit is contained in:
parent
7d2a147fec
commit
aa74e12ee7
20 changed files with 591 additions and 101 deletions
|
|
@ -1,9 +1,15 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:app_links/app_links.dart";
|
||||
import "package:auto_route/auto_route.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
|
||||
import "package:smol_mail/data/providers/providers.dart";
|
||||
import "package:smol_mail/presentation/theme/theme.dart";
|
||||
import "package:smol_mail/presentation/routes/app_router.dart";
|
||||
import "package:smol_mail/presentation/routes/app_router.gr.dart";
|
||||
import "package:smol_mail/presentation/theme/theme.dart";
|
||||
import "package:smol_mail/smol/link.dart";
|
||||
|
||||
/// SnackBars need no context through this key, so the client's trust
|
||||
/// warnings can be shown from anywhere in the protocol stack.
|
||||
|
|
@ -17,6 +23,10 @@ class AppWidget extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _AppWidgetState extends ConsumerState<AppWidget> {
|
||||
late final AppRouter _router = AppRouter(ref);
|
||||
final AppLinks _appLinks = AppLinks();
|
||||
StreamSubscription<Uri>? _linkSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -37,6 +47,33 @@ class _AppWidgetState extends ConsumerState<AppWidget> {
|
|||
),
|
||||
);
|
||||
};
|
||||
|
||||
_linkSub = _appLinks.uriLinkStream.listen(_handleLink);
|
||||
}
|
||||
|
||||
// A smol:// link is the self-certifying form: the §8 strong path,
|
||||
// confirmed in the import dialog route before its key is bound. The push
|
||||
// waits a frame — the stream can deliver the cold-start link while the
|
||||
// first frame is still being laid out.
|
||||
void _handleLink(Uri uri) {
|
||||
final link = parseSmolLink(uri.toString());
|
||||
if (link == null) return;
|
||||
if (ref.read(identityProvider) == null) {
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("finish onboarding before using smol:// links")),
|
||||
);
|
||||
return;
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => _router.push(ImportContactDialogRoute(prefill: link.source)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_linkSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -48,7 +85,16 @@ class _AppWidgetState extends ConsumerState<AppWidget> {
|
|||
themeMode: ThemeMode.system,
|
||||
debugShowCheckedModeBanner: false,
|
||||
scaffoldMessengerKey: rootScaffoldMessengerKey,
|
||||
routerConfig: AppRouter(ref).config(),
|
||||
// smol:// URIs are for app_links, not the route table. The startup link
|
||||
// must still resolve to a real route — auto_route throws "Can not
|
||||
// resolve initial route" on an unmatched link and leaves a blank
|
||||
// screen — so it lands on the normal entry route, whose guard
|
||||
// redirects to home exactly like a launcher start. Every later
|
||||
// platform link is ignored by the router; the stream handler reacts.
|
||||
routerConfig: _router.config(
|
||||
deepLinkBuilder: (link) =>
|
||||
link.initial ? DeepLink.single(const OnboardingRoute()) : DeepLink.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue