9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
141 lines
4.5 KiB
Dart
141 lines
4.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import '../../shared/deeplink/deep_link.dart';
|
|
import '../../shared/deeplink/pending_deep_link_provider.dart';
|
|
import '../../shared/l10n/l10n.dart';
|
|
import '../invites/invite_join_provider.dart';
|
|
import '../invites/invite_join_sheet.dart';
|
|
import 'channel.dart';
|
|
import 'channel_detail_page.dart';
|
|
import 'channels_provider.dart';
|
|
|
|
/// Routes pending `buzz://message` deep links into the channel view.
|
|
///
|
|
/// Wraps the authenticated home subtree. Whenever a parsed link is parked in
|
|
/// [pendingDeepLinkProvider] and the channel list is available, this pushes
|
|
/// the target [ChannelDetailPage] on the enclosing [Navigator]. Links are
|
|
/// held (not dropped) while channels are still loading, so cold-start links
|
|
/// dispatch as soon as the first channel fetch completes.
|
|
typedef DeepLinkDestinationBuilder =
|
|
Widget Function(Channel channel, MessageDeepLink link);
|
|
|
|
class DeepLinkDispatcher extends ConsumerStatefulWidget {
|
|
final Widget child;
|
|
final DeepLinkDestinationBuilder? destinationBuilder;
|
|
final bool dispatchMessageLinks;
|
|
|
|
const DeepLinkDispatcher({
|
|
super.key,
|
|
required this.child,
|
|
this.destinationBuilder,
|
|
this.dispatchMessageLinks = true,
|
|
});
|
|
|
|
@override
|
|
ConsumerState<DeepLinkDispatcher> createState() => _DeepLinkDispatcherState();
|
|
}
|
|
|
|
class _DeepLinkDispatcherState extends ConsumerState<DeepLinkDispatcher> {
|
|
bool _preparingInvite = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!mounted) return;
|
|
_maybeDispatch(ref.read(pendingDeepLinkProvider));
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Re-evaluate dispatch when either a new link arrives or channels load.
|
|
ref.listen<BuzzDeepLink?>(pendingDeepLinkProvider, (_, link) {
|
|
_maybeDispatch(link);
|
|
});
|
|
if (widget.dispatchMessageLinks) {
|
|
ref.listen<AsyncValue<List<Channel>>>(channelsProvider, (_, _) {
|
|
_maybeDispatch(ref.read(pendingDeepLinkProvider));
|
|
});
|
|
}
|
|
|
|
return widget.child;
|
|
}
|
|
|
|
void _maybeDispatch(BuzzDeepLink? link) {
|
|
if (link == null) return;
|
|
if (link is InviteDeepLink) {
|
|
_maybeDispatchInvite(link);
|
|
return;
|
|
}
|
|
if (link is! MessageDeepLink || !widget.dispatchMessageLinks) return;
|
|
|
|
final channels = ref.read(channelsProvider).asData?.value;
|
|
// Channels not loaded yet — keep the link parked; the channelsProvider
|
|
// listener re-attempts once data arrives.
|
|
if (channels == null) return;
|
|
|
|
ref.read(pendingDeepLinkProvider.notifier).consume();
|
|
|
|
final channel = channels
|
|
.where((c) => c.id == link.channelId)
|
|
.cast<Channel?>()
|
|
.firstOrNull;
|
|
if (channel == null) {
|
|
debugPrint(
|
|
'deep-link: channel ${link.channelId} not found in workspace; '
|
|
'dropping link',
|
|
);
|
|
ScaffoldMessenger.maybeOf(context)?.showSnackBar(
|
|
SnackBar(content: Text(context.l10n.channelNotFound)),
|
|
);
|
|
return;
|
|
}
|
|
if (!context.mounted) return;
|
|
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(
|
|
builder: (_) =>
|
|
widget.destinationBuilder?.call(channel, link) ??
|
|
ChannelDetailPage(
|
|
channel: channel,
|
|
initialMessageId: link.messageId,
|
|
initialThreadRootId: link.threadRootId,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _maybeDispatchInvite(InviteDeepLink link) {
|
|
if (_preparingInvite) return;
|
|
_preparingInvite = true;
|
|
final navigatorContext = context;
|
|
final messenger = ScaffoldMessenger.maybeOf(context);
|
|
Future.microtask(() async {
|
|
try {
|
|
await ref.read(inviteJoinProvider.notifier).prepare(link);
|
|
ref.read(pendingDeepLinkProvider.notifier).consume();
|
|
if (!navigatorContext.mounted) return;
|
|
final status = ref.read(inviteJoinProvider).status;
|
|
if (status == InviteJoinStatus.confirming) {
|
|
showInviteJoinSheet(navigatorContext, ref);
|
|
} else if (status == InviteJoinStatus.switchedExisting) {
|
|
messenger?.showSnackBar(
|
|
SnackBar(content: Text(navigatorContext.l10n.switchedCommunity)),
|
|
);
|
|
}
|
|
} catch (error) {
|
|
debugPrint('deep-link: failed to prepare invite: $error');
|
|
if (navigatorContext.mounted) {
|
|
messenger?.showSnackBar(
|
|
SnackBar(content: Text(navigatorContext.l10n.inviteOpenFailed)),
|
|
);
|
|
}
|
|
} finally {
|
|
_preparingInvite = false;
|
|
}
|
|
});
|
|
}
|
|
}
|