From 1862b71243b11621cfde23ffdf6d16e56d5f3e37 Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:08:40 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E3=81=AE=E3=83=97?= =?UTF-8?q?=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/misskey_notes/link_preview.dart | 73 +++++++++++++++++++ .../common/misskey_notes/misskey_note.dart | 24 ++++++ pubspec.lock | 36 +++++---- pubspec.yaml | 1 + 4 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 lib/view/common/misskey_notes/link_preview.dart diff --git a/lib/view/common/misskey_notes/link_preview.dart b/lib/view/common/misskey_notes/link_preview.dart new file mode 100644 index 000000000..cae6e5ab2 --- /dev/null +++ b/lib/view/common/misskey_notes/link_preview.dart @@ -0,0 +1,73 @@ +import 'package:any_link_preview/any_link_preview.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class LinkPreview extends StatelessWidget { + const LinkPreview({super.key, required this.link}); + + final String link; + + Widget _buildItem( + BuildContext context, + Metadata metadata, + ImageProvider? image, + ) { + final title = metadata.title?.trim(); + return Padding( + padding: const EdgeInsets.all(5), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).dividerColor, + ), + borderRadius: BorderRadius.circular(5), + ), + child: ListTile( + leading: image == null + ? null + : Image( + image: image, + width: 50, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container(), + ), + title: Text( + (title != null && title.isNotEmpty) ? title : link, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + subtitle: Text( + metadata.desc ?? "", + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: Theme.of(context).textTheme.bodySmall, + ), + isThreeLine: true, + onTap: () async { + final url = Uri.parse(link); + if (await canLaunchUrl(url)) { + launchUrl(url, mode: LaunchMode.externalApplication); + } + }, + onLongPress: () { + Clipboard.setData(ClipboardData(text: link)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("コピーしました")), + ); + }, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return AnyLinkPreview.builder( + link: link, + itemBuilder: _buildItem, + placeholderWidget: _buildItem(context, Metadata(), null), + errorWidget: _buildItem(context, Metadata(), null), + ); + } +} diff --git a/lib/view/common/misskey_notes/misskey_note.dart b/lib/view/common/misskey_notes/misskey_note.dart index bdc0bd3b6..d804b2d93 100644 --- a/lib/view/common/misskey_notes/misskey_note.dart +++ b/lib/view/common/misskey_notes/misskey_note.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:any_link_preview/any_link_preview.dart'; import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:dotted_border/dotted_border.dart'; @@ -16,6 +17,7 @@ import 'package:miria/view/common/account_scope.dart'; import 'package:miria/view/common/avatar_icon.dart'; import 'package:miria/view/common/constants.dart'; import 'package:miria/view/common/error_dialog_handler.dart'; +import 'package:miria/view/common/misskey_notes/link_preview.dart'; import 'package:miria/view/common/misskey_notes/local_only_icon.dart'; import 'package:miria/view/common/misskey_notes/mfm_text.dart'; import 'package:miria/view/common/misskey_notes/misskey_file_view.dart'; @@ -180,6 +182,24 @@ class MisskeyNoteState extends ConsumerState { return (thisNodeCount, newLinesCount); } + List extractLinks(List nodes) { + final links = []; + for (final node in nodes) { + final children = node.children; + if (children != null) { + links.addAll(extractLinks(children)); + } + if (node is parser.MfmURL) { + links.add(node.value); + } else if (node is parser.MfmLink) { + if (!node.silent) { + links.add(node.url); + } + } + } + return links; + } + @override Widget build(BuildContext context) { final latestActualNote = ref.watch(notesProvider(AccountScope.of(context)) @@ -257,6 +277,8 @@ class MisskeyNoteState extends ConsumerState { final isLongVisible = ref.watch(notesProvider(AccountScope.of(context)) .select((value) => value.noteStatuses[widget.note.id]!.isLongVisible)); + final links = extractLinks(displayTextNodes!); + return MediaQuery( data: MediaQuery.of(context).copyWith( textScaleFactor: MediaQuery.of(context).textScaleFactor * @@ -509,6 +531,8 @@ class MisskeyNoteState extends ConsumerState { poll: displayNote.poll!, loginAs: widget.loginAs, ), + if (isLongVisible && widget.recursive < 2) + ...links.map((link) => LinkPreview(link: link)), if (displayNote.renoteId != null && (widget.recursive < 2 && !widget.isForceUnvisibleRenote)) diff --git a/pubspec.lock b/pubspec.lock index 7b9f043ac..2bff55065 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + any_link_preview: + dependency: "direct main" + description: + name: any_link_preview + sha256: be2adb74944140428c46c3688c1b843c884ead6908e13e8cae359fa31f37cf07 + url: "https://pub.dev" + source: hosted + version: "3.0.1" archive: dependency: transitive description: @@ -386,10 +394,10 @@ packages: dependency: transitive description: name: flutter_cache_manager - sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" flutter_highlighting: dependency: "direct main" description: @@ -545,10 +553,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: "6b6f10f0ce3c42f6552d1c70d2c28d764cf22bb487f50f66cca31dcd5194f4d6" + sha256: "2776c66b3e97c6cdd58d1bd3281548b074b64f1fd5c8f82391f7456e38849567" url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.0.5" graphs: dependency: transitive description: @@ -577,10 +585,10 @@ packages: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_multi_server: dependency: transitive description: @@ -914,14 +922,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.6" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" - url: "https://pub.dev" - source: hosted - version: "1.11.1" percent_indicator: dependency: "direct main" description: @@ -1255,6 +1255,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + string_validator: + dependency: transitive + description: + name: string_validator + sha256: b419cf5d21d608522e6e7cafed4deb34b6f268c43df866e63c320bab98a08cf6 + url: "https://pub.dev" + source: hosted + version: "1.0.0" synchronized: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 747ffe471..8b7345e61 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,6 +59,7 @@ dependencies: colorfilter_generator: ^0.0.8 matrix2d: ^1.0.4 twemoji_v2: ^0.5.3 + any_link_preview: ^3.0.1 dependency_overrides: image_editor: