fixed chat bubble link styling and gesture handling, and added android package visibility for common schemes

This commit is contained in:
2026-05-10 00:54:13 +02:00
parent 1ff57b29f9
commit ed2badfd35
3 changed files with 51 additions and 9 deletions
+23 -5
View File
@@ -73,16 +73,34 @@
android:resource="@xml/timetable_week_widget_info" /> android:resource="@xml/timetable_week_widget_info" />
</receiver> </receiver>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required so url_launcher / can_launch can actually see browsers,
https://developer.android.com/training/package-visibility?hl=en and mail clients and dialers under Android 11+ package-visibility rules
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT. (otherwise UrlLauncher logs "component name for ... is null" and
link taps in Talk silently do nothing). The PROCESS_TEXT intent is
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> needed by io.flutter.plugin.text.ProcessTextPlugin (selection
menu).
See https://developer.android.com/training/package-visibility -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.PROCESS_TEXT"/> <action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain"/>
</intent> </intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="mailto"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="tel"/>
</intent>
</queries> </queries>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<!-- Workmanager periodic widget refresh needs to reschedule after device <!-- Workmanager periodic widget refresh needs to reschedule after device
+13 -1
View File
@@ -205,6 +205,18 @@ class _ChatBubbleState extends State<ChatBubble>
onRefetch: widget.refetch, onRefetch: widget.refetch,
); );
/// True only for messages whose body has a meaningful tap action (poll
/// dialog or file download/cancel). For plain text messages we leave
/// `onTap: null` on the bubble's `GestureDetector` so its
/// `TapGestureRecognizer` does not enter the gesture arena — otherwise
/// it competes with (and blocks) the per-link `TapGestureRecognizer`s
/// that `HighlightedLinkify` attaches to URL spans.
bool get _hasTapAction {
final obj = message.originalData?['object'];
if (obj?.type == RichObjectStringObjectType.talkPoll) return true;
return message.file != null;
}
void _onTap() { void _onTap() {
final obj = message.originalData?['object']; final obj = message.originalData?['object'];
if (obj?.type == RichObjectStringObjectType.talkPoll) { if (obj?.type == RichObjectStringObjectType.talkPoll) {
@@ -302,7 +314,7 @@ class _ChatBubbleState extends State<ChatBubble>
}, },
onLongPress: _showOptionsDialog, onLongPress: _showOptionsDialog,
onDoubleTap: _showOptionsDialog, onDoubleTap: _showOptionsDialog,
onTap: _onTap, onTap: _hasTapAction ? _onTap : null,
child: Transform.translate( child: Transform.translate(
offset: _position, offset: _position,
child: Bubble( child: Bubble(
@@ -98,9 +98,21 @@ class _HighlightedLinkifyState extends State<HighlightedLinkify> {
final defaultStyle = widget.style ?? final defaultStyle = widget.style ??
Theme.of(context).textTheme.bodyMedium ?? Theme.of(context).textTheme.bodyMedium ??
DefaultTextStyle.of(context).style; DefaultTextStyle.of(context).style;
final linkStyle = (widget.linkStyle ?? // Start from the surrounding text style so links inherit font family,
const TextStyle(color: Colors.blue, decoration: TextDecoration.underline)) // size, weight, etc., then layer the link-specific color and underline
.merge(defaultStyle.copyWith(color: null, decoration: null)); // on top. (Going the other way around — link style as base — used to
// work because TextStyle.copyWith treats `null` as "leave unchanged",
// so the explicit `color: null, decoration: null` were silently
// ignored and the merge pulled defaultStyle's color/decoration over
// the blue + underline. Result: links rendered in body-text color
// with no underline.)
final linkStyle = defaultStyle.merge(
widget.linkStyle ??
const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
);
const linkHighlight = TextStyle( const linkHighlight = TextStyle(
backgroundColor: Color(0xFFFFD54F), backgroundColor: Color(0xFFFFD54F),
color: Colors.black, color: Colors.black,