Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions lib/core/misc/custom_bb_tags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import 'package:flutter_bbcode/flutter_bbcode.dart';
import 'package:injector/injector.dart';
import 'package:unn_mobile/core/constants/api/protocol_type.dart';
import 'package:unn_mobile/core/misc/hex_color.dart';
import 'package:unn_mobile/core/misc/html_utils/html_widget_callbacks.dart';
import 'package:unn_mobile/core/services/interfaces/common/logger_service.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_factory.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_helper.dart';
import 'package:unn_mobile/ui/widgets/spoiler_display.dart';
import 'package:url_launcher/url_launcher.dart';

Expand Down Expand Up @@ -351,6 +354,54 @@ class UserTag extends StyleTag {
oldStyle;
}

class UrlTag extends WrappedStyleTag {
final Future<void> Function(String url)? onTap;
final void Function(String url, BuildContext context)? onLongPress;

UrlTag({
this.onTap,
this.onLongPress,
}) : super('url');

@override
List<InlineSpan> wrap(
FlutterRenderer renderer,
bbob.Element element,
List<InlineSpan> spans,
) {
final String url = element.attributes.keys.firstOrNull ??
(spans.isNotEmpty && spans[0] is TextSpan
? (spans[0] as TextSpan).text ?? ''
: 'URL is missing!');

final textSpan = TextSpan(
text: spans.isEmpty ? url : null,
children: spans,
style: renderer.getCurrentStyle().copyWith(
color: Colors.blue,
decoration: TextDecoration.underline,
),
);

return [
WidgetSpan(
child: Builder(
builder: (ctx) => GestureDetector(
onTap: () => onTap?.call(url),
onLongPress:
onLongPress != null ? () => onLongPress!(url, ctx) : null,
child: RichText(
text: textSpan,
textAlign: TextAlign.left,
softWrap: true,
),
),
),
),
];
}
}

BBStylesheet getBBStyleSheet() => defaultBBStylesheet()
.copyWith(selectableText: true)
.replaceTag(
Expand All @@ -362,6 +413,17 @@ BBStylesheet getBBStyleSheet() => defaultBBStylesheet()
.log('Could not launch url $url');
}
},
onLongPress: (url, ctx) {
ContextMenuHelper.showContextMenu(
context: ctx,
model: url,
actionsBuilder: () => createLinkActions(
context: ctx,
url: url,
onOpen: () => htmlWidgetOnTapUrl(url),
),
);
},
),
)
.addTag(PTag())
Expand Down
4 changes: 2 additions & 2 deletions lib/ui/views/main_page/feed/widgets/feed_comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import 'package:unn_mobile/ui/views/base_view.dart';
import 'package:unn_mobile/ui/views/main_page/feed/functions/reactions_window.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/attached_file.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/packed_post_images.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/post_html_widget.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/reaction_bubble.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/text_html_widget.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_factory.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_helper.dart';
import 'package:unn_mobile/ui/widgets/shimmer.dart';
Expand Down Expand Up @@ -54,7 +54,7 @@ class FeedCommentView extends StatelessWidget {
top: 8,
),
child: model.renderMessage
? PostHtmlWidget(text: model.message)
? TextHtmlWidget(text: model.message)
: const SizedBox(),
),
Padding(
Expand Down
4 changes: 2 additions & 2 deletions lib/ui/views/main_page/feed/widgets/feed_post.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import 'package:unn_mobile/ui/views/base_view.dart';
import 'package:unn_mobile/ui/views/main_page/feed/functions/reactions_window.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/attached_file.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/packed_post_images.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/post_html_widget.dart';
import 'package:unn_mobile/ui/views/main_page/feed/widgets/text_html_widget.dart';
import 'package:unn_mobile/ui/views/main_page/main_page_routing.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_factory.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_helper.dart';
Expand Down Expand Up @@ -333,7 +333,7 @@ class _FeedPostState extends State<FeedPost> {
Widget _buildPostContent(FeedPostViewModel model) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PostHtmlWidget(text: model.postText),
TextHtmlWidget(text: model.postText),
PackedPostImages(attachedImages: model.attachedImages),
],
);
Expand Down
32 changes: 0 additions & 32 deletions lib/ui/views/main_page/feed/widgets/post_html_widget.dart

