Skip to content

Commit

Permalink
feat(html): Build netease_card
Browse files Browse the repository at this point in the history
  • Loading branch information
realth000 committed Sep 24, 2024
1 parent abe4563 commit 247cdf3
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- 网页:解析网页时,若处在深色模式下,将难以看清的浅色转换为深色。
- 网页:现在在渲染网页时,不会变更文字换行位置。
- 看起来可能和网页端不同,但这是为了后续能够复制文本内容。
- 网页:支持解析网易云音乐的外链播放器(仅显示歌曲信息并提供点击跳转功能)。
- 认证:支持使用UID和邮箱登录。
- 认证:登录失败时自动刷新验证码。
- 帖子:显示楼层用户的昵称和分组,回复时间调整为精确格式。
Expand Down
23 changes: 2 additions & 21 deletions lib/utils/html/html_muncher.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:tsdm_client/constants/layout.dart';
import 'package:tsdm_client/extensions/build_context.dart';
import 'package:tsdm_client/extensions/universal_html.dart';
import 'package:tsdm_client/shared/models/models.dart';
import 'package:tsdm_client/utils/html/adaptive_color.dart';
import 'package:tsdm_client/utils/html/css_parser.dart';
import 'package:tsdm_client/utils/html/netease_card.dart';
import 'package:tsdm_client/utils/html/types.dart';
import 'package:tsdm_client/utils/logger.dart';
import 'package:tsdm_client/utils/show_bottom_sheet.dart';
Expand All @@ -19,7 +19,6 @@ import 'package:tsdm_client/widgets/card/spoiler_card.dart';
import 'package:tsdm_client/widgets/network_indicator_image.dart';
import 'package:tsdm_client/widgets/quoted_text.dart';
import 'package:universal_html/html.dart' as uh;
import 'package:url_launcher/url_launcher_string.dart';

/// Use the same span to append line break.
const emptySpan = TextSpan(text: '\n');
Expand Down Expand Up @@ -1005,25 +1004,7 @@ final class _Muncher with LoggerMixin {
?.namedGroup('id');
if (neteasePlayerId != null) {
// Recognized netease player iframe.
// But only a song id here, nothing with song title/artist or other info.
// TODO: Check and get song info.
final url = 'https://music.163.com/#/song?id=$neteasePlayerId';
return [
WidgetSpan(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () async => launchUrlString(url),
child: Card(
child: Padding(
padding: edgeInsetsL12T12R12B12,
child: Text('netease music here (id=$neteasePlayerId)'),
),
),
),
),
),
];
return [WidgetSpan(child: NeteaseCard(neteasePlayerId))];
}
return null;
}
Expand Down
97 changes: 97 additions & 0 deletions lib/utils/html/netease_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:fpdart/fpdart.dart' as fp;
import 'package:tsdm_client/constants/layout.dart';
import 'package:tsdm_client/instance.dart';
import 'package:tsdm_client/shared/providers/net_client_provider/net_client_provider.dart';
import 'package:tsdm_client/shared/providers/providers.dart';
import 'package:tsdm_client/utils/logger.dart';
import 'package:universal_html/parsing.dart';
import 'package:url_launcher/url_launcher_string.dart';

/// Card to show a parsed netease iframe player.
class NeteaseCard extends StatefulWidget {
/// Constructor.
const NeteaseCard(this.id, {super.key});

/// Song id.
final String id;

@override
State<NeteaseCard> createState() => _NeteaseCardState();
}

class _NeteaseCardState extends State<NeteaseCard> with LoggerMixin {
static const urlPrefix = 'https://music.163.com/song?id=';

late String fallbackInfo;

String _parseMusicInfo(String rawDocument) {
final doc = parseHtmlDocument(rawDocument);
final title = doc.querySelector('em.f-ff2')?.innerText;
final artist = doc.querySelector('p.des.s-fc4 > span')?.title;

if (title == null || artist == null) {
error('failed to parse netease music info (id ${widget.id}), '
'title=$title, artist=$artist');
return fallbackInfo;
}

return '$artist - $title';
}

@override
void initState() {
super.initState();
fallbackInfo = 'id: ${widget.id}';
}

@override
Widget build(BuildContext context) {
final infoStyle = Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.secondary,
);
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () async => launchUrlString('$urlPrefix${widget.id}'),
child: Card(
child: Padding(
padding: edgeInsetsL12T12R12B12,
child: Row(
children: [
Icon(
Icons.music_note_outlined,
color: Theme.of(context).colorScheme.secondary,
),
sizedBoxW8H8,
Expanded(
child: FutureBuilder(
future: getIt
.get<NetClientProvider>(
instanceName: ServiceKeys.noCookie,
)
.get('$urlPrefix${widget.id}')
.run(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return switch (snapshot.data!) {
fp.Left() => Text(fallbackInfo, style: infoStyle),
fp.Right(:final value) => Text(
_parseMusicInfo(value.data as String),
style: infoStyle,
),
};
}

return sizedCircularProgressIndicator;
},
),
),
],
),
),
),
),
);
}
}

0 comments on commit 247cdf3

Please sign in to comment.