fixed chat bubble link styling and gesture handling, and added android package visibility for common schemes
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user