mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-03 00:29:42 +08:00
opt ui
opt req Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -16,7 +16,6 @@ import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:PiliPlus/utils/url_utils.dart';
|
||||
import 'package:flutter/rendering.dart' show ScrollDirection;
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
@@ -227,22 +226,6 @@ class ArticleController extends CommonDynController<MainListReply> {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void listener() {
|
||||
showTitle.value = scrollController.positions.last.pixels >= 45;
|
||||
final ScrollDirection direction1 =
|
||||
scrollController.positions.first.userScrollDirection;
|
||||
late final ScrollDirection direction2 =
|
||||
scrollController.positions.last.userScrollDirection;
|
||||
if (direction1 == ScrollDirection.forward ||
|
||||
direction2 == ScrollDirection.forward) {
|
||||
showFab();
|
||||
} else if (direction1 == ScrollDirection.reverse ||
|
||||
direction2 == ScrollDirection.reverse) {
|
||||
hideFab();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Summary {
|
||||
|
||||
@@ -81,112 +81,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
double padding = max(
|
||||
context.width / 2 - Grid.smallCardWidth,
|
||||
0,
|
||||
);
|
||||
if (isPortrait) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxWidth =
|
||||
constraints.maxWidth - 2 * padding - 24;
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
_buildContent(theme, maxWidth),
|
||||
SliverToBoxAdapter(
|
||||
child: Divider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(
|
||||
alpha: 0.05,
|
||||
),
|
||||
),
|
||||
),
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: controller.ratio[0].toInt(),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxWidth =
|
||||
constraints.maxWidth - padding / 4 - 24;
|
||||
return CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: padding / 4,
|
||||
bottom:
|
||||
MediaQuery.paddingOf(context).bottom +
|
||||
80,
|
||||
),
|
||||
sliver: _buildContent(theme, maxWidth),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
VerticalDivider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(alpha: 0.05),
|
||||
),
|
||||
Expanded(
|
||||
flex: controller.ratio[1].toInt(),
|
||||
child: Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: refreshIndicator(
|
||||
onRefresh: controller.onRefresh,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: padding / 4),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics:
|
||||
const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildPage(theme, isPortrait),
|
||||
_buildBottom(theme),
|
||||
],
|
||||
),
|
||||
@@ -194,6 +89,103 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPage(ThemeData theme, bool isPortrait) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
double padding = max(
|
||||
context.width / 2 - Grid.smallCardWidth,
|
||||
0,
|
||||
);
|
||||
|
||||
if (isPortrait) {
|
||||
final maxWidth = constraints.maxWidth - 2 * padding - 24;
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
_buildContent(theme, maxWidth),
|
||||
SliverToBoxAdapter(
|
||||
child: Divider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(
|
||||
alpha: 0.05,
|
||||
),
|
||||
),
|
||||
),
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
padding = padding / 4;
|
||||
final flex = controller.ratio[0].toInt();
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: flex,
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: padding,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: _buildContent(
|
||||
theme,
|
||||
constraints.maxWidth * flex - padding - 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
VerticalDivider(
|
||||
thickness: 8,
|
||||
color: theme.dividerColor.withValues(alpha: 0.05),
|
||||
),
|
||||
Expanded(
|
||||
flex: controller.ratio[1].toInt(),
|
||||
child: Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: refreshIndicator(
|
||||
onRefresh: controller.onRefresh,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: padding),
|
||||
child: CustomScrollView(
|
||||
controller: controller.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
buildReplyHeader(theme),
|
||||
Obx(
|
||||
() => _buildReplyList(
|
||||
theme,
|
||||
controller.loadingState.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(ThemeData theme, double maxWidth) => SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
sliver: Obx(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/image_view.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/models/common/image_preview_type.dart';
|
||||
@@ -73,75 +72,71 @@ class OpusContent extends StatelessWidget {
|
||||
switch (element.paraType) {
|
||||
case 1 || 4:
|
||||
final isQuote = element.paraType == 4;
|
||||
Widget widget = SelectionArea(
|
||||
child: Text.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text?.nodes?.map((item) {
|
||||
switch (item.type) {
|
||||
case 'TEXT_NODE_TYPE_RICH' when (item.rich != null):
|
||||
Rich rich = item.rich!;
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_EMOJI':
|
||||
Emoji emoji = rich.emoji!;
|
||||
final size = 20.0 * emoji.size;
|
||||
return WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
width: size,
|
||||
height: size,
|
||||
src: emoji.url,
|
||||
type: ImageType.emote,
|
||||
),
|
||||
);
|
||||
default:
|
||||
return TextSpan(
|
||||
text:
|
||||
'${rich.type == 'RICH_TEXT_NODE_TYPE_WEB' ? '\u{1F517}' : ''}${item.rich!.text}',
|
||||
style: _getStyle(
|
||||
rich.style,
|
||||
rich.type == 'RICH_TEXT_NODE_TYPE_TEXT'
|
||||
? null
|
||||
: colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_AT':
|
||||
Get.toNamed('/member?mid=${rich.rid}');
|
||||
// case 'RICH_TEXT_NODE_TYPE_TOPIC':
|
||||
default:
|
||||
if (rich.jumpUrl != null) {
|
||||
PiliScheme.routePushFromUrl(
|
||||
rich.jumpUrl!,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
case 'TEXT_NODE_TYPE_FORMULA'
|
||||
when (item.formula != null):
|
||||
return WidgetSpan(
|
||||
child: CachedNetworkSVGImage(
|
||||
height: 65,
|
||||
'https://api.bilibili.com/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(item.formula!.latexContent!)}',
|
||||
colorFilter: ColorFilter.mode(
|
||||
colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
Widget widget = SelectableText.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text?.nodes?.map((item) {
|
||||
switch (item.type) {
|
||||
case 'TEXT_NODE_TYPE_RICH' when (item.rich != null):
|
||||
Rich rich = item.rich!;
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_EMOJI':
|
||||
Emoji emoji = rich.emoji!;
|
||||
final size = 20.0 * emoji.size;
|
||||
return WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
width: size,
|
||||
height: size,
|
||||
src: emoji.url,
|
||||
type: ImageType.emote,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
placeholderBuilder: (_) =>
|
||||
const SizedBox.shrink(),
|
||||
);
|
||||
default:
|
||||
return TextSpan(
|
||||
text:
|
||||
'${rich.type == 'RICH_TEXT_NODE_TYPE_WEB' ? '\u{1F517}' : ''}${item.rich!.text}',
|
||||
style: _getStyle(
|
||||
rich.style,
|
||||
rich.type == 'RICH_TEXT_NODE_TYPE_TEXT'
|
||||
? null
|
||||
: colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
switch (rich.type) {
|
||||
case 'RICH_TEXT_NODE_TYPE_AT':
|
||||
Get.toNamed('/member?mid=${rich.rid}');
|
||||
// case 'RICH_TEXT_NODE_TYPE_TOPIC':
|
||||
default:
|
||||
if (rich.jumpUrl != null) {
|
||||
PiliScheme.routePushFromUrl(
|
||||
rich.jumpUrl!,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
case 'TEXT_NODE_TYPE_FORMULA' when (item.formula != null):
|
||||
return WidgetSpan(
|
||||
child: CachedNetworkSVGImage(
|
||||
height: 65,
|
||||
'https://api.bilibili.com/x/web-frontend/mathjax/tex?formula=${Uri.encodeComponent(item.formula!.latexContent!)}',
|
||||
colorFilter: ColorFilter.mode(
|
||||
colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
);
|
||||
default:
|
||||
return _getSpan(
|
||||
item.word,
|
||||
isQuote ? colorScheme.onSurfaceVariant : null,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
placeholderBuilder: (_) => const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return _getSpan(
|
||||
item.word,
|
||||
isQuote ? colorScheme.onSurfaceVariant : null,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
if (isQuote) {
|
||||
@@ -223,25 +218,23 @@ class OpusContent extends StatelessWidget {
|
||||
imageUrl: ImageUtil.thumbnailUrl(element.line!.pic!.url!),
|
||||
);
|
||||
case 5 when (element.list != null):
|
||||
return SelectionArea(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: element.list!.items?.indexed.map((entry) {
|
||||
return TextSpan(
|
||||
children: [
|
||||
const 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(),
|
||||
),
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
children: element.list!.items?.indexed.map((entry) {
|
||||
return TextSpan(
|
||||
children: [
|
||||
const 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:
|
||||
@@ -292,7 +285,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.ugc!.cover,
|
||||
),
|
||||
@@ -331,7 +324,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.common!.cover,
|
||||
),
|
||||
@@ -368,7 +361,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.live!.cover,
|
||||
),
|
||||
@@ -405,7 +398,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.opus!.cover,
|
||||
),
|
||||
@@ -466,7 +459,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: element.linkCard!.card!.music!.cover,
|
||||
),
|
||||
@@ -504,7 +497,7 @@ class OpusContent extends StatelessWidget {
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
width: 65 * StyleString.aspectRatio,
|
||||
width: 104,
|
||||
height: 65,
|
||||
src: e.cover,
|
||||
),
|
||||
@@ -570,41 +563,35 @@ class OpusContent extends StatelessWidget {
|
||||
color: colorScheme.onInverseSurface,
|
||||
),
|
||||
width: double.infinity,
|
||||
child: SelectionArea(child: Text.rich(renderer.span!)),
|
||||
child: SelectableText.rich(renderer.span!),
|
||||
);
|
||||
default:
|
||||
if (kDebugMode) debugPrint('unknown type ${element.paraType}');
|
||||
if (element.text?.nodes?.isNotEmpty == true) {
|
||||
return SelectionArea(
|
||||
child: Text.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text!.nodes!
|
||||
.map<TextSpan>((item) => _getSpan(item.word))
|
||||
.toList(),
|
||||
),
|
||||
return SelectableText.rich(
|
||||
textAlign: element.align == 1 ? TextAlign.center : null,
|
||||
TextSpan(
|
||||
children: element.text!.nodes!
|
||||
.map<TextSpan>((item) => _getSpan(item.word))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SelectionArea(
|
||||
child: Text(
|
||||
'不支持的类型 (${element.paraType})',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
return SelectableText(
|
||||
'不支持的类型 (${element.paraType})',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return SelectionArea(
|
||||
child: Text(
|
||||
'错误的类型 $e',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
return SelectableText(
|
||||
'错误的类型 $e',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -676,7 +663,7 @@ Widget moduleBlockedItem(
|
||||
CachedNetworkImage(
|
||||
height: 16,
|
||||
color: Colors.white,
|
||||
imageUrl: moduleBlocked.button!.icon!,
|
||||
imageUrl: moduleBlocked.button!.icon!.http2https,
|
||||
),
|
||||
Text(moduleBlocked.button!.text ?? ''),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user