From 90ee92382da9948dbd12b134861b8b08694aaff0 Mon Sep 17 00:00:00 2001 From: kidozh <11661760+kidozh@users.noreply.github.com> Date: Thu, 28 Nov 2024 00:35:06 +0000 Subject: [PATCH] add bilibili support --- lib/JsonResult/BilibiliVideoResult.dart | 68 ++++++ lib/JsonResult/BilibiliVideoResult.g.dart | 88 +++++++ lib/client/BilibiliApiClient.dart | 23 ++ lib/client/BilibiliApiClient.g.dart | 121 ++++++++++ lib/client/CheveretoApiClient.g.dart | 2 +- lib/client/MobileApiClient.g.dart | 2 +- lib/client/PushServiceClient.g.dart | 2 +- lib/client/UtilityServiceApiClient.g.dart | 2 +- lib/utility/NetworkUtils.dart | 47 +--- lib/utility/PostTextUtils.dart | 4 +- lib/widget/BilibiliWidget.dart | 270 ++++++++++++++++++++++ lib/widget/DiscuzHtmlWidget.dart | 18 +- 12 files changed, 602 insertions(+), 45 deletions(-) create mode 100644 lib/JsonResult/BilibiliVideoResult.dart create mode 100644 lib/JsonResult/BilibiliVideoResult.g.dart create mode 100644 lib/client/BilibiliApiClient.dart create mode 100644 lib/client/BilibiliApiClient.g.dart create mode 100644 lib/widget/BilibiliWidget.dart diff --git a/lib/JsonResult/BilibiliVideoResult.dart b/lib/JsonResult/BilibiliVideoResult.dart new file mode 100644 index 0000000..62ded83 --- /dev/null +++ b/lib/JsonResult/BilibiliVideoResult.dart @@ -0,0 +1,68 @@ + + +import 'package:discuz_flutter/converter/SecondToDateTimeConverter.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'BilibiliVideoResult.g.dart'; + +@JsonSerializable() +class BilibiliVideoResult{ + + int code = 0; + String message = ""; + int ttl = 0; + + BilibiliVideoData data = BilibiliVideoData(); + + + + BilibiliVideoResult(); + + factory BilibiliVideoResult.fromJson(Map json) => _$BilibiliVideoResultFromJson(json); +} + +@JsonSerializable() +class BilibiliVideoData{ + @JsonKey(name: "View") + BilibiliVideoViewData viewData = BilibiliVideoViewData(); + + BilibiliVideoData(); + + factory BilibiliVideoData.fromJson(Map json) => _$BilibiliVideoDataFromJson(json); +} + +@JsonSerializable() +class BilibiliVideoViewData{ + + String bvid = ""; + int aid = 0; + int videos = 0; + int tid = 0; + String tname = ""; + int copyright = 1; + String pic = ""; + String title = ""; + int pubdate = 0, ctime = 0; + String desc = ""; + int state = 0; + int duration = 0, mission_id=0; + + BilibiliVideoViewDataOwner owner = BilibiliVideoViewDataOwner(); + + + BilibiliVideoViewData(); + + factory BilibiliVideoViewData.fromJson(Map json) => _$BilibiliVideoViewDataFromJson(json); +} + +@JsonSerializable() +class BilibiliVideoViewDataOwner{ + + int mid = 0; + String name = "", face=""; + + BilibiliVideoViewDataOwner(); + + factory BilibiliVideoViewDataOwner.fromJson(Map json) => _$BilibiliVideoViewDataOwnerFromJson(json); + +} \ No newline at end of file diff --git a/lib/JsonResult/BilibiliVideoResult.g.dart b/lib/JsonResult/BilibiliVideoResult.g.dart new file mode 100644 index 0000000..9f292c6 --- /dev/null +++ b/lib/JsonResult/BilibiliVideoResult.g.dart @@ -0,0 +1,88 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'BilibiliVideoResult.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +BilibiliVideoResult _$BilibiliVideoResultFromJson(Map json) => + BilibiliVideoResult() + ..code = (json['code'] as num).toInt() + ..message = json['message'] as String + ..ttl = (json['ttl'] as num).toInt() + ..data = BilibiliVideoData.fromJson(json['data'] as Map); + +Map _$BilibiliVideoResultToJson( + BilibiliVideoResult instance) => + { + 'code': instance.code, + 'message': instance.message, + 'ttl': instance.ttl, + 'data': instance.data, + }; + +BilibiliVideoData _$BilibiliVideoDataFromJson(Map json) => + BilibiliVideoData() + ..viewData = + BilibiliVideoViewData.fromJson(json['View'] as Map); + +Map _$BilibiliVideoDataToJson(BilibiliVideoData instance) => + { + 'View': instance.viewData, + }; + +BilibiliVideoViewData _$BilibiliVideoViewDataFromJson( + Map json) => + BilibiliVideoViewData() + ..bvid = json['bvid'] as String + ..aid = (json['aid'] as num).toInt() + ..videos = (json['videos'] as num).toInt() + ..tid = (json['tid'] as num).toInt() + ..tname = json['tname'] as String + ..copyright = (json['copyright'] as num).toInt() + ..pic = json['pic'] as String + ..title = json['title'] as String + ..pubdate = (json['pubdate'] as num).toInt() + ..ctime = (json['ctime'] as num).toInt() + ..desc = json['desc'] as String + ..state = (json['state'] as num).toInt() + ..duration = (json['duration'] as num).toInt() + ..mission_id = (json['mission_id'] as num).toInt() + ..owner = BilibiliVideoViewDataOwner.fromJson( + json['owner'] as Map); + +Map _$BilibiliVideoViewDataToJson( + BilibiliVideoViewData instance) => + { + 'bvid': instance.bvid, + 'aid': instance.aid, + 'videos': instance.videos, + 'tid': instance.tid, + 'tname': instance.tname, + 'copyright': instance.copyright, + 'pic': instance.pic, + 'title': instance.title, + 'pubdate': instance.pubdate, + 'ctime': instance.ctime, + 'desc': instance.desc, + 'state': instance.state, + 'duration': instance.duration, + 'mission_id': instance.mission_id, + 'owner': instance.owner, + }; + +BilibiliVideoViewDataOwner _$BilibiliVideoViewDataOwnerFromJson( + Map json) => + BilibiliVideoViewDataOwner() + ..mid = (json['mid'] as num).toInt() + ..name = json['name'] as String + ..face = json['face'] as String; + +Map _$BilibiliVideoViewDataOwnerToJson( + BilibiliVideoViewDataOwner instance) => + { + 'mid': instance.mid, + 'name': instance.name, + 'face': instance.face, + }; diff --git a/lib/client/BilibiliApiClient.dart b/lib/client/BilibiliApiClient.dart new file mode 100644 index 0000000..d76fc75 --- /dev/null +++ b/lib/client/BilibiliApiClient.dart @@ -0,0 +1,23 @@ + + +import 'package:dio/dio.dart'; +import 'package:discuz_flutter/JsonResult/BilibiliVideoResult.dart'; +import 'package:retrofit/error_logger.dart'; +import 'package:retrofit/http.dart'; + +part 'BilibiliApiClient.g.dart'; + +@RestApi(baseUrl: "https://api.bilibili.com") +abstract class BilibiliApiClient { + factory BilibiliApiClient(Dio dio, {required String baseUrl}) = _BilibiliApiClient; + + @GET("/x/web-interface/view/detail") + Future getVideoResultByAid( + @Query("aid") String aid, + ); + + @GET("/x/web-interface/view/detail") + Future getVideoResultByBvid( + @Query("bvid") String bvid, + ); +} \ No newline at end of file diff --git a/lib/client/BilibiliApiClient.g.dart b/lib/client/BilibiliApiClient.g.dart new file mode 100644 index 0000000..a487a75 --- /dev/null +++ b/lib/client/BilibiliApiClient.g.dart @@ -0,0 +1,121 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'BilibiliApiClient.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations + +class _BilibiliApiClient implements BilibiliApiClient { + _BilibiliApiClient( + this._dio, { + this.baseUrl, + this.errorLogger, + }) { + baseUrl ??= 'https://api.bilibili.com'; + } + + final Dio _dio; + + String? baseUrl; + + final ParseErrorLogger? errorLogger; + + @override + Future getVideoResultByAid(String aid) async { + final _extra = {}; + final queryParameters = {r'aid': aid}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/x/web-interface/view/detail', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late BilibiliVideoResult _value; + try { + _value = BilibiliVideoResult.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + @override + Future getVideoResultByBvid(String bvid) async { + final _extra = {}; + final queryParameters = {r'bvid': bvid}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/x/web-interface/view/detail', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late BilibiliVideoResult _value; + try { + _value = BilibiliVideoResult.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls( + String dioBaseUrl, + String? baseUrl, + ) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/client/CheveretoApiClient.g.dart b/lib/client/CheveretoApiClient.g.dart index 6337096..ae9f5b6 100644 --- a/lib/client/CheveretoApiClient.g.dart +++ b/lib/client/CheveretoApiClient.g.dart @@ -6,7 +6,7 @@ part of 'CheveretoApiClient.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _CheveretoApiClient implements CheveretoApiClient { _CheveretoApiClient( diff --git a/lib/client/MobileApiClient.g.dart b/lib/client/MobileApiClient.g.dart index 7457dd9..e7c0cd4 100644 --- a/lib/client/MobileApiClient.g.dart +++ b/lib/client/MobileApiClient.g.dart @@ -6,7 +6,7 @@ part of 'MobileApiClient.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _MobileApiClient implements MobileApiClient { _MobileApiClient( diff --git a/lib/client/PushServiceClient.g.dart b/lib/client/PushServiceClient.g.dart index 10b3098..e12da54 100644 --- a/lib/client/PushServiceClient.g.dart +++ b/lib/client/PushServiceClient.g.dart @@ -6,7 +6,7 @@ part of 'PushServiceClient.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _PushServiceClient implements PushServiceClient { _PushServiceClient( diff --git a/lib/client/UtilityServiceApiClient.g.dart b/lib/client/UtilityServiceApiClient.g.dart index c2a4ecf..4978c7b 100644 --- a/lib/client/UtilityServiceApiClient.g.dart +++ b/lib/client/UtilityServiceApiClient.g.dart @@ -6,7 +6,7 @@ part of 'UtilityServiceApiClient.dart'; // RetrofitGenerator // ************************************************************************** -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _UtilityServiceApiClient implements UtilityServiceApiClient { _UtilityServiceApiClient( diff --git a/lib/utility/NetworkUtils.dart b/lib/utility/NetworkUtils.dart index ee989e6..58cb6e5 100644 --- a/lib/utility/NetworkUtils.dart +++ b/lib/utility/NetworkUtils.dart @@ -36,18 +36,13 @@ class NetworkUtils{ return cookieJar; } + static Dio getDio(){ + return Dio(); + } + static Future getDioWithTempCookieJar() async{ - String userAgent = ua; - final header = await userAgentClientHintsHeader(); - var dio = Dio( - BaseOptions( - headers: { - 'User-Agent': userAgent, - //'Accept-Language': 'en-US,en;q=0.9' - } - ) - ); - print("Running on UA ${ua} ${header}"); + + var dio = getDio(); PersistCookieJar cookieJar = await getTemporaryCookieJar(); dio.interceptors.add(CookieManager(cookieJar)); @@ -63,36 +58,14 @@ class NetworkUtils{ static Future getDioWithPersistCookieJar(User? user) async{ if(user == null){ String userAgent = ua; - var dio = Dio( - BaseOptions( - headers: { - 'User-Agent': userAgent - } - ) - ); - // dio.interceptors.add(InterceptorsWrapper( - // onRequest: (options, handler) async{ - // options.headers.addAll(await userAgentClientHintsHeader()); - // //return options; - // }, - // )); + var dio = getDio(); + + return dio; } else{ String userAgent = ua; - var dio = Dio( - BaseOptions( - headers: { - 'User-Agent': userAgent - } - ) - ); - // dio.interceptors.add(InterceptorsWrapper( - // onRequest: (options, handler) async{ - // options.headers.addAll(await userAgentClientHintsHeader()); - // //return options; - // }, - // )); + var dio = getDio(); PersistCookieJar cookieJar = await getPersistentCookieJarByUser(user); dio.interceptors.add(CookieManager(cookieJar)); return dio; diff --git a/lib/utility/PostTextUtils.dart b/lib/utility/PostTextUtils.dart index d219556..4497879 100644 --- a/lib/utility/PostTextUtils.dart +++ b/lib/utility/PostTextUtils.dart @@ -50,10 +50,10 @@ class PostTextUtils{ return ""; }); - string = string.replaceAllMapped(RegExp(r"\[media\](.*?)\[/media\]"), (match) { + string = string.replaceAllMapped(RegExp(r"\[media.*?\](.*?)\[/media\]"), (match) { if(match.groupCount == 1){ - return ''; + return '${match.group(1)}'; } return ""; }); diff --git a/lib/widget/BilibiliWidget.dart b/lib/widget/BilibiliWidget.dart new file mode 100644 index 0000000..07b5fc7 --- /dev/null +++ b/lib/widget/BilibiliWidget.dart @@ -0,0 +1,270 @@ +import 'dart:developer'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dio/dio.dart'; +import 'package:discuz_flutter/JsonResult/BilibiliVideoResult.dart'; +import 'package:discuz_flutter/client/BilibiliApiClient.dart'; +import 'package:discuz_flutter/utility/NetworkUtils.dart'; +import 'package:discuz_flutter/utility/URLUtils.dart'; +import 'package:discuz_flutter/utility/VibrationUtils.dart'; +import 'package:discuz_flutter/widget/UserAvatar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; + +enum BilibiliWidgetType { video, live } + +enum BilibiliVideoRequestType { aid, bvid } + +class BilibiliWidget extends StatefulWidget { + String url = ""; + BilibiliWidget(this.url); + + @override + State createState() { + return BilibiliState(url); + } +} + +class BilibiliState extends State { + BilibiliApiClient client = BilibiliApiClient(NetworkUtils.getDio(), + baseUrl: "https://api.bilibili.com"); + + String url = ""; + BilibiliState(this.url); + + BilibiliWidgetType type = BilibiliWidgetType.video; + BilibiliVideoRequestType videoRequestType = BilibiliVideoRequestType.bvid; + String videoRequestParameter = ""; + BilibiliVideoResult videoResult = BilibiliVideoResult(); + + bool isLoadingApi = false; + + Uri? uri = null; + + @override + void initState() { + super.initState(); + parseUrl(); + } + + void parseUrl() { + Uri? bilibiliUri = Uri.tryParse(url); + if (bilibiliUri != null) { + setState(() { + uri = bilibiliUri; + }); + log("Get Bilibili URL ${url}, host: ${bilibiliUri.host}, path: ${bilibiliUri.path}, split: ${bilibiliUri.path.split("/")}"); + + // not the live + if (bilibiliUri.host == "live.bilibili.com") { + type = BilibiliWidgetType.live; + } else if (bilibiliUri.path.startsWith("/video")) { + type = BilibiliWidgetType.video; + // judge whether it's bvid or avid + List urlPathList = bilibiliUri.path.split("/"); + // remove unneccessary slash + List urlPathFilteredList = urlPathList + .where((i) => i != "/" && i.isNotEmpty && i != "video") + .toList(); + String videoParameterAtLast = urlPathFilteredList.last; + + if (int.tryParse(videoParameterAtLast) != null) { + videoRequestType = BilibiliVideoRequestType.aid; + } + videoRequestParameter = videoParameterAtLast; + // start fetch it? + log("load bilibili information ${url} with ${videoRequestParameter} from list : ${urlPathFilteredList}"); + loadBilibiliVideoApi(); + } + } + } + + Future loadBilibiliVideoApi() async { + setState(() { + isLoadingApi = true; + }); + switch (videoRequestType) { + case BilibiliVideoRequestType.aid: + { + client + .getVideoResultByAid(videoRequestParameter) + .then((result) => renderVideoResult(result)); + break; + } + case BilibiliVideoRequestType.bvid: + { + client + .getVideoResultByBvid(videoRequestParameter) + .then((result) => renderVideoResult(result)); + break; + } + } + } + + Future renderVideoResult(BilibiliVideoResult result) async { + log("Recv bilibili result ${result}"); + setState(() { + isLoadingApi = false; + videoResult = result; + }); + } + + @override + Widget build(BuildContext context) { + if (uri == null) { + return Text("Not a valid Bilibili link ${url}"); + } else { + if (videoResult.data.viewData.pic.isNotEmpty) { + return bilibiliVideoPreviewWidget; + } + } + return bilibiliDefaultWidget; + } + + static const int bilibiliColorPink = 0xFFFB7299; + static const int bilibiliColorGray = 0xFFF4F4F4; + + Widget get bilibiliDefaultWidget => InkWell( + onTap: () { + VibrationUtils.vibrateWithClickIfPossible(); + URLUtils.openURL(context, null, url, null, null); + }, + child: Card( + color: Color(bilibiliColorPink), + child: Container( + padding: EdgeInsets.all(4.0), + child: PlatformListTile( + leading: Icon( + PlatformIcons(context).playCircleSolid, + color: Color(bilibiliColorGray), + ), + title: Text( + url, + maxLines: 1, + style: TextStyle(color: Color(bilibiliColorGray)), + )), + ), + ), + ); + + Widget get bilibiliVideoPreviewWidget => InkWell( + onTap: () { + VibrationUtils.vibrateWithClickIfPossible(); + URLUtils.openURL(context, null, url, null, null); + }, + child: Card( + elevation: isCupertino(context) ? 1 : 4, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: Row( + children: [ + Expanded( + flex: 4, + child: CachedNetworkImage( + imageUrl: videoResult.data.viewData.pic), + ), + Expanded( + flex: 8, + child: Padding( + padding: + EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + maxLines: 2, + overflow: TextOverflow.ellipsis, + text: TextSpan(children: [ + WidgetSpan( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Theme.of(context) + .colorScheme + .primary), + padding: EdgeInsets.symmetric( + horizontal: 8.0, vertical: 2.0), + margin: EdgeInsets.only(right: 8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(PlatformIcons(context).playCircleSolid, + size: 10, + color: Theme.of(context) + .colorScheme + .onPrimary), + SizedBox( + width: 4, + ), + Text( + videoResult.data.viewData.tname, + style: TextStyle( + fontSize: 12, + color: Theme.of(context) + .colorScheme + .onPrimary), + ), + ], + )), + ), + TextSpan( + text: videoResult.data.viewData.title, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + fontSize: 18)), + ]), + ), + SizedBox( + width: 16, + ), + RichText( + maxLines: 1, + overflow: TextOverflow.ellipsis, + text: TextSpan(children: [ + WidgetSpan( + child: Container( + padding: EdgeInsets.zero, + margin: EdgeInsets.only(top: 8,right: 8), + child: SizedBox( + width: 18, + height: 18, + child: CircleAvatar( + backgroundImage: + CachedNetworkImageProvider( + videoResult.data.viewData.owner.face, + maxWidth: 36, + maxHeight: 36, + )), + ), + ), + ), + TextSpan( + text: videoResult.data.viewData.owner.name, + style: TextStyle( + fontSize: 16, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + )), + ]), + ), + ], + ), + )) + ], + ), + ), + ), + ); +} diff --git a/lib/widget/DiscuzHtmlWidget.dart b/lib/widget/DiscuzHtmlWidget.dart index f3eea5c..2164a71 100644 --- a/lib/widget/DiscuzHtmlWidget.dart +++ b/lib/widget/DiscuzHtmlWidget.dart @@ -10,6 +10,7 @@ import 'package:discuz_flutter/provider/TypeSettingNotifierProvider.dart'; import 'package:discuz_flutter/utility/AppPlatformIcons.dart'; import 'package:discuz_flutter/utility/PostTextUtils.dart'; import 'package:discuz_flutter/utility/VibrationUtils.dart'; +import 'package:discuz_flutter/widget/BilibiliWidget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; @@ -113,11 +114,22 @@ class DiscuzHtmlWidget extends StatelessWidget { }, textStyle: TextStyle( fontSize: themeFontSize * scalingParameter, - fontWeight: useThinFont? FontWeight.w300: FontWeight.normal - ), + fontWeight: useThinFont? FontWeight.w300: FontWeight.normal, + wordSpacing: defaultTextStyle?.wordSpacing, + height: defaultTextStyle?.height, + textBaseline: defaultTextStyle?.textBaseline, + ).useSystemChineseFont(), // textStyle: Theme.of(context).useSystemChineseFont(Theme.of(context).brightness).textTheme.bodyLarge?..copyWith( // fontSize: 12 * scalingParameter // ), + customStylesBuilder: (element){ + if (element.localName == "br"){ + return { + "margin": '0.1em 0', + "display" : "block" + }; + } + }, customWidgetBuilder: (element) { // "collapse", "spoil" if (element.localName == "collapse" || @@ -191,6 +203,8 @@ class DiscuzHtmlWidget extends StatelessWidget { return Text(S.of(context).brokenCountDown); } + } else if (element.attributes["href"]!= null && element.attributes["href"]!.startsWith("https://www.bilibili.com")){ + return BilibiliWidget(element.attributes["href"]!); } return null; },