Skip to content

Commit

Permalink
twitterの埋め込みを追加
Browse files Browse the repository at this point in the history
  • Loading branch information
poppingmoon committed Oct 19, 2023
1 parent dce2020 commit cb2cc17
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 5 deletions.
39 changes: 34 additions & 5 deletions lib/view/common/misskey_notes/link_preview.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:miria/model/account.dart';
import 'package:miria/model/summaly_result.dart';
import 'package:miria/providers.dart';
import 'package:miria/view/common/misskey_notes/player_embed.dart';
import 'package:miria/view/common/misskey_notes/twitter_embed.dart';
import 'package:miria/view/themes/app_theme.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';

Expand Down Expand Up @@ -84,33 +87,59 @@ class LinkPreviewItem extends StatefulWidget {
class _LinkPreviewItemState extends State<LinkPreviewItem> {
bool isPlayerOpen = false;

String? extractTweetId(String link) {
final url = Uri.parse(link);
if (!["twitter.com", "mobile.twitter.com", "x.com", "mobile.x.com"]
.contains(url.host)) {
return null;
}
final index = url.pathSegments.indexWhere(
(segment) => ["status", "statuses"].contains(segment),
);
if (index < 0 || url.pathSegments.length - 1 <= index) {
return null;
}
final tweetId = url.pathSegments[index + 1];
return int.tryParse(tweetId)?.toString();
}

@override
Widget build(BuildContext context) {
final playerUrl = widget.summalyResult.player.url;
final tweetId = extractTweetId(widget.link);
return Column(
children: [
if (!isPlayerOpen)
LinkPreviewTile(
link: widget.link,
summalyResult: widget.summalyResult,
),
if (widget.summalyResult.player.url != null &&
WebViewPlatform.instance != null)
if (WebViewPlatform.instance != null &&
(playerUrl != null || tweetId != null))
if (isPlayerOpen) ...[
PlayerEmbed(player: widget.summalyResult.player),
if (playerUrl != null)
PlayerEmbed(player: widget.summalyResult.player),
if (tweetId != null)
TwitterEmbed(
tweetId: tweetId,
isDark: AppTheme.of(context).isDarkMode,
// TODO: l10n
lang: "ja",
),
OutlinedButton.icon(
onPressed: () => setState(() {
isPlayerOpen = false;
}),
icon: const Icon(Icons.close),
label: const Text("プレイヤーを閉じる"),
label: Text(playerUrl != null ? "プレイヤーを閉じる" : "ツイートを閉じる"),
),
] else
OutlinedButton.icon(
onPressed: () => setState(() {
isPlayerOpen = true;
}),
icon: const Icon(Icons.play_arrow),
label: const Text("プレイヤーを開く"),
label: Text(playerUrl != null ? "プレイヤーを開く" : "ツイートを開く"),
),
],
);
Expand Down
112 changes: 112 additions & 0 deletions lib/view/common/misskey_notes/twitter_embed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';

class TwitterEmbed extends StatefulWidget {
const TwitterEmbed({
super.key,
required this.tweetId,
this.isDark = false,

// https://developer.twitter.com/en/docs/twitter-for-websites/supported-languages
this.lang,
});

final String tweetId;
final bool isDark;
final String? lang;

@override
State<TwitterEmbed> createState() => _TwitterEmbedState();
}

class _TwitterEmbedState extends State<TwitterEmbed> {
WebViewController? controller;
double? height;

@override
void initState() {
super.initState();

controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.transparent)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (request) async {
final url = Uri.tryParse(request.url);
if (url != null && await canLaunchUrl(url)) {
launchUrl(url, mode: LaunchMode.externalApplication);
}
return NavigationDecision.prevent;
},
),
)
..addJavaScriptChannel(
"Twitter",
onMessageReceived: (message) {
setState(() {
// そのままだと下が見切れる
height = double.parse(message.message) + 10;
});
},
)
// https://developer.twitter.com/en/docs/twitter-for-websites/embedded-tweets/guides/embedded-tweet-javascript-factory-function
..loadHtmlString(
"""
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="container"></div>
</body>
<script>
window.twttr = (function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function (f) {
t._e.push(f);
};
return t;
}(document, "script", "twitter-wjs"));
window.twttr.ready(
(twttr) => twttr.widgets.createTweet(
"${widget.tweetId}",
document.getElementById("container"),
{
${widget.isDark ? "theme: 'dark'," : ""}
${widget.lang != null ? "lang: '${widget.lang}'," : ""}
}
).then((el) => Twitter.postMessage(el.clientHeight))
);
</script>
</html>""",
);
}

@override
Widget build(BuildContext context) {
final controller = this.controller;
if (controller == null) {
return const SizedBox.shrink();
}
return SizedBox(
height: height ?? 200,
child: WebViewWidget(controller: controller),
);
}
}

0 comments on commit cb2cc17

Please sign in to comment.