mirror of
https://github.com/block/buzz.git
synced 2026-10-27 20:41:57 +00:00
## Summary Replace message long-press popovers with the shared grouped bottom sheet: an emoji row, Reply / Copy link / Remind me tiles, and remaining actions in regular sections. Match emoji backgrounds to the sections and use the theme primary color for the selected reaction. Keep the scrolling date above the navigation blur in channels and threads. Restore composer focus when the sheet is canceled, keep section gaps consistent, and soften shared dividers to 7% opacity. The reaction details sheet shows all reactors by default. Emoji count chips toggle your own reaction, show your selected state, and update live without dismissing the sheet. Unselected reactions have visible outlines; adding a supported emoji plays the chat-style burst and respects reduced motion. ### Related issue No matching issue found. Related sheet styling: #4911. ### Testing - Full mobile checks and tests passed, including 23 reaction tests and composer-focus regression coverage. - Analysis and file-size checks passed; signed iOS build installed and launched on a physical iPhone. - Full `just ci` stopped during Rust compilation due to low disk space. - On-device visual capture unavailable. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
71 lines
2.4 KiB
Dart
71 lines
2.4 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
/// UIKit presentation for message actions and reaction membership on iOS.
|
|
/// A null response means unavailable; a dismissed native surface is handled.
|
|
class NativeMessagePresentation {
|
|
/// Whether the current platform can host UIKit.
|
|
static bool get isSupportedPlatform =>
|
|
!kIsWeb && defaultTargetPlatform == TargetPlatform.iOS;
|
|
|
|
/// Transport shared with the iOS presentation coordinator.
|
|
static const channel = MethodChannel('buzz/native_message_presentation');
|
|
|
|
static final _presentedCallbacks = <String, VoidCallback>{};
|
|
static var _nextRequest = 0;
|
|
static bool _active = false;
|
|
|
|
/// Owns preflight through dismissal across message and reaction surfaces.
|
|
/// An overlapping request is consumed instead of opening a fallback surface.
|
|
static Future<bool> withLease(Future<bool> Function() presentation) async {
|
|
if (_active) return true;
|
|
_active = true;
|
|
try {
|
|
return await presentation();
|
|
} finally {
|
|
_active = false;
|
|
}
|
|
}
|
|
|
|
/// Completes after dismissal so callers can safely open the next surface.
|
|
static Future<Map<Object?, Object?>?> present(
|
|
String method,
|
|
Map<String, Object?> arguments, {
|
|
VoidCallback? onPresented,
|
|
ValueChanged<String>? onReaction,
|
|
}) async {
|
|
if (!isSupportedPlatform) return null;
|
|
final requestId =
|
|
arguments['requestId'] as String? ?? 'message-${_nextRequest++}';
|
|
if (onPresented != null || onReaction != null) {
|
|
if (onPresented != null) _presentedCallbacks[requestId] = onPresented;
|
|
channel.setMethodCallHandler((call) async {
|
|
if (call.method == 'toggleReaction') {
|
|
final data = call.arguments as Map;
|
|
if (data['requestId'] == requestId && data['emoji'] is String) {
|
|
onReaction?.call(data['emoji'] as String);
|
|
}
|
|
}
|
|
if (call.method == 'messagePresented') {
|
|
_presentedCallbacks[call.arguments]?.call();
|
|
}
|
|
});
|
|
}
|
|
try {
|
|
return await channel.invokeMapMethod<Object?, Object?>(method, {
|
|
...arguments,
|
|
'requestId': requestId,
|
|
});
|
|
} on MissingPluginException {
|
|
return null;
|
|
} on PlatformException {
|
|
return null;
|
|
} finally {
|
|
_presentedCallbacks.remove(requestId);
|
|
if (onPresented != null || onReaction != null) {
|
|
channel.setMethodCallHandler(null);
|
|
}
|
|
}
|
|
}
|
|
}
|