This file was deleted.

60 changes: 60 additions & 0 deletions lib/ui/views/main_page/feed/widgets/text_html_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 BitCodersNN

import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:unn_mobile/core/misc/html_utils/html_widget_callbacks.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_factory.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_helper.dart';
import 'package:unn_mobile/ui/widgets/dismissable_image.dart';

class TextHtmlWidget extends StatelessWidget {
const TextHtmlWidget({required this.text, super.key});
final String text;

@override
Widget build(BuildContext context) => HtmlWidget(
text,
onTapUrl: htmlWidgetOnTapUrl,
onTapImage: (imageMetadata) async {
await showDialog(
context: context,
builder: (context) => ExtendedImageSlidePage(
slideAxis: SlideAxis.vertical,
child: DismissibleImage(image: imageMetadata.sources.first.url),
),
);
},
customWidgetBuilder: (element) {
if (element.localName == 'a') {
final href = element.attributes['href'];
final text = element.text;
if (href != null) {
return Builder(
builder: (ctx) => GestureDetector(
onTap: () => htmlWidgetOnTapUrl(href),
onLongPress: () => ContextMenuHelper.showContextMenu(
context: ctx,
model: href,
actionsBuilder: () => createLinkActions(
context: ctx,
url: href,
onOpen: () => htmlWidgetOnTapUrl(href),
),
),
child: Text(
text,
style: TextStyle(
color: Theme.of(ctx).colorScheme.primary,
decoration: TextDecoration.underline,
),
),
),
);
}
}
return null;
},
);
}
44 changes: 44 additions & 0 deletions lib/ui/widgets/context_menu/context_menu_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:unn_mobile/core/viewmodels/main_page/feed/feed_comment_view_mode
import 'package:unn_mobile/core/viewmodels/main_page/feed/feed_post_view_model.dart';
import 'package:unn_mobile/ui/views/main_page/chat/widgets/message.dart';
import 'package:unn_mobile/ui/widgets/context_menu/context_menu_action.dart';
import 'package:url_launcher/url_launcher.dart';

List<ContextMenuAction> createMessageActions({
required BuildContext context,
Expand Down Expand Up @@ -46,14 +47,29 @@ List<ContextMenuAction> createCommentActions({
textToCopy: model.message,
);

List<ContextMenuAction> createLinkActions({
required BuildContext context,
required String url,
VoidCallback? onOpen,
VoidCallback? onShare,
}) =>
_createActions(
context: context,
linkToCopy: url,
onOpenLink: onOpen,
onShare: onShare,
);

List<ContextMenuAction> _createActions({
required BuildContext context,
ReactionViewModelBase? reactionViewModel,
String? textToCopy,
String? linkToCopy,
VoidCallback? onReply,
VoidCallback? onTogglePin,
bool? isPinned,
VoidCallback? onShare,
VoidCallback? onOpenLink,
}) {
final actions = <ContextMenuAction>[];

Expand All @@ -76,6 +92,34 @@ List<ContextMenuAction> _createActions({
);
}

if (linkToCopy != null) {
actions
..add(
ContextMenuAction.text(
label: 'Открыть',
onTap: onOpenLink ??
() {
launchUrl(Uri.parse(linkToCopy));
},
leadingIcon: const Icon(Icons.open_in_new, size: 18),
),
)
..add(
ContextMenuAction.text(
label: 'Скопировать ссылку',
onTap: () {
Clipboard.setData(ClipboardData(text: linkToCopy));
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ссылка скопирована')),
);
}
},
leadingIcon: const Icon(Icons.link, size: 18),
),
);
}

if (onReply != null) {
actions.add(
ContextMenuAction.text(
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: unn_mobile
description: A mobile application for UNN Portal website
publish_to: 'none'

version: 0.6.0+367
version: 0.6.0+368

environment:
sdk: '>=3.1.2 <4.0.0'
Expand Down