import 'dart:math'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart' show SourceModel; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/models/dynamics/article_content_model.dart' show ArticleContentModel, Style, Word; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:re_highlight/languages/all.dart'; import 'package:re_highlight/re_highlight.dart'; import 'package:re_highlight/styles/all.dart'; class OpusContent extends StatelessWidget { final List opus; final void Function(List, int)? callback; final double maxWidth; const OpusContent({ super.key, required this.opus, this.callback, required this.maxWidth, }); static TextStyle _getStyle(Style? style, [Color? color, double? fontSize]) => TextStyle( decoration: style?.strikethrough == true ? TextDecoration.lineThrough : null, fontStyle: style?.italic == true ? FontStyle.italic : null, fontWeight: style?.bold == true ? FontWeight.bold : null, color: color, fontSize: fontSize, ); static TextSpan _getSpan(Word? word) => TextSpan( text: word?.words, style: _getStyle( word?.style, word?.color != null ? Color(word!.color!) : null, word?.fontSize, )); @override Widget build(BuildContext context) { debugPrint('opusContent'); if (opus.isEmpty) { return const SliverToBoxAdapter(); } final colorScheme = Theme.of(context).colorScheme; return SliverList.separated( itemCount: opus.length, itemBuilder: (context, index) { final element = opus[index]; try { switch (element.paraType) { case 1: return SelectableText.rich( textAlign: element.align == 1 ? TextAlign.center : null, TextSpan( children: element.text?.nodes?.map((item) { if (item.rich != null) { return TextSpan( text: '\u{1F517}${item.rich?.text}', style: _getStyle(item.rich?.style, colorScheme.primary), recognizer: item.rich?.jumpUrl == null ? null : (TapGestureRecognizer() ..onTap = () { PiliScheme.routePushFromUrl(item.rich!.jumpUrl!); }), ); } return _getSpan(item.word); }).toList()), ); case 4: return Container( padding: const EdgeInsets.only(left: 8), decoration: const BoxDecoration( border: Border( left: BorderSide(color: Color(0xFFE0E0E0), width: 4), ), ), child: SelectableText.rich( textAlign: element.align == 1 ? TextAlign.center : null, TextSpan( children: element.text?.nodes?.map((item) { if (item.rich != null) { return TextSpan( text: '\u{1F517}${item.rich?.text}', style: _getStyle(item.rich?.style, colorScheme.primary), recognizer: item.rich?.jumpUrl == null ? null : (TapGestureRecognizer() ..onTap = () { PiliScheme.routePushFromUrl( item.rich!.jumpUrl!); }), ); } return TextSpan( text: item.word?.words, style: _getStyle( item.word?.style, item.word?.color != null ? Color(item.word!.color!).withOpacity(0.7) : colorScheme.onSurface.withOpacity(0.7), item.word?.fontSize, )); }).toList()), ), ); case 2 when (element.pic != null): element.pic!.pics!.first.onCalHeight(maxWidth); return Hero( tag: element.pic!.pics!.first.url!, child: GestureDetector( onTap: () { if (callback != null) { callback!([element.pic!.pics!.first.url!], 0); } else { context.imageView( initialPage: 0, imgList: [ SourceModel(url: element.pic!.pics!.first.url!) ], ); } }, child: NetworkImgLayer( width: maxWidth, height: element.pic!.pics!.first.calHeight, src: element.pic!.pics!.first.url!, quality: 60, ), ), ); case 3 when (element.line != null): return CachedNetworkImage( width: maxWidth, fit: BoxFit.contain, height: element.line!.pic!.height?.toDouble(), imageUrl: Utils.thumbnailImgUrl(element.line!.pic!.url!), ); case 5 when (element.list != null): return SelectableText.rich( TextSpan( children: element.list!.items?.indexed.map((entry) { return TextSpan( children: [ WidgetSpan( child: Icon(MdiIcons.circleMedium), alignment: PlaceholderAlignment.middle, ), ...entry.$2.nodes!.map((item) { return _getSpan(item.word); }), if (entry.$1 < element.list!.items!.length - 1) const TextSpan(text: '\n'), ], ); }).toList(), ), ); case 6: return Material( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8)), ), color: colorScheme.onInverseSurface, child: InkWell( onTap: () { try { PiliScheme.videoPush( int.parse(element.linkCard!.card!.oid!), null, ); } catch (_) {} }, borderRadius: const BorderRadius.all(Radius.circular(8)), child: Padding( padding: const EdgeInsets.all(8), child: switch (element.linkCard?.card?.type) { 'LINK_CARD_TYPE_UGC' => Row( children: [ NetworkImgLayer( radius: 6, width: 65 * StyleString.aspectRatio, height: 65, src: element.linkCard!.card!.ugc!.cover, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(element.linkCard!.card!.ugc!.title!), Text( element.linkCard!.card!.ugc!.descSecond!, style: TextStyle( fontSize: 13, color: colorScheme.outline, ), ), ], ), ), ], ), 'LINK_CARD_TYPE_ITEM_NULL' => Row( children: [ if (element.linkCard?.card?.itemNull?.icon ?.isNullOrEmpty == true) Icon(Icons.info, size: 20), Text(' ${element.linkCard?.card?.itemNull?.text}'), ], ), _ => throw UnimplementedError( '\nparaType: ${element.paraType},\ncard type: ${element.linkCard?.card?.type}', ), }, ), ), ); case 7 when (element.code != null): final Highlight highlight = Highlight() ..registerLanguages(builtinAllLanguages); final HighlightResult result = highlight.highlightAuto( element.code!.content!, element.code!.lang == 'language-clike' ? const ['c', 'java'] : [ element.code!.lang! .replaceAll('language-', '') .replaceAll('like', ''), ]); final TextSpanRenderer renderer = TextSpanRenderer( const TextStyle(), builtinAllThemes['github']!); result.render(renderer); return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(8)), color: colorScheme.onInverseSurface, ), width: double.infinity, child: SelectableText.rich(renderer.span!), ); default: debugPrint('unknown type ${element.paraType}'); if (element.text?.nodes?.isNotEmpty == true) { return SelectableText.rich( textAlign: element.align == 1 ? TextAlign.center : null, TextSpan( children: element.text!.nodes! .map((item) => _getSpan(item.word)) .toList()), ); } return SelectableText('不支持的类型 (${element.paraType})', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.red, )); } } catch (e) { return SelectableText('错误的类型 $e', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.red, )); } }, separatorBuilder: (context, index) => const SizedBox(height: 10), ); } } Widget moduleBlockedItem( ThemeData theme, ModuleBlocked moduleBlocked, double maxWidth) { if (moduleBlocked.blockedType == 1) { maxWidth = min(400, maxWidth * 0.8); return Stack( clipBehavior: Clip.none, children: [ if (moduleBlocked.bgImg != null) CachedNetworkImage( width: maxWidth, fit: BoxFit.cover, imageUrl: Utils.thumbnailImgUrl( Get.isDarkMode ? moduleBlocked.bgImg!.imgDark : moduleBlocked.bgImg!.imgDay, ), ), Container( width: maxWidth, height: maxWidth, padding: const EdgeInsets.all(12), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (moduleBlocked.icon != null) CachedNetworkImage( width: maxWidth / 7, fit: BoxFit.contain, imageUrl: Utils.thumbnailImgUrl( Get.isDarkMode ? moduleBlocked.icon!.imgDark : moduleBlocked.icon!.imgDay, ), ), if (moduleBlocked.hintMessage != null) ...[ const SizedBox(height: 5), Text( moduleBlocked.hintMessage!, textAlign: TextAlign.center, style: TextStyle( color: theme.colorScheme.outline, ), ), ], if (moduleBlocked.button != null) ...[ const SizedBox(height: 5), FilledButton.tonal( style: FilledButton.styleFrom( visualDensity: const VisualDensity(vertical: -2.5), backgroundColor: Get.isDarkMode ? const Color(0xFF8F0030) : const Color(0xFFFF6699), foregroundColor: Colors.white, ), onPressed: () { if (moduleBlocked.button!.jumpUrl != null) { PiliScheme.routePushFromUrl( moduleBlocked.button!.jumpUrl!); } }, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (moduleBlocked.button!.icon != null) CachedNetworkImage( height: 16, color: Colors.white, imageUrl: moduleBlocked.button!.icon!, ), Text(moduleBlocked.button!.text ?? ''), ], ), ), ], ], ), ), ], ); } return Stack( clipBehavior: Clip.none, alignment: Alignment.center, children: [ if (moduleBlocked.bgImg != null) CachedNetworkImage( width: maxWidth, fit: BoxFit.cover, imageUrl: Utils.thumbnailImgUrl( Get.isDarkMode ? moduleBlocked.bgImg!.imgDark : moduleBlocked.bgImg!.imgDay, ), ), Padding( padding: const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (moduleBlocked.icon != null) ...[ CachedNetworkImage( width: 42, fit: BoxFit.contain, imageUrl: Utils.thumbnailImgUrl( Get.isDarkMode ? moduleBlocked.icon!.imgDark : moduleBlocked.icon!.imgDay, ), ), const SizedBox(width: 8), ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (moduleBlocked.title != null) Text( moduleBlocked.title!, ), if (moduleBlocked.hintMessage != null) ...[ const SizedBox(height: 2), Text( moduleBlocked.hintMessage!, style: TextStyle( fontSize: 13, color: theme.colorScheme.outline, ), ), ], ], ), ), if (moduleBlocked.button != null) ...[ const SizedBox(width: 8), FilledButton.tonal( style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 10), tapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: const VisualDensity(vertical: -3, horizontal: -4), backgroundColor: Get.isDarkMode ? const Color(0xFF8F0030) : const Color(0xFFFF6699), foregroundColor: Colors.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(6))), ), onPressed: () { if (moduleBlocked.button!.jumpUrl != null) { PiliScheme.routePushFromUrl(moduleBlocked.button!.jumpUrl!); } }, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (moduleBlocked.button!.icon != null) CachedNetworkImage( height: 16, color: Colors.white, imageUrl: moduleBlocked.button!.icon!, ), Text(moduleBlocked.button!.text ?? ''), ], ), ), ], ], ), ), ], ); }