mirror of
https://github.com/block/buzz.git
synced 2026-11-02 15:31:27 +00:00
**Category:** improvement **User Impact:** Buzz channel, message, repository, pull request, and issue links now display recognizable context and navigate reliably in the mobile app. **Problem:** Bare Buzz permalinks appeared as raw or ambiguous URLs on mobile, while channel and message links were not handled consistently across Markdown forms and startup states. **Solution:** Normalize eligible bare Buzz URLs without consuming Markdown syntax, render them as semantic icon-prefixed chips, and route channel/message targets through the mobile deep-link dispatcher while preserving authored Markdown labels as ordinary links. <details> <summary>File changes</summary> **mobile/lib/features/channels/deep_link_dispatcher.dart** Routes parsed channel and message links through the appropriate in-app navigation callbacks. **mobile/lib/features/channels/message_content.dart** Presents all bare Buzz permalinks as semantic icon chips and keeps authored labels as ordinary links. **mobile/lib/features/channels/message_content/link_normalizer.dart** Normalizes bare and autolinked Buzz URLs without consuming Markdown delimiters, code, or punctuation. **mobile/lib/shared/deeplink/deep_link.dart** Adds strict channel and project-entity parsing alongside message deep links. **mobile/lib/shared/deeplink/pending_deep_link_provider.dart** Preserves pending navigation until the mobile routing surface is ready. **mobile/test/features/channels/channel_detail_page_test.dart** Updates navigation integration coverage for icon-prefixed channel chips. **mobile/test/features/channels/deep_link_dispatcher_test.dart** Covers channel/message dispatch and missing-target behavior. **mobile/test/features/channels/message_content/link_normalizer_test.dart** Exercises Markdown-safe normalization across the full Buzz link suite. **mobile/test/features/channels/message_content_test.dart** Verifies chip labels, icons, semantics, authored-label opt-out, and navigation callbacks. **mobile/test/shared/deeplink/deep_link_test.dart** Covers strict parsing for channel, message, repository, pull-request, and issue links. </details> ## Reproduction steps 1. Run the mobile app and open a channel containing bare `buzz://channel`, `buzz://message`, `buzz://repo`, `buzz://pr`, and `buzz://issue` URLs. 2. Confirm each bare URL renders as one cohesive chip with a type icon, a useful name or shortened identifier, and no duplicated channel `#` character. 3. Add an authored Markdown link such as `[design discussion](buzz://issue?...)` and confirm the supplied label remains an ordinary link rather than becoming a chip. 4. Select channel and message links and confirm they navigate correctly from inline and autolinked forms. ## Screenshots / demos **iOS Simulator — channel, message, repository, pull request, and issue permalink chips** Real app build (`37b2cb5eb`) running on an iPhone 17 Pro simulator.  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
62 lines
1.9 KiB
Dart
62 lines
1.9 KiB
Dart
import 'dart:async';
|
|
import 'dart:collection';
|
|
|
|
import 'package:app_links/app_links.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import 'deep_link.dart';
|
|
|
|
/// Holds supported deep links until they can be dispatched.
|
|
///
|
|
/// Links are queued in arrival order. Navigation cannot always happen the
|
|
/// moment a link arrives — the user may not be authenticated yet, channels may
|
|
/// still be loading, or another link may already be dispatching. Keeping a FIFO
|
|
/// prevents a later app-link event from silently replacing an earlier one.
|
|
///
|
|
/// Listens to [AppLinks.uriLinkStream], which delivers both the cold-start link
|
|
/// (the URL that launched the app) and links received while running.
|
|
class PendingDeepLinkNotifier extends Notifier<BuzzDeepLink?> {
|
|
@visibleForTesting
|
|
static Stream<Uri>? debugUriStreamOverride;
|
|
|
|
StreamSubscription<Uri>? _subscription;
|
|
final Queue<BuzzDeepLink> _waiting = Queue<BuzzDeepLink>();
|
|
|
|
@override
|
|
BuzzDeepLink? build() {
|
|
_waiting.clear();
|
|
final stream = debugUriStreamOverride ?? AppLinks().uriLinkStream;
|
|
_subscription = stream.listen(open);
|
|
ref.onDispose(() {
|
|
_subscription?.cancel();
|
|
_subscription = null;
|
|
});
|
|
return null;
|
|
}
|
|
|
|
/// Parse and park an incoming URI. Unsupported links are ignored loudly.
|
|
void open(Uri uri) {
|
|
final link = parseBuzzDeepLink(uri);
|
|
if (link == null) {
|
|
debugPrint('deep-link: ignoring unsupported link: $uri');
|
|
return;
|
|
}
|
|
if (state == null) {
|
|
state = link;
|
|
} else {
|
|
_waiting.addLast(link);
|
|
}
|
|
}
|
|
|
|
/// Acknowledge the current link and expose the next queued link, if any.
|
|
void consume() {
|
|
state = _waiting.isEmpty ? null : _waiting.removeFirst();
|
|
}
|
|
}
|
|
|
|
final pendingDeepLinkProvider =
|
|
NotifierProvider<PendingDeepLinkNotifier, BuzzDeepLink?>(
|
|
PendingDeepLinkNotifier.new,
|
|
);
|