opt: item

chore: clean up widgets

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-16 21:45:58 +08:00
parent 5ea8a7d313
commit 4972e64cad
104 changed files with 1059 additions and 5871 deletions

View File

@@ -1,79 +0,0 @@
import 'package:PiliPlus/common/widgets/no_splash_factory.dart';
import 'package:PiliPlus/common/widgets/overlay_pop.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:flutter/material.dart';
class AnimatedDialog extends StatefulWidget {
const AnimatedDialog({
super.key,
required this.videoItem,
required this.closeFn,
});
final BaseVideoItemModel videoItem;
final Function closeFn;
@override
State<StatefulWidget> createState() => AnimatedDialogState();
}
class AnimatedDialogState extends State<AnimatedDialog>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> opacityAnimation;
late Animation<double> scaleAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 255));
opacityAnimation = Tween<double>(begin: 0.0, end: 0.6)
.animate(CurvedAnimation(parent: controller, curve: Curves.linear));
scaleAnimation = CurvedAnimation(parent: controller, curve: Curves.linear);
controller.addListener(listener);
controller.forward();
}
void listener() {
setState(() {});
}
@override
void dispose() {
controller.removeListener(listener);
controller.dispose();
super.dispose();
}
void closeFn() async {
await controller.reverse();
widget.closeFn();
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.black.withOpacity(opacityAnimation.value),
child: InkWell(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
splashFactory: NoSplashFactory(),
onTap: closeFn,
child: Center(
child: FadeTransition(
opacity: scaleAnimation,
child: ScaleTransition(
scale: scaleAnimation,
child: OverlayPop(
videoItem: widget.videoItem,
closeFn: closeFn,
),
),
),
),
),
);
}
}

View File

@@ -1,351 +0,0 @@
import 'package:flutter/material.dart';
const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension;
class _SaltedKey<S, V> extends LocalKey {
const _SaltedKey(this.salt, this.value);
final S salt;
final V value;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is _SaltedKey<S, V> &&
other.salt == salt &&
other.value == value;
}
@override
int get hashCode => Object.hash(runtimeType, salt, value);
@override
String toString() {
final String saltString = S == String ? "<'$salt'>" : '<$salt>';
final String valueString = V == String ? "<'$value'>" : '<$value>';
return '[$saltString $valueString]';
}
}
class AppExpansionPanelList extends StatefulWidget {
/// Creates an expansion panel list widget. The [expansionCallback] is
/// triggered when an expansion panel expand/collapse button is pushed.
///
/// The [children] and [animationDuration] arguments must not be null.
const AppExpansionPanelList({
super.key,
required this.children,
this.expansionCallback,
this.animationDuration = kThemeAnimationDuration,
this.expandedHeaderPadding = EdgeInsets.zero,
this.dividerColor,
this.elevation = 2,
}) : _allowOnlyOnePanelOpen = false,
initialOpenPanelValue = null;
/// The children of the expansion panel list. They are laid out in a similar
/// fashion to [ListBody].
final List<AppExpansionPanel> children;
/// The callback that gets called whenever one of the expand/collapse buttons
/// is pressed. The arguments passed to the callback are the index of the
/// pressed panel and whether the panel is currently expanded or not.
///
/// If AppExpansionPanelList.radio is used, the callback may be called a
/// second time if a different panel was previously open. The arguments
/// passed to the second callback are the index of the panel that will close
/// and false, marking that it will be closed.
///
/// For AppExpansionPanelList, the callback needs to setState when it's notified
/// about the closing/opening panel. On the other hand, the callback for
/// AppExpansionPanelList.radio is simply meant to inform the parent widget of
/// changes, as the radio panels' open/close states are managed internally.
///
/// This callback is useful in order to keep track of the expanded/collapsed
/// panels in a parent widget that may need to react to these changes.
final ExpansionPanelCallback? expansionCallback;
/// The duration of the expansion animation.
final Duration animationDuration;
// Whether multiple panels can be open simultaneously
final bool _allowOnlyOnePanelOpen;
/// The value of the panel that initially begins open. (This value is
/// only used when initializing with the [AppExpansionPanelList.radio]
/// constructor.)
final Object? initialOpenPanelValue;
/// The padding that surrounds the panel header when expanded.
///
/// By default, 16px of space is added to the header vertically (above and below)
/// during expansion.
final EdgeInsets expandedHeaderPadding;
/// Defines color for the divider when [AppExpansionPanel.isExpanded] is false.
///
/// If `dividerColor` is null, then [DividerThemeData.color] is used. If that
/// is null, then [ThemeData.dividerColor] is used.
final Color? dividerColor;
/// Defines elevation for the [AppExpansionPanel] while it's expanded.
///
/// By default, the value of elevation is 2.
final double elevation;
@override
State<AppExpansionPanelList> createState() => _AppExpansionPanelListState();
}
class _AppExpansionPanelListState extends State<AppExpansionPanelList> {
ExpansionPanelRadio? _currentOpenPanel;
@override
void initState() {
super.initState();
if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(),
'All ExpansionPanelRadio identifier values must be unique.');
if (widget.initialOpenPanelValue != null) {
_currentOpenPanel = searchPanelByValue(
widget.children.cast<ExpansionPanelRadio>(),
widget.initialOpenPanelValue);
}
}
}
@override
void didUpdateWidget(AppExpansionPanelList oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget._allowOnlyOnePanelOpen) {
assert(_allIdentifiersUnique(),
'All ExpansionPanelRadio identifier values must be unique.');
// If the previous widget was non-radio AppExpansionPanelList, initialize the
// open panel to widget.initialOpenPanelValue
if (!oldWidget._allowOnlyOnePanelOpen) {
_currentOpenPanel = searchPanelByValue(
widget.children.cast<ExpansionPanelRadio>(),
widget.initialOpenPanelValue);
}
} else {
_currentOpenPanel = null;
}
}
bool _allIdentifiersUnique() {
final Map<Object, bool> identifierMap = <Object, bool>{};
for (final ExpansionPanelRadio child
in widget.children.cast<ExpansionPanelRadio>()) {
identifierMap[child.value] = true;
}
return identifierMap.length == widget.children.length;
}
bool _isChildExpanded(int index) {
if (widget._allowOnlyOnePanelOpen) {
final ExpansionPanelRadio radioWidget =
widget.children[index] as ExpansionPanelRadio;
return _currentOpenPanel?.value == radioWidget.value;
}
return widget.children[index].isExpanded;
}
void _handlePressed(bool isExpanded, int index) {
widget.expansionCallback?.call(index, isExpanded);
if (widget._allowOnlyOnePanelOpen) {
final ExpansionPanelRadio pressedChild =
widget.children[index] as ExpansionPanelRadio;
// If another ExpansionPanelRadio was already open, apply its
// expansionCallback (if any) to false, because it's closing.
for (int childIndex = 0;
childIndex < widget.children.length;
childIndex += 1) {
final ExpansionPanelRadio child =
widget.children[childIndex] as ExpansionPanelRadio;
if (widget.expansionCallback != null &&
childIndex != index &&
child.value == _currentOpenPanel?.value) {
widget.expansionCallback?.call(childIndex, false);
}
}
setState(() {
_currentOpenPanel = isExpanded ? null : pressedChild;
});
}
}
ExpansionPanelRadio? searchPanelByValue(
List<ExpansionPanelRadio> panels, Object? value) {
for (final ExpansionPanelRadio panel in panels) {
if (panel.value == value) return panel;
}
return null;
}
@override
Widget build(BuildContext context) {
assert(
kElevationToShadow.containsKey(widget.elevation),
'Invalid value for elevation. See the kElevationToShadow constant for'
' possible elevation values.',
);
final List<MergeableMaterialItem> items = <MergeableMaterialItem>[];
for (int index = 0; index < widget.children.length; index += 1) {
//todo: Uncomment to add gap between selected panels
/*if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));*/
final AppExpansionPanel child = widget.children[index];
final Widget headerWidget = child.headerBuilder(
context,
_isChildExpanded(index),
);
Widget? expandIconContainer = ExpandIcon(
isExpanded: _isChildExpanded(index),
onPressed: !child.canTapOnHeader
? (bool isExpanded) => _handlePressed(isExpanded, index)
: null,
);
if (!child.canTapOnHeader) {
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
expandIconContainer = Semantics(
label: _isChildExpanded(index)
? localizations.expandedIconTapHint
: localizations.collapsedIconTapHint,
container: true,
child: expandIconContainer,
);
}
final iconContainer = child.iconBuilder;
if (iconContainer != null) {
expandIconContainer = iconContainer(
expandIconContainer,
_isChildExpanded(index),
);
}
Widget header = Row(
children: <Widget>[
Expanded(
child: AnimatedContainer(
duration: widget.animationDuration,
curve: Curves.fastOutSlowIn,
margin: _isChildExpanded(index)
? widget.expandedHeaderPadding
: EdgeInsets.zero,
child: ConstrainedBox(
constraints: const BoxConstraints(
minHeight: _kPanelHeaderCollapsedHeight),
child: headerWidget,
),
),
),
if (expandIconContainer != null) expandIconContainer,
],
);
if (child.canTapOnHeader) {
header = MergeSemantics(
child: InkWell(
onTap: () => _handlePressed(_isChildExpanded(index), index),
child: header,
),
);
}
items.add(
MaterialSlice(
key: _SaltedKey<BuildContext, int>(context, index * 2),
color: child.backgroundColor,
child: Column(
children: <Widget>[
header,
AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: child.body,
firstCurve:
const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve:
const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: _isChildExpanded(index)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: widget.animationDuration,
),
],
),
),
);
if (_isChildExpanded(index) && index != widget.children.length - 1) {
items.add(MaterialGap(
key: _SaltedKey<BuildContext, int>(context, index * 2 + 1)));
}
}
return MergeableMaterial(
hasDividers: true,
dividerColor: widget.dividerColor,
elevation: widget.elevation,
children: items,
);
}
}
typedef ExpansionPanelIconBuilder = Widget? Function(
Widget child,
bool isExpanded,
);
class AppExpansionPanel {
/// Creates an expansion panel to be used as a child for [ExpansionPanelList].
/// See [ExpansionPanelList] for an example on how to use this widget.
///
/// The [headerBuilder], [body], and [isExpanded] arguments must not be null.
AppExpansionPanel({
required this.headerBuilder,
required this.body,
this.iconBuilder,
this.isExpanded = false,
this.canTapOnHeader = false,
this.backgroundColor,
});
/// The widget builder that builds the expansion panels' header.
final ExpansionPanelHeaderBuilder headerBuilder;
/// The widget builder that builds the expansion panels' icon.
///
/// If not pass any function, then default icon will be displayed.
///
/// If builder function return null, then icon will not displayed.
final ExpansionPanelIconBuilder? iconBuilder;
/// The body of the expansion panel that's displayed below the header.
///
/// This widget is visible only when the panel is expanded.
final Widget body;
/// Whether the panel is expanded.
///
/// Defaults to false.
final bool isExpanded;
/// Whether tapping on the panel's header will expand/collapse it.
///
/// Defaults to false.
final bool canTapOnHeader;
/// Defines the background color of the panel.
///
/// Defaults to [ThemeData.cardColor].
final Color? backgroundColor;
}

View File

@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
const AppBarWidget({
required this.child,
required this.controller,
required this.visible,
super.key,
});
final PreferredSizeWidget child;
final AnimationController controller;
final bool visible;
@override
Size get preferredSize => child.preferredSize;
@override
Widget build(BuildContext context) {
visible ? controller.reverse() : controller.forward();
return SlideTransition(
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(0, -1),
).animate(CurvedAnimation(
parent: controller,
curve: Curves.easeInOutBack,
)),
child: child,
);
}
}

View File

@@ -1,47 +0,0 @@
import 'package:flutter/material.dart';
class ContentContainer extends StatelessWidget {
final Widget? contentWidget;
final Widget? bottomWidget;
final bool isScrollable;
final Clip? childClipBehavior;
const ContentContainer({
super.key,
this.contentWidget,
this.bottomWidget,
this.isScrollable = true,
this.childClipBehavior,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
clipBehavior: childClipBehavior ?? Clip.hardEdge,
physics: isScrollable ? null : const NeverScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
maxHeight: double.infinity,
),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
if (contentWidget != null)
Expanded(
child: contentWidget!,
)
else
const Spacer(),
if (bottomWidget != null) bottomWidget!,
],
),
),
),
);
},
);
}
}

View File

@@ -468,10 +468,7 @@ class _EpisodePanelState extends CommonSlidePageState<EpisodePanel>
Utils.dateFormat(pubdate), Utils.dateFormat(pubdate),
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context) fontSize: 12,
.textTheme
.labelSmall!
.fontSize,
height: 1, height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip, overflow: TextOverflow.clip,

View File

@@ -1,142 +0,0 @@
import 'package:flutter/material.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'network_img_layer.dart';
class LiveCard extends StatelessWidget {
final dynamic liveItem;
const LiveCard({
super.key,
required this.liveItem,
});
@override
Widget build(BuildContext context) {
final String heroTag = Utils.makeHeroTag(liveItem.roomid);
return Card(
elevation: 0,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
side: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
),
),
margin: EdgeInsets.zero,
child: InkWell(
onTap: () {},
child: Column(
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: liveItem.cover as String,
type: 'emote',
width: maxWidth,
height: maxHeight,
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 200),
child: liveStat(context),
),
),
],
);
}),
),
liveContent(context)
],
),
),
);
}
Widget liveContent(context) {
return Padding(
// 多列
padding: const EdgeInsets.fromLTRB(8, 8, 6, 7),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
liveItem.title as String,
textAlign: TextAlign.start,
style: const TextStyle(fontSize: 13),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(
width: double.infinity,
child: Text(
liveItem.uname as String,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
),
],
),
);
}
Widget liveStat(context) {
return Container(
height: 45,
padding: const EdgeInsets.only(top: 22, left: 8, right: 8),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Colors.transparent,
Colors.black54,
],
tileMode: TileMode.mirror,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
// Row(
// children: [
// StatView(
// theme: 'white',
// view: view,
// ),
// const SizedBox(width: 8),
// StatDanMu(
// theme: 'white',
// danmu: danmaku,
// ),
// ],
// ),
Text(
liveItem.online.toString(),
style: const TextStyle(fontSize: 11, color: Colors.white),
)
],
),
);
}
}

View File

@@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
class NoSplashFactory extends InteractiveInkFeatureFactory {
@override
InteractiveInkFeature create(
{required MaterialInkController controller,
required RenderBox referenceBox,
required Offset position,
required Color color,
required TextDirection textDirection,
bool containedInkWell = false,
RectCallback? rectCallback,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
double? radius,
VoidCallback? onRemoved}) {
return _NoInteractiveInkFeature(
controller: controller,
referenceBox: referenceBox,
color: color,
onRemoved: onRemoved);
}
}
class _NoInteractiveInkFeature extends InteractiveInkFeature {
@override
void paintFeature(Canvas canvas, Matrix4 transform) {}
_NoInteractiveInkFeature({
required super.controller,
required super.referenceBox,
required super.color,
super.onRemoved,
});
}

View File

@@ -1,99 +0,0 @@
import 'dart:math';
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../utils/download.dart';
import '../constants.dart';
import 'network_img_layer.dart';
class OverlayPop extends StatelessWidget {
const OverlayPop({super.key, this.videoItem, this.closeFn});
final dynamic videoItem;
final Function? closeFn;
@override
Widget build(BuildContext context) {
final double imgWidth = min(Get.height, Get.width) - 8 * 2;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8),
width: imgWidth,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(10.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
src: videoItem is card.Card
? (videoItem as card.Card).smallCoverV5.base.cover
: videoItem.pic,
quality: 100,
),
Positioned(
right: 8,
top: 8,
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius:
const BorderRadius.all(Radius.circular(20))),
child: IconButton(
tooltip: '关闭',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => closeFn?.call(),
icon: const Icon(
Icons.close,
size: 18,
color: Colors.white,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
child: Row(
children: [
Expanded(
child: SelectableText(
videoItem is card.Card
? (videoItem as card.Card).smallCoverV5.base.title
: videoItem.title,
),
),
const SizedBox(width: 4),
IconButton(
tooltip: '保存封面图',
onPressed: () async {
await DownloadUtils.downloadImg(
context,
[
videoItem is card.Card
? (videoItem as card.Card).smallCoverV5.base.cover
: videoItem.pic ?? videoItem.cover
],
);
closeFn?.call();
},
icon: const Icon(Icons.download, size: 20),
)
],
)),
],
),
);
}
}

View File

@@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
class SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
SliverHeaderDelegate({required this.height, required this.child});
final double height;
final Widget child;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return child;
}
@override
double get maxExtent => height;
@override
double get minExtent => height;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
true;
}

View File

@@ -5,7 +5,6 @@ abstract class _StatItemBase extends StatelessWidget {
final BuildContext context; final BuildContext context;
final Object value; final Object value;
final String? theme; final String? theme;
final String? size;
final Color? textColor; final Color? textColor;
final double iconSize; final double iconSize;
@@ -13,7 +12,6 @@ abstract class _StatItemBase extends StatelessWidget {
required this.context, required this.context,
required this.value, required this.value,
this.theme, this.theme,
this.size,
this.textColor, this.textColor,
this.iconSize = 13, this.iconSize = 13,
}); });
@@ -42,7 +40,7 @@ abstract class _StatItemBase extends StatelessWidget {
const SizedBox(width: 2), const SizedBox(width: 2),
Text( Text(
Utils.numFormat(value), Utils.numFormat(value),
style: TextStyle(fontSize: size == 'medium' ? 12 : 11, color: color), style: TextStyle(fontSize: 12, color: color),
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
semanticsLabel: semanticsLabel, semanticsLabel: semanticsLabel,
) )
@@ -59,7 +57,6 @@ class StatView extends _StatItemBase {
required super.value, required super.value,
this.goto, this.goto,
super.theme, super.theme,
super.size,
super.textColor, super.textColor,
}) : super(iconSize: 13); }) : super(iconSize: 13);
@@ -81,7 +78,6 @@ class StatDanMu extends _StatItemBase {
required super.context, required super.context,
required super.value, required super.value,
super.theme, super.theme,
super.size,
super.textColor, super.textColor,
}) : super(iconSize: 14); }) : super(iconSize: 14);

View File

@@ -42,9 +42,6 @@ class VideoCardH extends StatelessWidget {
final int aid = videoItem.aid!; final int aid = videoItem.aid!;
final String bvid = videoItem.bvid!; final String bvid = videoItem.bvid!;
String type = 'video'; String type = 'video';
// try {
// type = videoItem.type;
// } catch (_) {}
if (videoItem is SearchVideoItemModel) { if (videoItem is SearchVideoItemModel) {
var typeOrNull = (videoItem as SearchVideoItemModel).type; var typeOrNull = (videoItem as SearchVideoItemModel).type;
if (typeOrNull?.isNotEmpty == true) { if (typeOrNull?.isNotEmpty == true) {
@@ -59,11 +56,6 @@ class VideoCardH extends StatelessWidget {
Semantics( Semantics(
label: Utils.videoItemSemantics(videoItem), label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true, excludeSemantics: true,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(
// label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
// },
child: InkWell( child: InkWell(
onLongPress: () { onLongPress: () {
if (onLongPress != null) { if (onLongPress != null) {
@@ -181,10 +173,6 @@ class VideoCardH extends StatelessWidget {
bottom: 6.0, bottom: 6.0,
type: 'primary', type: 'primary',
), ),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
], ],
); );
}, },
@@ -261,36 +249,15 @@ class VideoCardH extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate) if (showOwner || showPubdate)
Expanded( Text(
flex: 0, "$pubdate ${showOwner ? videoItem.owner.name : ''}",
child: Text( maxLines: 1,
"$pubdate ${showOwner ? videoItem.owner.name : ''}", style: TextStyle(
maxLines: 1, fontSize: 12,
style: TextStyle( height: 1,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline,
height: 1, overflow: TextOverflow.clip,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
), ),
), ),
const SizedBox(height: 3), const SizedBox(height: 3),

View File

@@ -29,107 +29,84 @@ class VideoCardHGrpc extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int aid = videoItem.smallCoverV5.base.args.aid.toInt(); final int aid = videoItem.smallCoverV5.base.args.aid.toInt();
// final String bvid = IdUtils.av2bv(aid);
String type = 'video'; String type = 'video';
// try {
// type = videoItem.type;
// } catch (_) {}
// List<VideoCustomAction> actions =
// VideoCustomActions(videoItem, context).actions;
final String heroTag = Utils.makeHeroTag(aid); final String heroTag = Utils.makeHeroTag(aid);
return Stack(children: [ return Stack(
Semantics( children: [
// label: Utils.videoItemSemantics(videoItem), Semantics(
excludeSemantics: true, excludeSemantics: true,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{ child: InkWell(
// for (var item in actions) borderRadius: BorderRadius.circular(12),
// CustomSemanticsAction(label: item.title): item.onTap!, onLongPress: () => imageSaveDialog(
// }, context: context,
child: InkWell( title: videoItem.smallCoverV5.base.title,
borderRadius: BorderRadius.circular(12), cover: videoItem.smallCoverV5.base.cover,
onLongPress: () => imageSaveDialog( ),
context: context, onTap: () async {
title: videoItem.smallCoverV5.base.title, if (type == 'ketang') {
cover: videoItem.smallCoverV5.base.cover, SmartDialog.showToast('课堂视频暂不支持播放');
), return;
onTap: () async { }
if (type == 'ketang') { try {
SmartDialog.showToast('课堂视频暂不支持播放'); PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri);
return; } catch (err) {
} SmartDialog.showToast(err.toString());
try { }
PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.smallCoverV5.base.cover,
width: maxWidth,
height: maxHeight,
),
),
if (videoItem
.smallCoverV5.coverRightText1.isNotEmpty)
PBadge(
text: Utils.timeFormat(
videoItem.smallCoverV5.coverRightText1),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
],
);
},
),
),
const SizedBox(width: 10),
videoContent(context),
],
);
}, },
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.smallCoverV5.base.cover,
width: maxWidth,
height: maxHeight,
),
),
if (videoItem
.smallCoverV5.coverRightText1.isNotEmpty)
PBadge(
text: Utils.timeFormat(
videoItem.smallCoverV5.coverRightText1),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
],
);
},
),
),
const SizedBox(width: 10),
videoContent(context),
],
);
},
),
), ),
), ),
), ],
// if (source == 'normal') );
// Positioned(
// bottom: 0,
// right: 0,
// child: VideoPopupMenu(
// size: 29,
// iconSize: 17,
// actions: actions,
// ),
// ),
]);
} }
Widget videoContent(context) { Widget videoContent(context) {
@@ -150,24 +127,6 @@ class VideoCardHGrpc extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate) if (showOwner || showPubdate)
Text( Text(
videoItem.smallCoverV5.rightDesc1, videoItem.smallCoverV5.rightDesc1,
@@ -190,24 +149,6 @@ class VideoCardHGrpc extends StatelessWidget {
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
), ),
), ),
// Row(
// children: [
// if (showView) ...[
// StatView(
// theme: 'gray',
// view: videoItem.stat.view as int,
// ),
// const SizedBox(width: 8),
// ],
// if (showDanmaku)
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu as int,
// ),
// const Spacer(),
// if (source == 'normal') const SizedBox(width: 24),
// ],
// ),
], ],
), ),
); );

View File

@@ -80,7 +80,6 @@ class VideoCardHMemberVideo extends StatelessWidget {
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
src: videoItem.cover, src: videoItem.cover,
// videoItem.season?['cover'] ?? videoItem.cover,
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
@@ -191,7 +190,6 @@ class VideoCardHMemberVideo extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
// videoItem.season?['title'] ?? videoItem.title ?? '',
videoItem.title, videoItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
@@ -215,7 +213,7 @@ class VideoCardHMemberVideo extends StatelessWidget {
: videoItem.publishTimeText ?? '', : videoItem.publishTimeText ?? '',
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: 12,
height: 1, height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
@@ -227,15 +225,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
// view: videoItem.season?['view_content'] ??
// videoItem.viewContent,
value: videoItem.stat.viewStr, value: videoItem.stat.viewStr,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
value: videoItem.stat.danmuStr, value: videoItem.stat.danmuStr,
), ),
], ],

View File

@@ -94,10 +94,6 @@ class VideoCardV extends StatelessWidget {
Semantics( Semantics(
label: Utils.videoItemSemantics(videoItem), label: Utils.videoItemSemantics(videoItem),
excludeSemantics: true, excludeSemantics: true,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
// for (var item in actions)
// CustomSemanticsAction(label: item.title): item.onTap!,
// },
child: Card( child: Card(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
@@ -109,6 +105,7 @@ class VideoCardV extends StatelessWidget {
cover: videoItem.pic, cover: videoItem.pic,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: StyleString.aspectRatio, aspectRatio: StyleString.aspectRatio,
@@ -129,8 +126,6 @@ class VideoCardV extends StatelessWidget {
size: 'small', size: 'small',
type: 'gray', type: 'gray',
text: Utils.timeFormat(videoItem.duration), text: Utils.timeFormat(videoItem.duration),
// semanticsLabel:
// '时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}',
) )
], ],
); );
@@ -162,23 +157,17 @@ class VideoCardV extends StatelessWidget {
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5), padding: const EdgeInsets.fromLTRB(6, 5, 6, 5),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Expanded(
children: [ child: Text(
Expanded( "${videoItem.title}\n",
child: Text("${videoItem.title}\n", maxLines: 2,
// semanticsLabel: "${videoItem.title}", overflow: TextOverflow.ellipsis,
maxLines: 2, style: const TextStyle(
overflow: TextOverflow.ellipsis, height: 1.38,
style: const TextStyle(
height: 1.38,
)),
), ),
], ),
), ),
const Spacer(),
// const SizedBox(height: 2),
videoStat(context), videoStat(context),
Row( Row(
children: [ children: [
@@ -224,7 +213,6 @@ class VideoCardV extends StatelessWidget {
flex: 1, flex: 1,
child: Text( child: Text(
videoItem.owner.name.toString(), videoItem.owner.name.toString(),
// semanticsLabel: "Up主${videoItem.owner.name}",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
style: TextStyle( style: TextStyle(
@@ -260,46 +248,32 @@ class VideoCardV extends StatelessWidget {
theme: 'gray', theme: 'gray',
value: videoItem.stat.danmuStr, value: videoItem.stat.danmuStr,
), ),
if (videoItem is RecVideoItemModel) ...<Widget>[ if (videoItem is RecVideoItemModel) ...[
const Spacer(), const Spacer(),
Expanded( Text.rich(
flex: 0, maxLines: 1,
child: Text.rich( TextSpan(
maxLines: 1, style: TextStyle(
TextSpan( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
style: TextStyle( color: Theme.of(context).colorScheme.outline.withOpacity(0.8),
fontSize: ),
Theme.of(context).textTheme.labelSmall!.fontSize, text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
color: Theme.of(context) ),
.colorScheme
.outline
.withOpacity(0.8),
),
text:
Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
)),
const SizedBox(width: 2), const SizedBox(width: 2),
], ] else if (videoItem is RecVideoItemAppModel &&
if (videoItem is RecVideoItemAppModel &&
videoItem.desc != null && videoItem.desc != null &&
videoItem.desc!.contains(' · ')) ...<Widget>[ videoItem.desc!.contains(' · ')) ...[
const Spacer(), const Spacer(),
Expanded( Text.rich(
flex: 0, maxLines: 1,
child: Text.rich( TextSpan(
maxLines: 1, style: TextStyle(
TextSpan( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
style: TextStyle( color: Theme.of(context).colorScheme.outline.withOpacity(0.8),
fontSize: ),
Theme.of(context).textTheme.labelSmall!.fontSize, text: Utils.shortenChineseDateString(
color: Theme.of(context) videoItem.desc!.split(' · ').last)),
.colorScheme ),
.outline
.withOpacity(0.8),
),
text: Utils.shortenChineseDateString(
videoItem.desc!.split(' · ').last)),
)),
const SizedBox(width: 2), const SizedBox(width: 2),
] ]
], ],

View File

@@ -40,55 +40,11 @@ class VideoCardVMemberHome extends StatelessWidget {
Utils.toViewPage( Utils.toViewPage(
'bvid=${bvid ?? IdUtils.av2bv(int.parse(aid!))}&cid=$cid', 'bvid=${bvid ?? IdUtils.av2bv(int.parse(aid!))}&cid=$cid',
arguments: { arguments: {
// 'videoItem': videoItem,
'pic': videoItem.cover, 'pic': videoItem.cover,
'heroTag': heroTag, 'heroTag': heroTag,
}, },
); );
break; break;
// 动态
// case 'picture':
// try {
// String dynamicType = 'picture';
// String uri = videoItem.uri;
// String id = '';
// if (videoItem.uri.startsWith('bilibili://article/')) {
// // https://www.bilibili.com/read/cv27063554
// dynamicType = 'read';
// RegExp regex = RegExp(r'\d+');
// Match match = regex.firstMatch(videoItem.uri)!;
// String matchedNumber = match.group(0)!;
// videoItem.param = int.parse(matchedNumber);
// id = 'cv${videoItem.param}';
// }
// if (uri.startsWith('http')) {
// String path = Uri.parse(uri).path;
// if (isStringNumeric(path.split('/')[1])) {
// // 请求接口
// var res =
// await DynamicsHttp.dynamicDetail(id: path.split('/')[1]);
// if (res['status']) {
// Get.toNamed('/dynamicDetail', arguments: {
// 'item': res['data'],
// 'floor': 1,
// 'action': 'detail'
// });
// } else {
// SmartDialog.showToast(res['msg']);
// }
// return;
// }
// }
// Get.toNamed('/htmlRender', parameters: {
// 'url': uri,
// 'title': videoItem.title,
// 'id': id,
// 'dynamicType': dynamicType
// });
// } catch (err) {
// SmartDialog.showToast(err.toString());
// }
// break;
default: default:
SmartDialog.showToast(goto); SmartDialog.showToast(goto);
Utils.handleWebview(videoItem.uri ?? ''); Utils.handleWebview(videoItem.uri ?? '');
@@ -97,70 +53,57 @@ class VideoCardVMemberHome extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// List<VideoCustomAction> actions = return Stack(
// VideoCustomActions(videoItem, context).actions; children: [
return Stack(children: [ Semantics(
Semantics( excludeSemantics: true,
// label: Utils.videoItemSemantics(videoItem), child: Card(
excludeSemantics: true, clipBehavior: Clip.hardEdge,
// customSemanticsActions: <CustomSemanticsAction, void Function()>{ margin: EdgeInsets.zero,
// for (var item in actions) child: InkWell(
// CustomSemanticsAction(label: item.title): item.onTap!, onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)),
// }, onLongPress: () => imageSaveDialog(
child: Card( context: context,
clipBehavior: Clip.hardEdge, title: videoItem.title,
margin: EdgeInsets.zero, cover: videoItem.cover,
child: InkWell( ),
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)), child: Column(
onLongPress: () => imageSaveDialog( crossAxisAlignment: CrossAxisAlignment.start,
context: context, children: [
title: videoItem.title, AspectRatio(
cover: videoItem.cover, aspectRatio: StyleString.aspectRatio,
), child: LayoutBuilder(
child: Column( builder: (context, boxConstraints) {
children: [ double maxWidth = boxConstraints.maxWidth;
AspectRatio( double maxHeight = boxConstraints.maxHeight;
aspectRatio: StyleString.aspectRatio, return Stack(
child: LayoutBuilder(builder: (context, boxConstraints) { children: [
double maxWidth = boxConstraints.maxWidth; NetworkImgLayer(
double maxHeight = boxConstraints.maxHeight; src: videoItem.cover,
return Stack( width: maxWidth,
children: [ height: maxHeight,
NetworkImgLayer( ),
src: videoItem.cover, if ((videoItem.duration ?? -1) > 0)
width: maxWidth, PBadge(
height: maxHeight, bottom: 6,
), right: 7,
if ((videoItem.duration ?? -1) > 0) size: 'small',
PBadge( type: 'gray',
bottom: 6, text: Utils.timeFormat(videoItem.duration),
right: 7, )
size: 'small', ],
type: 'gray', );
text: Utils.timeFormat(videoItem.duration), },
// semanticsLabel: ),
// '时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}', ),
) videoContent(context, videoItem)
], ],
); ),
}),
),
videoContent(context, videoItem)
],
), ),
), ),
), ),
), ],
// if (videoItem.goto == 'av') );
// Positioned(
// right: -5,
// bottom: -2,
// child: VideoPopupMenu(
// size: 29,
// iconSize: 17,
// actions: actions,
// )),
]);
} }
} }
@@ -168,136 +111,14 @@ Widget videoContent(BuildContext context, Item videoItem) {
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5), padding: const EdgeInsets.fromLTRB(6, 5, 6, 5),
child: Column( child: Text(
crossAxisAlignment: CrossAxisAlignment.start, '${videoItem.title}\n',
// mainAxisAlignment: MainAxisAlignment.spaceBetween, maxLines: 2,
children: [ overflow: TextOverflow.ellipsis,
Row( style: const TextStyle(
children: [ height: 1.38,
Expanded( ),
child: Text('${videoItem.title}\n',
// semanticsLabel: "${videoItem.title}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
height: 1.38,
)),
),
],
),
// const Spacer(),
// const SizedBox(height: 2),
// VideoStat(
// videoItem: videoItem,
// ),
// Row(
// children: [
// if (videoItem.goto == 'bangumi') ...[
// PBadge(
// text: videoItem.bangumiBadge,
// stack: 'normal',
// size: 'small',
// type: 'line',
// fs: 9,
// )
// ],
// if (videoItem.rcmdReason != null) ...[
// PBadge(
// text: videoItem.rcmdReason,
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
// if (videoItem.goto == 'picture') ...[
// const PBadge(
// text: '动态',
// stack: 'normal',
// size: 'small',
// type: 'line',
// fs: 9,
// )
// ],
// if (videoItem.isFollowed == 1) ...[
// const PBadge(
// text: '已关注',
// stack: 'normal',
// size: 'small',
// type: 'color',
// )
// ],
// Expanded(
// flex: 1,
// child: Text(
// videoItem.author ?? '',
// // semanticsLabel: "Up主${videoItem.owner.name}",
// maxLines: 1,
// overflow: TextOverflow.clip,
// style: TextStyle(
// height: 1.5,
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
// ),
// if (videoItem.goto == 'av') const SizedBox(width: 10)
// ],
// ),
],
), ),
), ),
); );
} }
// Widget videoStat(BuildContext context, Item videoItem) {
// return Row(
// children: [
// StatView(
// theme: 'gray',
// view: videoItem.stat.view,
// goto: videoItem.goto,
// ),
// const SizedBox(width: 6),
// if (videoItem.goto != 'picture')
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu,
// ),
// if (videoItem is RecVideoItemModel) ...<Widget>[
// const Spacer(),
// Expanded(
// flex: 0,
// child: RichText(
// maxLines: 1,
// text: TextSpan(
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
// color:
// Theme.of(context).colorScheme.outline.withOpacity(0.8),
// ),
// text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
// )),
// const SizedBox(width: 2),
// ],
// if (videoItem is RecVideoItemAppModel &&
// videoItem.desc != null &&
// videoItem.desc.contains(' · ')) ...<Widget>[
// const Spacer(),
// Expanded(
// flex: 0,
// child: RichText(
// maxLines: 1,
// text: TextSpan(
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
// color:
// Theme.of(context).colorScheme.outline.withOpacity(0.8),
// ),
// text: Utils.shortenChineseDateString(
// videoItem.desc.split(' · ').last)),
// )),
// const SizedBox(width: 2),
// ]
// ],
// );
// }

View File

@@ -10,7 +10,7 @@ import 'package:PiliPlus/models/space_archive/data.dart' as space_archive;
import 'package:PiliPlus/models/space_article/data.dart' as space_article; import 'package:PiliPlus/models/space_article/data.dart' as space_article;
import 'package:PiliPlus/models/space/data.dart' as space_; import 'package:PiliPlus/models/space/data.dart' as space_;
import 'package:PiliPlus/models/space_fav/space_fav.dart'; import 'package:PiliPlus/models/space_fav/space_fav.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';

View File

@@ -87,7 +87,6 @@ class HisListItem with MultiSelectData {
int? kid; int? kid;
String? tagName; String? tagName;
int? liveStatus; int? liveStatus;
dynamic isFullScreen;
HisListItem.fromJson(Map<String, dynamic> json) { HisListItem.fromJson(Map<String, dynamic> json) {
title = json['title']; title = json['title'];

View File

@@ -341,7 +341,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
value: Utils.numFormat(!widget.isLoading value: Utils.numFormat(!widget.isLoading
? widget.bangumiDetail!.stat!['views'] ? widget.bangumiDetail!.stat!['views']
: bangumiItem!.stat!['views']), : bangumiItem!.stat!['views']),
size: 'medium',
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
StatDanMu( StatDanMu(
@@ -351,7 +350,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
? widget ? widget
.bangumiDetail!.stat!['danmakus'] .bangumiDetail!.stat!['danmakus']
: bangumiItem!.stat!['danmakus']), : bangumiItem!.stat!['danmakus']),
size: 'medium',
), ),
if (isLandscape) ...[ if (isLandscape) ...[
const SizedBox(width: 6), const SizedBox(width: 6),

View File

@@ -83,14 +83,12 @@ class _IntroDetailState extends CommonCollapseSlidePageState<IntroDetail> {
context: context, context: context,
theme: 'gray', theme: 'gray',
value: Utils.numFormat(widget.bangumiDetail.stat!['views']), value: Utils.numFormat(widget.bangumiDetail.stat!['views']),
size: 'medium',
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
StatDanMu( StatDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: Utils.numFormat(widget.bangumiDetail.stat!['danmakus']), value: Utils.numFormat(widget.bangumiDetail.stat!['danmakus']),
size: 'medium',
), ),
], ],
), ),

View File

@@ -89,13 +89,9 @@ class BangumiCardV extends StatelessWidget {
Widget bagumiContent(context) { Widget bagumiContent(context) {
return Expanded( return Expanded(
child: Padding( child: Padding(
// 多列
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3), padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
bangumiItem.title, bangumiItem.title,

View File

@@ -55,22 +55,6 @@ class BangumiCardVMemberHome extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
), ),
// if (bangumiItem.badge != null)
// PBadge(
// text: bangumiItem.badge,
// top: 6,
// right: 6,
// bottom: null,
// left: null),
// if (bangumiItem.order != null)
// PBadge(
// text: bangumiItem.order,
// top: null,
// right: null,
// bottom: 6,
// left: 6,
// type: 'gray',
// ),
], ],
); );
}), }),
@@ -87,13 +71,9 @@ class BangumiCardVMemberHome extends StatelessWidget {
Widget bangumiContent(Item bangumiItem) { Widget bangumiContent(Item bangumiItem) {
return Expanded( return Expanded(
child: Padding( child: Padding(
// 多列
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3), padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
bangumiItem.title, bangumiItem.title,
@@ -105,24 +85,6 @@ Widget bangumiContent(Item bangumiItem) {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 1), const SizedBox(height: 1),
// if (bangumiItem.indexShow != null)
// Text(
// bangumiItem.indexShow,
// maxLines: 1,
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
// if (bangumiItem.progress != null)
// Text(
// bangumiItem.progress,
// maxLines: 1,
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
], ],
), ),
), ),

View File

@@ -75,13 +75,9 @@ class BangumiCardVPgcIndex extends StatelessWidget {
Widget bagumiContent(context) { Widget bagumiContent(context) {
return Expanded( return Expanded(
child: Padding( child: Padding(
// 多列
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3), padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
bangumiItem['title'], bangumiItem['title'],
@@ -102,15 +98,6 @@ class BangumiCardVPgcIndex extends StatelessWidget {
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
// if (bangumiItem.progress != null)
// Text(
// bangumiItem.progress,
// maxLines: 1,
// style: TextStyle(
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
], ],
), ),
), ),

View File

@@ -69,16 +69,8 @@ class _UpPanelState extends State<UpPanel> {
), ),
), ),
), ),
const SliverToBoxAdapter( const SliverToBoxAdapter(child: SizedBox(height: 10)),
child: SizedBox(height: 10), SliverList(
),
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
mainAxisExtent: 76,
crossAxisSpacing: 0,
mainAxisSpacing: 0,
),
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
if (widget.dynamicsController.showLiveItems && if (widget.dynamicsController.showLiveItems &&
@@ -102,9 +94,7 @@ class _UpPanelState extends State<UpPanel> {
], ],
), ),
), ),
const SliverToBoxAdapter( const SliverToBoxAdapter(child: SizedBox(height: 200)),
child: SizedBox(height: 200),
),
], ],
); );
} }
@@ -112,119 +102,122 @@ class _UpPanelState extends State<UpPanel> {
Widget upItemBuild(data, i) { Widget upItemBuild(data, i) {
bool isCurrent = widget.dynamicsController.currentMid == data.mid || bool isCurrent = widget.dynamicsController.currentMid == data.mid ||
widget.dynamicsController.currentMid == -1; widget.dynamicsController.currentMid == -1;
return InkWell( return SizedBox(
onTap: () { height: 76,
feedBack(); child: InkWell(
if (data.type == 'up') { onTap: () {
widget.dynamicsController.currentMid = data.mid; feedBack();
// dynamicsController.mid.value = data.mid; if (data.type == 'up') {
widget.dynamicsController widget.dynamicsController.currentMid = data.mid;
..upInfo.value = data // dynamicsController.mid.value = data.mid;
..onSelectUp(data.mid); widget.dynamicsController
// int liveLen = liveList.length; ..upInfo.value = data
// int upLen = upList.length; ..onSelectUp(data.mid);
// double itemWidth = contentWidth + itemPadding.horizontal; // int liveLen = liveList.length;
// double screenWidth = MediaQuery.sizeOf(context).width; // int upLen = upList.length;
// double moveDistance = 0.0; // double itemWidth = contentWidth + itemPadding.horizontal;
// if (itemWidth * (upList.length + liveList.length) <= screenWidth) { // double screenWidth = MediaQuery.sizeOf(context).width;
// } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) { // double moveDistance = 0.0;
// moveDistance = // if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
// (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2; // } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
// } else { // moveDistance =
// moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth; // (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
// } // } else {
data.hasUpdate = false; // moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
// scrollController.animateTo( // }
// moveDistance, data.hasUpdate = false;
// duration: const Duration(milliseconds: 500), // scrollController.animateTo(
// curve: Curves.easeInOut, // moveDistance,
// ); // duration: const Duration(milliseconds: 500),
setState(() {}); // curve: Curves.easeInOut,
} else if (data.type == 'live') { // );
// LiveItemModel liveItem = LiveItemModel.fromJson({ setState(() {});
// 'title': data.title, } else if (data.type == 'live') {
// 'uname': data.uname, // LiveItemModel liveItem = LiveItemModel.fromJson({
// 'face': data.face, // 'title': data.title,
// 'roomid': data.roomId, // 'uname': data.uname,
// }); // 'face': data.face,
Get.toNamed('/liveRoom?roomid=${data.roomId}'); // 'roomid': data.roomId,
} // });
}, Get.toNamed('/liveRoom?roomid=${data.roomId}');
onLongPress: () { }
if (data.mid == -1) { },
return; onLongPress: () {
} if (data.mid == -1) {
String heroTag = Utils.makeHeroTag(data.mid); return;
Get.toNamed('/member?mid=${data.mid}', }
arguments: {'face': data.face, 'heroTag': heroTag}); String heroTag = Utils.makeHeroTag(data.mid);
}, Get.toNamed('/member?mid=${data.mid}',
child: AnimatedOpacity( arguments: {'face': data.face, 'heroTag': heroTag});
opacity: isCurrent ? 1 : 0.6, },
duration: const Duration(milliseconds: 200), child: AnimatedOpacity(
curve: Curves.easeInOut, opacity: isCurrent ? 1 : 0.6,
child: Column( duration: const Duration(milliseconds: 200),
mainAxisSize: MainAxisSize.min, curve: Curves.easeInOut,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
Stack( mainAxisAlignment: MainAxisAlignment.center,
clipBehavior: Clip.none, children: [
children: [ Stack(
Padding( clipBehavior: Clip.none,
padding: const EdgeInsets.symmetric(horizontal: 4), children: [
child: data.face != '' Padding(
? NetworkImgLayer( padding: const EdgeInsets.symmetric(horizontal: 4),
width: 38, child: data.face != ''
height: 38, ? NetworkImgLayer(
src: data.face, width: 38,
type: 'avatar', height: 38,
) src: data.face,
: const CircleAvatar( type: 'avatar',
backgroundColor: Color(0xFF5CB67B), )
backgroundImage: AssetImage( : const CircleAvatar(
'assets/images/logo/logo.png', backgroundColor: Color(0xFF5CB67B),
backgroundImage: AssetImage(
'assets/images/logo/logo.png',
),
), ),
), ),
), Positioned(
Positioned( top: data.type == 'live' ? -5 : 0,
top: data.type == 'live' ? -5 : 0, right: data.type == 'live' ? -6 : 4,
right: data.type == 'live' ? -6 : 4, child: Badge(
child: Badge( smallSize: 8,
smallSize: 8, label: data.type == 'live' ? const Text(' Live ') : null,
label: data.type == 'live' ? const Text(' Live ') : null, textColor:
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
Theme.of(context).colorScheme.onSecondaryContainer, alignment: AlignmentDirectional.topStart,
alignment: AlignmentDirectional.topStart, isLabelVisible: data.type == 'live' ||
isLabelVisible: data.type == 'live' || (data.type == 'up' && (data.hasUpdate ?? false)),
(data.type == 'up' && (data.hasUpdate ?? false)), backgroundColor: data.type == 'live'
backgroundColor: data.type == 'live' ? Theme.of(context)
? Theme.of(context) .colorScheme
.colorScheme .secondaryContainer
.secondaryContainer .withOpacity(0.75)
.withOpacity(0.75) : Theme.of(context).colorScheme.primary,
: Theme.of(context).colorScheme.primary, ),
),
],
),
const SizedBox(height: 3),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
data.uname,
overflow: TextOverflow.clip,
maxLines: 2,
softWrap: true,
textAlign: TextAlign.center,
style: TextStyle(
color: widget.dynamicsController.currentMid == data.mid
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
height: 1.1,
fontSize: 12.5,
), ),
), ),
],
),
const SizedBox(height: 3),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
data.uname,
overflow: TextOverflow.clip,
maxLines: 2,
softWrap: true,
textAlign: TextAlign.center,
style: TextStyle(
color: widget.dynamicsController.currentMid == data.mid
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
height: 1.1,
fontSize: 12.5,
),
), ),
), ],
], ),
), ),
), ),
); );

View File

@@ -59,7 +59,7 @@ class _FansPageState extends State<FansPage> {
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: Grid.smallCardWidth * 2, maxCrossAxisExtent: Grid.smallCardWidth * 2,
mainAxisExtent: 56, mainAxisExtent: 66,
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -11,13 +11,16 @@ Widget fanItem({item}) {
leading: Hero( leading: Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
width: 38, width: 45,
height: 38, height: 45,
type: 'avatar', type: 'avatar',
src: item.face, src: item.face,
), ),
), ),
title: Text(item.uname), title: Text(
item.uname,
style: const TextStyle(fontSize: 14),
),
subtitle: Text( subtitle: Text(
item.sign, item.sign,
maxLines: 1, maxLines: 1,

View File

@@ -43,11 +43,7 @@ class _FavArticlePageState extends State<FavArticlePage>
Widget _buildBody(LoadingState<List<dynamic>?> loadingState) { Widget _buildBody(LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -62,11 +58,7 @@ class _FavArticlePageState extends State<FavArticlePage>
bottom: MediaQuery.paddingOf(context).bottom + 80, bottom: MediaQuery.paddingOf(context).bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.6,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -56,12 +56,21 @@ class FavArticleItem extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Expanded(
item['content'], child: Text(
maxLines: 2, item['content'],
overflow: TextOverflow.ellipsis, style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.bodyMedium!
.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
), ),
const Spacer(),
Row( Row(
children: [ children: [
StatView( StatView(
@@ -83,12 +92,15 @@ class FavArticleItem extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 3),
Text( Text(
'${item['author']['name']} · ${item['pub_time']}', '${item['author']['name']} · ${item['pub_time']}',
maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
), ),
), ),
], ],

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
@@ -136,11 +135,7 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
Widget _buildBody(LoadingState<List<FavArticleModel>?> loadingState) { Widget _buildBody(LoadingState<List<FavArticleModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -153,11 +148,7 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80), bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.6,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -70,14 +70,21 @@ class FavNoteItem extends StatelessWidget {
item.summary ?? '', item.summary ?? '',
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: 13,
height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
), ),
), ),
const SizedBox(height: 3),
Text( Text(
item.message ?? '', item.message ?? '',
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: 13,
height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
), ),
), ),
], ],

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
@@ -160,11 +159,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
Widget _buildBody(LoadingState<List<BangumiListItemModel>?> loadingState) { Widget _buildBody(LoadingState<List<BangumiListItemModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -177,11 +172,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80), bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.6,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -48,11 +48,7 @@ class _FavVideoPageState extends State<FavVideoPage>
Widget _buildBody(LoadingState<List<FavFolderItemData>?> loadingState) { Widget _buildBody(LoadingState<List<FavFolderItemData>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -67,11 +63,7 @@ class _FavVideoPageState extends State<FavVideoPage>
bottom: 80 + MediaQuery.paddingOf(context).bottom, bottom: 80 + MediaQuery.paddingOf(context).bottom,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: loadingState.response!.length, childCount: loadingState.response!.length,
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -430,11 +430,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
Widget _buildBody(LoadingState<List<FavDetailItemData>?> loadingState) { Widget _buildBody(LoadingState<List<FavDetailItemData>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -448,11 +444,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
bottom: MediaQuery.of(context).padding.bottom + 85, bottom: MediaQuery.of(context).padding.bottom + 85,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context, minHeight: 110),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length) { if (index == loadingState.response!.length) {

View File

@@ -66,15 +66,6 @@ class FavVideoCardH extends StatelessWidget {
return; return;
} }
onViewFav?.call(); onViewFav?.call();
// Utils.toViewPage(
// 'bvid=$bvid&cid=${videoItem.cid}${epId?.isNotEmpty == true ? '&epId=$epId' : ''}',
// arguments: {
// 'videoItem': videoItem,
// 'heroTag': Utils.makeHeroTag(id),
// 'videoType':
// epId != null ? SearchType.media_bangumi : SearchType.video,
// },
// );
}, },
onLongPress: isSort == true onLongPress: isSort == true
? null ? null
@@ -180,7 +171,7 @@ class FavVideoCardH extends StatelessWidget {
Text( Text(
videoItem.owner.name!, videoItem.owner.name!,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: 12,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),

View File

@@ -73,7 +73,10 @@ class _FavSearchPageState extends State<FavSearchPage> {
Loading() => errorWidget(), Loading() => errorWidget(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? switch (_favSearchCtr.searchType) { ? switch (_favSearchCtr.searchType) {
SearchType.fav || SearchType.history => CustomScrollView( SearchType.fav ||
SearchType.history ||
SearchType.later =>
CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController, controller: _favSearchCtr.scrollController,
slivers: [ slivers: [
@@ -82,111 +85,71 @@ class _FavSearchPageState extends State<FavSearchPage> {
bottom: MediaQuery.of(context).padding.bottom + 80, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: _favSearchCtr.searchType == SearchType.fav
mainAxisSpacing: 2, ? Grid.videoCardHDelegate(context, minHeight: 110)
maxCrossAxisExtent: Grid.mediumCardWidth * 2, : Grid.videoCardHDelegate(context),
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore(); _favSearchCtr.onLoadMore();
} }
final item = loadingState.response![index]; final item = loadingState.response![index];
return _favSearchCtr.searchType == SearchType.fav if (_favSearchCtr.searchType == SearchType.fav) {
? FavVideoCardH( return FavVideoCardH(
videoItem: item, videoItem: item,
onDelFav: _favSearchCtr.isOwner == true onDelFav: _favSearchCtr.isOwner == true
? () { ? () {
_favSearchCtr.onCancelFav( _favSearchCtr.onCancelFav(
index, index,
item.id!, item.id!,
item.type, item.type,
); );
} }
: null, : null,
onViewFav: () { onViewFav: () {
Utils.toViewPage( Utils.toViewPage(
'bvid=${item.bvid}&cid=${item.cid}', 'bvid=${item.bvid}&cid=${item.cid}',
arguments: { arguments: {
'videoItem': item, 'videoItem': item,
'heroTag': 'heroTag': Utils.makeHeroTag(item.bvid),
Utils.makeHeroTag(item.bvid), 'sourceType': 'fav',
'sourceType': 'fav', 'mediaId': Get.arguments['mediaId'],
'mediaId': Get.arguments['mediaId'], 'oid': item.id,
'oid': item.id, 'favTitle': Get.arguments['title'],
'favTitle': Get.arguments['title'], 'count': Get.arguments['count'],
'count': Get.arguments['count'], 'desc': true,
'desc': true, 'isContinuePlaying': true,
'isContinuePlaying': true,
},
);
},
)
: HistoryItem(
videoItem: item,
ctr: _favSearchCtr,
onChoose: null,
onDelete: (kid, business) {
_favSearchCtr.onDelHistory(
index, kid, business);
}, },
); );
}, },
childCount: loadingState.response!.length, );
),
),
),
],
),
SearchType.follow => ListView.builder(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
controller: _favSearchCtr.scrollController,
itemCount: loadingState.response!.length,
itemBuilder: ((context, index) {
if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore();
}
return FollowItem(
item: loadingState.response![index],
);
}),
),
SearchType.later => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController,
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore();
} }
var videoItem = loadingState.response![index];
if (_favSearchCtr.searchType ==
SearchType.history) {
return HistoryItem(
videoItem: item,
ctr: _favSearchCtr,
onChoose: null,
onDelete: (kid, business) {
_favSearchCtr.onDelHistory(
index, kid, business);
},
);
}
return Stack( return Stack(
children: [ children: [
VideoCardH( VideoCardH(
videoItem: videoItem, videoItem: item,
source: 'later', source: 'later',
onViewLater: (cid) { onViewLater: (cid) {
Utils.toViewPage( Utils.toViewPage(
'bvid=${videoItem.bvid}&cid=$cid', 'bvid=${item.bvid}&cid=$cid',
arguments: { arguments: {
'videoItem': videoItem, 'videoItem': item,
'oid': videoItem.aid, 'oid': item.aid,
'heroTag': 'heroTag': Utils.makeHeroTag(item.bvid),
Utils.makeHeroTag(videoItem.bvid),
'sourceType': 'watchLater', 'sourceType': 'watchLater',
'count': Get.arguments['count'], 'count': Get.arguments['count'],
'favTitle': '稍后再看', 'favTitle': '稍后再看',
@@ -205,8 +168,7 @@ class _FavSearchPageState extends State<FavSearchPage> {
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) => builder: (context, constraints) =>
AnimatedOpacity( AnimatedOpacity(
opacity: opacity: item.checked == true ? 1 : 0,
videoItem.checked == true ? 1 : 0,
duration: duration:
const Duration(milliseconds: 200), const Duration(milliseconds: 200),
child: Container( child: Container(
@@ -224,9 +186,8 @@ class _FavSearchPageState extends State<FavSearchPage> {
width: 34, width: 34,
height: 34, height: 34,
child: AnimatedScale( child: AnimatedScale(
scale: videoItem.checked == true scale:
? 1 item.checked == true ? 1 : 0,
: 0,
duration: const Duration( duration: const Duration(
milliseconds: 250), milliseconds: 250),
curve: Curves.easeInOut, curve: Curves.easeInOut,
@@ -272,7 +233,7 @@ class _FavSearchPageState extends State<FavSearchPage> {
_favSearchCtr.toViewDel( _favSearchCtr.toViewDel(
context, context,
index, index,
videoItem.aid, item.aid,
); );
}, },
icon: Icons.clear, icon: Icons.clear,
@@ -291,6 +252,21 @@ class _FavSearchPageState extends State<FavSearchPage> {
), ),
], ],
), ),
SearchType.follow => ListView.builder(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
controller: _favSearchCtr.scrollController,
itemCount: loadingState.response!.length,
itemBuilder: ((context, index) {
if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore();
}
return FollowItem(
item: loadingState.response![index],
);
}),
),
} }
: errorWidget( : errorWidget(
callback: _favSearchCtr.onReload, callback: _favSearchCtr.onReload,

View File

@@ -121,22 +121,3 @@ class _FollowPageState extends State<FollowPage> {
); );
} }
} }
// class _FakeAPI {
// static const List<String> _kOptions = <String>[
// 'aardvark',
// 'bobcat',
// 'chameleon',
// ];
// // Searches the options, but injects a fake "network" delay.
// static Future<Iterable<String>> search(String query) async {
// await Future<void>.delayed(
// const Duration(seconds: 1)); // Fake 1 second delay.
// if (query == '') {
// return const Iterable<String>.empty();
// }
// return _kOptions.where((String option) {
// return option.contains(query.toLowerCase());
// });
// }
// }

View File

@@ -82,7 +82,6 @@ class FollowItem extends StatelessWidget {
mid: item.mid, mid: item.mid,
isFollow: item.attribute != -1, isFollow: item.attribute != -1,
callback: callback, callback: callback,
// followStatus: {'special': item.special, 'tag': item.tag},
); );
}, },
style: FilledButton.styleFrom( style: FilledButton.styleFrom(

View File

@@ -251,11 +251,7 @@ class _HistoryPageState extends State<HistoryPage>
Widget _buildBody(LoadingState<List<HisListItem>?> loadingState) { Widget _buildBody(LoadingState<List<HisListItem>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -270,11 +266,7 @@ class _HistoryPageState extends State<HistoryPage>
bottom: MediaQuery.of(context).padding.bottom + 80, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -47,17 +47,6 @@ class HistoryItem extends StatelessWidget {
return; return;
} }
if (videoItem.history.business?.contains('article') == true) { if (videoItem.history.business?.contains('article') == true) {
// int cid = videoItem.history.cid ??
// // videoItem.history.oid ??
// await SearchHttp.ab2c(aid: aid, bvid: bvid);
// Get.toNamed(
// '/webview',
// parameters: {
// 'url': 'https://www.bilibili.com/read/cv$cid',
// 'type': 'note',
// 'pageTitle': videoItem.title
// },
// );
Utils.toDupNamed( Utils.toDupNamed(
'/htmlRender', '/htmlRender',
parameters: { parameters: {
@@ -69,21 +58,12 @@ class HistoryItem extends StatelessWidget {
); );
} else if (videoItem.history.business == 'live') { } else if (videoItem.history.business == 'live') {
if (videoItem.liveStatus == 1) { if (videoItem.liveStatus == 1) {
// LiveItemModel liveItem = LiveItemModel.fromJson({
// 'face': videoItem.authorFace,
// 'roomid': videoItem.history.oid,
// 'pic': videoItem.cover,
// 'title': videoItem.title,
// 'uname': videoItem.authorName,
// 'cover': videoItem.cover,
// });
Get.toNamed('/liveRoom?roomid=${videoItem.history.oid}'); Get.toNamed('/liveRoom?roomid=${videoItem.history.oid}');
} else { } else {
SmartDialog.showToast('直播未开播'); SmartDialog.showToast('直播未开播');
} }
} else if (videoItem.history.business == 'pgc' || } else if (videoItem.history.business == 'pgc' ||
videoItem.tagName?.contains('动画') == true) { videoItem.tagName?.contains('动画') == true) {
/// hack
var bvid = videoItem.history.bvid; var bvid = videoItem.history.bvid;
if (bvid != null && bvid != '') { if (bvid != null && bvid != '') {
var result = await VideoHttp.videoIntro(bvid: bvid); var result = await VideoHttp.videoIntro(bvid: bvid);
@@ -113,7 +93,6 @@ class HistoryItem extends StatelessWidget {
} }
} else { } else {
int cid = videoItem.history.cid ?? int cid = videoItem.history.cid ??
// videoItem.history.oid ??
await SearchHttp.ab2c(aid: aid, bvid: bvid); await SearchHttp.ab2c(aid: aid, bvid: bvid);
Utils.toViewPage( Utils.toViewPage(
'bvid=$bvid&cid=$cid', 'bvid=$bvid&cid=$cid',
@@ -138,137 +117,217 @@ class HistoryItem extends StatelessWidget {
onChoose?.call(); onChoose?.call();
} }
}, },
child: Padding( child: Stack(
padding: const EdgeInsets.symmetric( children: [
horizontal: StyleString.safeSpace, Padding(
vertical: 5, padding: const EdgeInsets.symmetric(
), horizontal: StyleString.safeSpace,
child: LayoutBuilder( vertical: 5,
builder: (context, boxConstraints) { ),
double width = child: LayoutBuilder(
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; builder: (context, boxConstraints) {
return SizedBox( double width =
height: width / StyleString.aspectRatio, (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
child: Row( return SizedBox(
mainAxisAlignment: MainAxisAlignment.start, height: width / StyleString.aspectRatio,
crossAxisAlignment: CrossAxisAlignment.start, child: Row(
children: [ mainAxisAlignment: MainAxisAlignment.start,
Stack( crossAxisAlignment: CrossAxisAlignment.start,
clipBehavior: Clip.none,
children: [ children: [
AspectRatio( Stack(
aspectRatio: StyleString.aspectRatio, clipBehavior: Clip.none,
child: LayoutBuilder( children: [
builder: (context, boxConstraints) { AspectRatio(
double maxWidth = boxConstraints.maxWidth; aspectRatio: StyleString.aspectRatio,
double maxHeight = boxConstraints.maxHeight; child: LayoutBuilder(
return Stack( builder: (context, boxConstraints) {
clipBehavior: Clip.none, double maxWidth = boxConstraints.maxWidth;
children: [ double maxHeight = boxConstraints.maxHeight;
NetworkImgLayer( return Stack(
src: (videoItem.cover.isNullOrEmpty clipBehavior: Clip.none,
? videoItem.covers?.first ?? '' children: [
: videoItem.cover), NetworkImgLayer(
width: maxWidth, src: (videoItem.cover.isNullOrEmpty
height: maxHeight, ? videoItem.covers?.firstOrNull ?? ''
), : videoItem.cover),
if (!BusinessType width: maxWidth,
.hiddenDurationType.hiddenDurationType height: maxHeight,
.contains(videoItem.history.business)) ),
PBadge( if (!BusinessType
text: videoItem.progress == -1 .hiddenDurationType.hiddenDurationType
? '已看完' .contains(videoItem.history.business))
: '${Utils.timeFormat(videoItem.progress)}/${Utils.timeFormat(videoItem.duration!)}', PBadge(
right: 6.0, text: videoItem.progress == -1
bottom: 8.0, ? '已看完'
type: 'gray', : '${Utils.timeFormat(videoItem.progress)}/${Utils.timeFormat(videoItem.duration!)}',
), right: 6.0,
// 右上角 bottom: 8.0,
if (BusinessType.showBadge.showBadge type: 'gray',
.contains(videoItem.history.business) || ),
videoItem.history.business == // 右上角
BusinessType.live.type) if (BusinessType.showBadge.showBadge
PBadge( .contains(
text: videoItem.badge, videoItem.history.business) ||
top: 6.0, videoItem.history.business ==
right: 6.0, BusinessType.live.type)
bottom: null, PBadge(
left: null, text: videoItem.badge,
), top: 6.0,
], right: 6.0,
); bottom: null,
}, left: null,
), ),
), ],
Positioned.fill( );
child: AnimatedOpacity( },
opacity: videoItem.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black.withOpacity(0.6),
), ),
child: SizedBox( ),
width: 34, Positioned.fill(
height: 34, child: AnimatedOpacity(
child: AnimatedScale( opacity: videoItem.checked == true ? 1 : 0,
scale: videoItem.checked == true ? 1 : 0, duration: const Duration(milliseconds: 200),
duration: const Duration(milliseconds: 250), child: Container(
curve: Curves.easeInOut, alignment: Alignment.center,
child: IconButton( decoration: BoxDecoration(
tooltip: '取消选择', borderRadius: BorderRadius.circular(10),
style: ButtonStyle( color: Colors.black.withOpacity(0.6),
padding: WidgetStateProperty.all( ),
EdgeInsets.zero), child: SizedBox(
backgroundColor: width: 34,
WidgetStateProperty.resolveWith( height: 34,
(states) { child: AnimatedScale(
return Theme.of(context) scale: videoItem.checked == true ? 1 : 0,
.colorScheme duration: const Duration(milliseconds: 250),
.surface curve: Curves.easeInOut,
.withOpacity(0.8); child: IconButton(
tooltip: '取消选择',
style: ButtonStyle(
padding: WidgetStateProperty.all(
EdgeInsets.zero),
backgroundColor:
WidgetStateProperty.resolveWith(
(states) {
return Theme.of(context)
.colorScheme
.surface
.withOpacity(0.8);
},
),
),
onPressed: () {
feedBack();
onChoose?.call();
}, },
icon: Icon(Icons.done_all_outlined,
color: Theme.of(context)
.colorScheme
.primary),
), ),
), ),
onPressed: () {
feedBack();
onChoose?.call();
},
icon: Icon(Icons.done_all_outlined,
color: Theme.of(context)
.colorScheme
.primary),
), ),
), ),
), ),
), ),
), if (videoItem.duration != null &&
videoItem.duration != 0 &&
videoItem.progress != null &&
videoItem.progress != 0)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: videoProgressIndicator(
videoItem.progress == -1
? 1
: videoItem.progress! / videoItem.duration!,
),
),
],
), ),
if (videoItem.duration != null && const SizedBox(width: 10),
videoItem.duration != 0 && videoContent(context),
videoItem.progress != null &&
videoItem.progress != 0)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: videoProgressIndicator(
videoItem.progress == -1
? 1
: videoItem.progress! / videoItem.duration!,
),
),
], ],
), ),
const SizedBox(width: 10), );
videoContent(context), },
),
),
Positioned(
right: 12,
bottom: 12,
child: SizedBox(
width: 29,
height: 29,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '功能菜单',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
position: PopupMenuPosition.under,
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
if (videoItem.authorMid != null &&
videoItem.authorName?.isNotEmpty == true)
PopupMenuItem<String>(
onTap: () {
Get.toNamed(
'/member?mid=${videoItem.authorMid}',
arguments: {
'heroTag': '${videoItem.authorMid}',
},
);
},
height: 35,
child: Row(
children: [
Icon(MdiIcons.accountCircleOutline, size: 16),
SizedBox(width: 6),
Text(
'访问:${videoItem.authorName}',
style: TextStyle(fontSize: 13),
)
],
),
),
if (videoItem.history.business != 'pgc' &&
videoItem.badge != '番剧' &&
videoItem.tagName?.contains('动画') != true &&
videoItem.history.business != 'live' &&
videoItem.history.business?.contains('article') != true)
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () =>
onDelete(videoItem.kid, videoItem.history.business),
height: 35,
child: const Row(
children: [
Icon(Icons.close_outlined, size: 16),
SizedBox(width: 6),
Text('删除记录', style: TextStyle(fontSize: 13))
],
),
),
], ],
), ),
); ),
}, ),
), ],
), ),
); );
} }
@@ -278,123 +337,34 @@ class HistoryItem extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Expanded(
videoItem.title, child: Text(
textAlign: TextAlign.start, videoItem.title,
style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: videoItem.videos! > 1 ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
if (videoItem.isFullScreen != null) ...[
const SizedBox(height: 2),
Text(
videoItem.isFullScreen,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
color: Theme.of(context).colorScheme.outline), height: 1.42,
maxLines: 1, letterSpacing: 0.3,
),
maxLines: videoItem.videos! > 1 ? 1 : 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
], ),
const Spacer(),
if (videoItem.authorName != '') if (videoItem.authorName != '')
Row( Text(
children: [ videoItem.authorName!,
Text( style: TextStyle(
videoItem.authorName!, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
style: TextStyle( color: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, ),
color: Theme.of(context).colorScheme.outline, ),
), const SizedBox(height: 2),
), Text(
], Utils.dateFormat(videoItem.viewAt!),
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
), ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
Utils.dateFormat(videoItem.viewAt!),
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
),
// if (videoItem is HisListItem)
SizedBox(
width: 29,
height: 29,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '功能菜单',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
position: PopupMenuPosition.under,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
if (videoItem.authorMid != null &&
videoItem.authorName?.isNotEmpty == true)
PopupMenuItem<String>(
onTap: () {
Get.toNamed(
'/member?mid=${videoItem.authorMid}',
arguments: {
'heroTag': '${videoItem.authorMid}',
},
);
},
height: 35,
child: Row(
children: [
Icon(MdiIcons.accountCircleOutline, size: 16),
SizedBox(width: 6),
Text(
'访问:${videoItem.authorName}',
style: TextStyle(fontSize: 13),
)
],
),
),
if (videoItem.history.business != 'pgc' &&
videoItem.badge != '番剧' &&
videoItem.tagName?.contains('动画') != true &&
videoItem.history.business != 'live' &&
videoItem.history.business?.contains('article') != true)
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () =>
onDelete(videoItem.kid, videoItem.history.business),
height: 35,
child: const Row(
children: [
Icon(Icons.close_outlined, size: 16),
SizedBox(width: 6),
Text('删除记录', style: TextStyle(fontSize: 13))
],
),
),
],
),
),
],
), ),
], ],
), ),

View File

@@ -1,101 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/pages/mine/view.dart';
import 'package:PiliPlus/utils/storage.dart';
class HomeAppBar extends StatelessWidget {
const HomeAppBar({super.key});
@override
Widget build(BuildContext context) {
dynamic userInfo = GStorage.userInfo.get('userInfoCache');
return SliverAppBar(
// forceElevated: true,
toolbarHeight: MediaQuery.of(context).padding.top,
expandedHeight: kToolbarHeight + MediaQuery.of(context).padding.top,
automaticallyImplyLeading: false,
pinned: true,
floating: true,
primary: false,
flexibleSpace: LayoutBuilder(
builder: (context, constraints) {
return FlexibleSpaceBar(
background: Column(
children: [
AppBar(
title: const Text(
'PiLiPlus',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1,
fontFamily: 'ArchivoNarrow',
),
),
actions: [
Hero(
tag: 'searchTag',
child: IconButton(
tooltip: '搜索',
onPressed: () {
Get.toNamed('/search');
},
icon: const Icon(CupertinoIcons.search, size: 22),
),
),
// IconButton(
// onPressed: () {},
// icon: const Icon(CupertinoIcons.bell, size: 22),
// ),
const SizedBox(width: 6),
/// TODO
if (userInfo != null) ...[
GestureDetector(
onTap: () => showModalBottomSheet(
context: context,
builder: (context) => const SizedBox(
height: 450,
child: MinePage(),
),
clipBehavior: Clip.hardEdge,
isScrollControlled: true,
),
child: NetworkImgLayer(
type: 'avatar',
width: 32,
height: 32,
src: userInfo.face,
semanticsLabel: '我的',
),
),
const SizedBox(width: 10),
] else ...[
IconButton(
tooltip: '登录',
onPressed: () => showModalBottomSheet(
context: context,
builder: (context) => const SizedBox(
height: 450,
child: MinePage(),
),
clipBehavior: Clip.hardEdge,
isScrollControlled: true,
),
icon: const Icon(CupertinoIcons.person, size: 22),
),
],
const SizedBox(width: 10)
],
),
],
),
);
},
),
);
}
}

View File

@@ -147,11 +147,7 @@ class _HotPageState extends CommonPageState<HotPage, HotController>
Widget _buildSkeleton() { Widget _buildSkeleton() {
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -166,11 +162,7 @@ class _HotPageState extends CommonPageState<HotPage, HotController>
Loading() => _buildSkeleton(), Loading() => _buildSkeleton(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverGrid( ? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -62,11 +62,7 @@ class _LaterViewChildPageState extends State<LaterViewChildPage>
Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) { Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -76,11 +72,7 @@ class _LaterViewChildPageState extends State<LaterViewChildPage>
), ),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverGrid( ? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -142,19 +142,6 @@ class LiveCardV extends StatelessWidget {
), ),
], ],
), ),
// child: RichText(
// maxLines: 1,
// textAlign: TextAlign.justify,
// softWrap: false,
// text: TextSpan(
// style: const TextStyle(fontSize: 11, color: Colors.white),
// children: [
// TextSpan(text: liveItem!.areaName!),
// TextSpan(text: liveItem!.watchedShow!['text_small']),
// ],
// ),
// ),
); );
} }
} }

View File

@@ -140,19 +140,6 @@ class LiveCardVFollow extends StatelessWidget {
), ),
], ],
), ),
// child: RichText(
// maxLines: 1,
// textAlign: TextAlign.justify,
// softWrap: false,
// text: TextSpan(
// style: const TextStyle(fontSize: 11, color: Colors.white),
// children: [
// TextSpan(text: liveItem!.areaName!),
// TextSpan(text: liveItem!.watchedShow!['text_small']),
// ],
// ),
// ),
); );
} }
} }

View File

@@ -1,10 +1,9 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_article/item.dart'; import 'package:PiliPlus/models/space_article/item.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/article/member_article_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -50,11 +49,7 @@ class _MemberArticleState extends State<MemberArticle>
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
SliverGrid( SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -59,27 +59,36 @@ class MemberArticleItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (item.title?.isNotEmpty == true) ...[ if (item.title?.isNotEmpty == true) ...[
Text( Expanded(
item.title!, child: Text(
maxLines: 2, item.title!,
overflow: TextOverflow.ellipsis, maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.bodyMedium!
.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
),
), ),
const Spacer(),
], ],
Text( Text(
'${item.publishTimeText}', '${item.publishTimeText}',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 3),
Row( Row(
children: [ children: [
StatView( StatView(
context: context, context: context,
value: item.stats?.view ?? 0, value: item.stats?.view ?? 0,
size: 'medium',
goto: 'picture', goto: 'picture',
textColor: Theme.of(context).colorScheme.outline, textColor: Theme.of(context).colorScheme.outline,
), ),
@@ -87,7 +96,6 @@ class MemberArticleItem extends StatelessWidget {
StatView( StatView(
context: context, context: context,
goto: 'reply', goto: 'reply',
size: 'medium',
value: item.stats?.reply ?? 0, value: item.stats?.reply ?? 0,
textColor: Theme.of(context).colorScheme.outline, textColor: Theme.of(context).colorScheme.outline,
), ),

View File

@@ -4,7 +4,7 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_archive/item.dart'; import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart'; import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/bangumi/member_bangumi_ctr.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';

View File

@@ -3,9 +3,9 @@ import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/item.dart'; import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/pages/member/controller.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/models/space/data.dart' as space; import 'package:PiliPlus/models/space/data.dart' as space;

View File

@@ -3,8 +3,8 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_fav/datum.dart'; import 'package:PiliPlus/models/space_fav/datum.dart';
import 'package:PiliPlus/models/space_fav/list.dart'; import 'package:PiliPlus/models/space_fav/list.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/favorite/member_favorite_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/favorite/widget/item.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/favorite/widget/item.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';

View File

@@ -1,10 +1,10 @@
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/controller.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/season_series/controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/widget/season_series_card.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/season_series/widget/season_series_card.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -51,11 +51,7 @@ class _SeasonSeriesPageState extends State<SeasonSeriesPage>
bottom: MediaQuery.paddingOf(context).bottom + 80, bottom: MediaQuery.paddingOf(context).bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -87,7 +87,7 @@ class SeasonSeriesCard extends StatelessWidget {
Utils.dateFormat(item['meta']['ptime']), Utils.dateFormat(item['meta']['ptime']),
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: 12,
height: 1, height: 1,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip, overflow: TextOverflow.clip,

View File

@@ -6,10 +6,10 @@ import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/video_card_h_member_video.dart'; import 'package:PiliPlus/common/widgets/video_card_h_member_video.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_archive/item.dart'; import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/video/member_video_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -178,11 +178,7 @@ class _MemberVideoState extends State<MemberVideo>
bottom: MediaQuery.of(context).padding.bottom + 80, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (widget.type != ContributeType.season && if (widget.type != ContributeType.season &&

View File

@@ -5,7 +5,7 @@ import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/episodic_button.dart'; import 'package:PiliPlus/models/space_archive/episodic_button.dart';
import 'package:PiliPlus/models/space_archive/item.dart'; import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';

View File

@@ -1,8 +1,8 @@
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/article/member_article.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/audio/member_audio.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/audio/member_audio.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/season_series/season_series_page.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute_ctr.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';

View File

@@ -3,12 +3,12 @@ import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space/tab2.dart'; import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../../../models/space/item.dart'; import '../../../../models/space/item.dart';
class MemberContributeCtr extends CommonDataController class MemberContributeCtr extends CommonDataController
with GetTickerProviderStateMixin { with GetTickerProviderStateMixin {

View File

@@ -7,10 +7,10 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space/data.dart'; import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/item.dart'; import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart'; import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_home/widget/fav_item.dart'; import 'package:PiliPlus/pages/member/content/member_home/widget/fav_item.dart';
import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/member_coin/index.dart'; import 'package:PiliPlus/pages/member_coin/index.dart';
import 'package:PiliPlus/pages/member_like/index.dart'; import 'package:PiliPlus/pages/member_like/index.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
@@ -172,11 +172,7 @@ class _MemberHomeState extends State<MemberHome>
count: loadingState.response.article.count, count: loadingState.response.article.count,
), ),
SliverGrid( SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return MemberArticleItem( return MemberArticleItem(

View File

@@ -1,205 +1,150 @@
import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/http/member.dart'; import 'package:intl/intl.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/models/member/info.dart';
import 'package:PiliPlus/utils/storage.dart';
import '../video/detail/introduction/widgets/group_panel.dart'; enum MemberTabType { none, home, dynamic, contribute, favorite, bangumi }
class MemberController extends GetxController { extension MemberTabTypeExt on MemberTabType {
int? mid; String get title => ['默认', '首页', '动态', '投稿', '收藏', '番剧'][index];
MemberController({this.mid}); }
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
late Map userStat; class MemberControllerNew extends CommonDataController<Data, dynamic>
RxString face = ''.obs; with GetTickerProviderStateMixin {
String? heroTag; MemberControllerNew({required this.mid});
late int ownerMid; int mid;
bool specialFollowed = false; RxBool showUname = false.obs;
// 投稿列表 String? username;
dynamic userInfo; int? ownerMid;
RxInt attribute = (-1).obs; RxBool isFollow = false.obs;
RxString attributeText = '关注'.obs; RxInt relation = 1.obs;
RxList<MemberCoinsDataModel> recentCoinsList = <MemberCoinsDataModel>[].obs; TabController? tabController;
String? wwebid; late List<Tab> tabs;
List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs;
double top = 0;
bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid'];
@override @override
void onInit() async { void onInit() {
super.onInit(); super.onInit();
mid = mid ?? int.parse(Get.parameters['mid']!); ownerMid = Accounts.main.mid;
userInfo = GStorage.userInfo.get('userInfoCache'); queryData();
ownerMid = userInfo != null ? userInfo.mid : -1;
try {
face.value = Get.arguments['face'] ?? '';
heroTag = Get.arguments['heroTag'] ?? '';
} catch (_) {}
relationSearch();
} }
// 获取用户信息 dynamic live;
Future<Map<String, dynamic>> getInfo() {
return Future.wait([getMemberInfo(), getMemberStat(), getMemberView()])
.then((res) => res[0]);
}
Future<Map<String, dynamic>> getMemberInfo() async { int? silence;
wwebid ??= await Utils.getWwebid(mid); String? endTime;
await getMemberStat();
await getMemberView();
var res = await MemberHttp.memberInfo(mid: mid, wwebid: wwebid);
if (res['status']) {
memberInfo.value = res['data'];
face.value = res['data'].face;
}
return res;
}
// 获取用户状态 late final implTabs = const [
Future<Map<String, dynamic>> getMemberStat() async { 'home',
var res = await MemberHttp.memberStat(mid: mid); 'dynamic',
if (res['status']) { 'contribute',
userStat = res['data']; 'favorite',
} 'bangumi',
return res; ];
}
// 获取用户播放数 获赞数 @override
Future<Map<String, dynamic>> getMemberView() async { bool customHandleResponse(bool isRefresh, Success<Data> response) {
var res = await MemberHttp.memberView(mid: mid!); Data data = response.response;
if (res['status']) { username = data.card?.name ?? '';
userStat.addAll(res['data']); isFollow.value = data.card?.relation?.isFollow == 1;
relation.value = data.relSpecial == 1 ? 2 : data.relation ?? 1;
tab2 = data.tab2;
live = data.live;
silence = data.card?.silence;
if ((data.ugcSeason?.count != null && data.ugcSeason?.count != 0) ||
data.series?.item?.isNotEmpty == true) {
hasSeasonOrSeries = true;
} }
return res; if (data.card?.endTime != null) {
} if (data.card!.endTime == 0) {
endTime = ': 永久封禁';
Future delayedUpdateRelation() async { } else if (data.card!.endTime! >
await Future.delayed(const Duration(milliseconds: 1000), () async { DateTime.now().millisecondsSinceEpoch ~/ 1000) {
SmartDialog.showToast('更新状态'); endTime =
await relationSearch(); ':至 ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(data.card!.endTime! * 1000))}';
memberInfo.update((val) {}); }
});
}
// 关注/取关up
Future actionRelationMod(BuildContext context) async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
} }
if (memberInfo.value.mid == null) { tab2?.retainWhere((item) => implTabs.contains(item.param));
SmartDialog.showToast('尚未获取到用户信息'); if (tab2?.isNotEmpty == true) {
return; if (!data.tab!.toJson().values.contains(true) &&
} tab2!.first.param == 'home') {
if (attribute.value == 128) { // remove empty home tab
blockUser(context); tab2!.removeAt(0);
return; }
} if (tab2!.isNotEmpty) {
await showDialog( int initialIndex = -1;
context: context, MemberTabType memberTab = GStorage.memberTab;
builder: (context) { if (memberTab != MemberTabType.none) {
return AlertDialog( initialIndex = tab2!.indexWhere((item) {
title: const Text('操作'), return item.param == memberTab.name;
actions: [ });
if (memberInfo.value.isFollowed!) ...[ }
TextButton( if (initialIndex == -1) {
onPressed: () async { if (data.defaultTab == 'video') {
final res = await MemberHttp.addUsers( data.defaultTab = 'contribute';
mid, specialFollowed ? '0' : '-10'); }
SmartDialog.showToast(res['msg']); initialIndex = tab2!.indexWhere((item) {
if (res['status']) { return item.param == data.defaultTab;
specialFollowed = !specialFollowed; });
} }
Get.back(); tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();
}, tabController = TabController(
child: Text(specialFollowed ? '移除特别关注' : '加入特别关注'), vsync: this,
), length: tabs.length,
TextButton( initialIndex: max(0, initialIndex),
onPressed: () async {
await Get.bottomSheet(
GroupPanel(mid: mid),
isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
);
Get.back();
},
child: const Text('设置分组'),
),
],
TextButton(
onPressed: () async {
var res = await VideoHttp.relationMod(
mid: mid!,
act: memberInfo.value.isFollowed! ? 2 : 1,
reSrc: 11,
);
SmartDialog.showToast(res['status'] ? "操作成功" : res['msg']);
if (res['status']) {
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
}
Get.back();
},
child: Text(memberInfo.value.isFollowed! ? '取消关注' : '关注'),
),
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
],
); );
},
);
await delayedUpdateRelation();
}
// 关系查询
Future relationSearch() async {
if (userInfo == null) return;
if (mid == ownerMid) return;
var res = await UserHttp.hasFollow(mid!);
if (res['status']) {
attribute.value = res['data']['attribute'];
switch (attribute.value) {
case 1:
attributeText.value = '悄悄关注';
memberInfo.value.isFollowed = true;
break;
case 2:
attributeText.value = '已关注';
memberInfo.value.isFollowed = true;
break;
case 6:
attributeText.value = '已互关';
memberInfo.value.isFollowed = true;
break;
case 128:
attributeText.value = '已拉黑';
memberInfo.value.isFollowed = false;
break;
default:
attributeText.value = '关注';
memberInfo.value.isFollowed = false;
} }
if (res['data']['special'] == 1) {
specialFollowed = true;
attributeText.value += ' 🔔';
} else {
specialFollowed = false;
}
} else {
SmartDialog.showToast(res['msg']);
} }
loadingState.value = response;
return true;
} }
// 拉黑用户 @override
bool handleError(String? errMsg) {
tab2 = [
Tab2(title: '动态', param: 'dynamic'),
Tab2(
title: '投稿',
param: 'contribute',
items: [Item(title: '视频', param: 'video')],
),
Tab2(title: '收藏', param: 'favorite'),
Tab2(title: '追番', param: 'bangumi'),
];
tabs = tab2!.map((item) => Tab(text: item.title)).toList();
tabController = TabController(
vsync: this,
length: tabs.length,
);
showUname.value = true;
username = errMsg;
loadingState.value = LoadingState.success(null);
return true;
}
@override
Future<LoadingState<Data>> customGetData() => MemberHttp.space(
mid: mid,
fromViewAid: fromViewAid,
);
Future blockUser(BuildContext context) async { Future blockUser(BuildContext context) async {
if (userInfo == null) { if (ownerMid == 0) {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
@@ -208,10 +153,10 @@ class MemberController extends GetxController {
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'), content: Text(relation.value != -1 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: Get.back,
child: Text( child: Text(
'点错了', '点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -220,18 +165,7 @@ class MemberController extends GetxController {
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Get.back(); Get.back();
var res = await VideoHttp.relationMod( _onBlock();
mid: mid!,
act: attribute.value != 128 ? 5 : 6,
reSrc: 11,
);
if (res['status']) {
attribute.value = attribute.value != 128 ? 128 : 0;
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
memberInfo.value.isFollowed = false;
relationSearch();
memberInfo.update((val) {});
}
}, },
child: const Text('确认'), child: const Text('确认'),
) )
@@ -242,41 +176,46 @@ class MemberController extends GetxController {
} }
void shareUser() { void shareUser() {
Utils.shareText( Utils.shareText('https://space.bilibili.com/$mid');
'${memberInfo.value.name} - https://space.bilibili.com/$mid');
} }
// 请求专栏 void _onBlock() async {
Future getMemberSeasons() async { dynamic res = await VideoHttp.relationMod(
if (userInfo == null) return; mid: mid,
var res = await MemberHttp.getMemberSeasons(mid, 1, 10); act: relation.value != -1 ? 5 : 6,
if (!res['status']) { reSrc: 11,
SmartDialog.showToast("用户专栏请求异常:${res['msg']}"); );
if (res['status']) {
relation.value = relation.value != -1 ? -1 : 1;
isFollow.value = false;
} }
return res;
} }
// 请求投币视频 void onFollow(BuildContext context) async {
Future getRecentCoinVideo() async { if (mid == ownerMid) {
// if (userInfo == null) return; Get.toNamed('/editProfile');
// var res = await MemberHttp.getRecentCoinVideo(mid: mid!); } else if (relation.value == -1) {
// recentCoinsList.value = res['data']; _onBlock();
// return res; } else {
if (ownerMid == null) {
SmartDialog.showToast('账号未登录');
return;
}
Utils.actionRelationMod(
context: context,
mid: mid,
isFollow: isFollow.value,
callback: (attribute) {
relation.value = attribute;
isFollow.value = attribute != 0;
},
);
}
} }
// 跳转查看动态 @override
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid'); void onClose() {
tabController?.dispose();
// 跳转查看投稿 super.onClose();
void pushArchivesPage() async {
wwebid ??= await Utils.getWwebid(mid);
Get.toNamed('/memberArchive?mid=$mid&wwebid=$wwebid');
}
// 跳转查看专栏
void pushSeasonsPage() {}
// 跳转查看最近投币
void pushRecentCoinsPage() async {
if (recentCoinsList.isNotEmpty) {}
} }
} }

View File

@@ -1,4 +0,0 @@
library member;
export './controller.dart';
export './view.dart';

View File

@@ -1,19 +1,21 @@
import 'package:PiliPlus/common/widgets/dynamic_sliver_appbar.dart'; import 'package:PiliPlus/common/widgets/dynamic_sliver_appbar.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space/data.dart'; import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/bangumi/member_bangumi.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/bangumi/member_bangumi.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/content/favorite/member_favorite.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'; import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/pages/member/new/content/member_home/member_home.dart'; import 'package:PiliPlus/pages/member/content/member_home/member_home.dart';
import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/member/new/widget/user_info_card.dart'; import 'package:PiliPlus/pages/member/widget/user_info_card.dart';
import 'package:PiliPlus/pages/member/view.dart';
import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_dynamics/view.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart';
@@ -298,3 +300,124 @@ class _MemberPageNewState extends State<MemberPageNew> {
}; };
} }
} }
class ReportPanel extends StatefulWidget {
const ReportPanel({
super.key,
required this.name,
required this.mid,
});
final dynamic name;
final dynamic mid;
@override
State<ReportPanel> createState() => _ReportPanelState();
}
class _ReportPanelState extends State<ReportPanel> {
final List<bool> _reasonList = List.generate(3, (_) => false).toList();
final Set<int> _reason = {};
int? _reasonV2;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'举报: ${widget.name}',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 4),
Text('uid: ${widget.mid}'),
const SizedBox(height: 10),
const Text('举报内容(必选,可多选)'),
...List.generate(
3,
(index) => _checkBoxWidget(
_reasonList[index],
(value) {
setState(() => _reasonList[index] = value);
if (value) {
_reason.add(index + 1);
} else {
_reason.remove(index + 1);
}
},
['头像违规', '昵称违规', '签名违规'][index],
),
),
const Text('举报理由(单选,非必选)'),
...List.generate(
5,
(index) => RadioWidget<int>(
value: index,
groupValue: _reasonV2,
onChanged: (value) {
setState(() => _reasonV2 = value);
},
title: const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
if (_reason.isEmpty) {
SmartDialog.showToast('至少选择一项作为举报内容');
} else {
Get.back();
dynamic result = await MemberHttp.reportMember(
widget.mid,
reason: _reason.join(','),
reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null,
);
if (result['msg'] is String && result['msg'].isNotEmpty) {
SmartDialog.showToast(result['msg']);
} else {
SmartDialog.showToast('举报失败');
}
}
},
child: const Text('确定'),
),
],
),
],
),
);
}
}
Widget _checkBoxWidget(
bool defValue,
ValueChanged onChanged,
String title,
) {
return InkWell(
onTap: () => onChanged(!defValue),
child: Row(
children: [
Checkbox(
value: defValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}

View File

@@ -1,221 +0,0 @@
import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
enum MemberTabType { none, home, dynamic, contribute, favorite, bangumi }
extension MemberTabTypeExt on MemberTabType {
String get title => ['默认', '首页', '动态', '投稿', '收藏', '番剧'][index];
}
class MemberControllerNew extends CommonDataController<Data, dynamic>
with GetTickerProviderStateMixin {
MemberControllerNew({required this.mid});
int mid;
RxBool showUname = false.obs;
String? username;
int? ownerMid;
RxBool isFollow = false.obs;
RxInt relation = 1.obs;
TabController? tabController;
late List<Tab> tabs;
List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs;
double top = 0;
bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid'];
@override
void onInit() {
super.onInit();
ownerMid = Accounts.main.mid;
queryData();
}
dynamic live;
int? silence;
String? endTime;
late final implTabs = const [
'home',
'dynamic',
'contribute',
'favorite',
'bangumi',
];
@override
bool customHandleResponse(bool isRefresh, Success<Data> response) {
Data data = response.response;
username = data.card?.name ?? '';
isFollow.value = data.card?.relation?.isFollow == 1;
relation.value = data.relSpecial == 1 ? 2 : data.relation ?? 1;
tab2 = data.tab2;
live = data.live;
silence = data.card?.silence;
if ((data.ugcSeason?.count != null && data.ugcSeason?.count != 0) ||
data.series?.item?.isNotEmpty == true) {
hasSeasonOrSeries = true;
}
if (data.card?.endTime != null) {
if (data.card!.endTime == 0) {
endTime = ': 永久封禁';
} else if (data.card!.endTime! >
DateTime.now().millisecondsSinceEpoch ~/ 1000) {
endTime =
':至 ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(data.card!.endTime! * 1000))}';
}
}
tab2?.retainWhere((item) => implTabs.contains(item.param));
if (tab2?.isNotEmpty == true) {
if (!data.tab!.toJson().values.contains(true) &&
tab2!.first.param == 'home') {
// remove empty home tab
tab2!.removeAt(0);
}
if (tab2!.isNotEmpty) {
int initialIndex = -1;
MemberTabType memberTab = GStorage.memberTab;
if (memberTab != MemberTabType.none) {
initialIndex = tab2!.indexWhere((item) {
return item.param == memberTab.name;
});
}
if (initialIndex == -1) {
if (data.defaultTab == 'video') {
data.defaultTab = 'contribute';
}
initialIndex = tab2!.indexWhere((item) {
return item.param == data.defaultTab;
});
}
tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();
tabController = TabController(
vsync: this,
length: tabs.length,
initialIndex: max(0, initialIndex),
);
}
}
loadingState.value = response;
return true;
}
@override
bool handleError(String? errMsg) {
tab2 = [
Tab2(title: '动态', param: 'dynamic'),
Tab2(
title: '投稿',
param: 'contribute',
items: [Item(title: '视频', param: 'video')],
),
Tab2(title: '收藏', param: 'favorite'),
Tab2(title: '追番', param: 'bangumi'),
];
tabs = tab2!.map((item) => Tab(text: item.title)).toList();
tabController = TabController(
vsync: this,
length: tabs.length,
);
showUname.value = true;
username = errMsg;
loadingState.value = LoadingState.success(null);
return true;
}
@override
Future<LoadingState<Data>> customGetData() => MemberHttp.space(
mid: mid,
fromViewAid: fromViewAid,
);
Future blockUser(BuildContext context) async {
if (ownerMid == 0) {
SmartDialog.showToast('账号未登录');
return;
}
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: Text(relation.value != -1 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
Get.back();
_onBlock();
},
child: const Text('确认'),
)
],
);
},
);
}
void shareUser() {
Utils.shareText('https://space.bilibili.com/$mid');
}
void _onBlock() async {
dynamic res = await VideoHttp.relationMod(
mid: mid,
act: relation.value != -1 ? 5 : 6,
reSrc: 11,
);
if (res['status']) {
relation.value = relation.value != -1 ? -1 : 1;
isFollow.value = false;
}
}
void onFollow(BuildContext context) async {
if (mid == ownerMid) {
Get.toNamed('/editProfile');
} else if (relation.value == -1) {
_onBlock();
} else {
if (ownerMid == null) {
SmartDialog.showToast('账号未登录');
return;
}
Utils.actionRelationMod(
context: context,
mid: mid,
isFollow: isFollow.value,
callback: (attribute) {
relation.value = attribute;
isFollow.value = attribute != 0;
},
);
}
}
@override
void onClose() {
tabController?.dispose();
super.onClose();
}
}

View File

@@ -1,652 +0,0 @@
import 'dart:async';
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/pages/member/index.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'widgets/conis.dart';
import 'widgets/profile.dart';
import 'widgets/seasons.dart';
@Deprecated('Use MemberPageNew instead')
class MemberPage extends StatefulWidget {
const MemberPage({super.key});
@override
State<MemberPage> createState() => _MemberPageState();
}
class _MemberPageState extends State<MemberPage> {
late String heroTag;
late MemberController _memberController;
late Future _futureBuilderFuture;
late Future _memberSeasonsFuture;
late Future _memberCoinsFuture;
final ScrollController _extendNestCtr = ScrollController();
final StreamController<bool> appbarStream = StreamController<bool>();
late int mid;
@override
void initState() {
super.initState();
mid = int.parse(Get.parameters['mid']!);
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
_memberController = Get.put(MemberController(), tag: heroTag);
_futureBuilderFuture = _memberController.getInfo();
_memberSeasonsFuture = _memberController.getMemberSeasons();
_memberCoinsFuture = _memberController.getRecentCoinVideo();
_extendNestCtr.addListener(listener);
}
void listener() {
final double offset = _extendNestCtr.position.pixels;
if (offset > 100) {
appbarStream.add(true);
} else {
appbarStream.add(false);
}
}
@override
void dispose() {
_extendNestCtr.removeListener(listener);
_extendNestCtr.dispose();
appbarStream.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
bool isHorizontal = context.width > context.height;
return Scaffold(
primary: true,
body: Column(
children: [
AppBar(
title: StreamBuilder(
stream: appbarStream.stream.distinct(),
initialData: false,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return AnimatedOpacity(
opacity: snapshot.data ? 1 : 0,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 500),
child: Row(
children: [
Row(
children: [
Obx(
() => NetworkImgLayer(
width: 35,
height: 35,
type: 'avatar',
src: _memberController.face.value,
),
),
const SizedBox(width: 10),
Obx(
() => Text(
_memberController.memberInfo.value.name ?? '',
style: TextStyle(
color:
Theme.of(context).colorScheme.onSurface,
fontSize: 14),
),
),
],
)
],
),
);
},
),
actions: [
IconButton(
tooltip: '搜索',
onPressed: () => Get.toNamed(
'/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'),
icon: const Icon(Icons.search_outlined),
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_memberController.ownerMid != _memberController.mid) ...[
PopupMenuItem(
onTap: () => _memberController.blockUser(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.block, size: 19),
const SizedBox(width: 10),
Text(_memberController.attribute.value != 128
? '加入黑名单'
: '移除黑名单'),
],
),
)
],
PopupMenuItem(
onTap: () => _memberController.shareUser(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.share_outlined, size: 19),
const SizedBox(width: 10),
Text(_memberController.ownerMid != _memberController.mid
? '分享UP主'
: '分享我的主页'),
],
),
),
if (_memberController.userInfo != null) ...[
const PopupMenuDivider(),
PopupMenuItem(
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
content: ReportPanel(
name: _memberController.memberInfo.value.name,
mid: mid,
),
),
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 19,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(width: 10),
Text(
'举报',
style: TextStyle(
color: Theme.of(context).colorScheme.error),
),
],
),
),
],
],
),
const SizedBox(width: 4),
],
),
Expanded(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
controller: _extendNestCtr,
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 20,
),
child: Column(
children: [
profileWidget(isHorizontal),
Row(children: [
const Spacer(),
InkWell(
onTap: _memberController.pushDynamicsPage,
child: const Row(
children: [
Text('Ta的动态', style: TextStyle(height: 2)),
SizedBox(width: 5),
Icon(Icons.arrow_forward_ios, size: 19),
],
),
),
const Spacer(),
InkWell(
onTap: _memberController.pushArchivesPage,
child: const Row(
children: [
Text('Ta的投稿', style: TextStyle(height: 2)),
SizedBox(width: 5),
Icon(Icons.arrow_forward_ios, size: 19),
],
),
),
const Spacer(),
InkWell(
onTap: () {},
child: const Row(
children: [
Text('Ta的专栏', style: TextStyle(height: 2)),
SizedBox(width: 5),
],
),
),
const Spacer(),
]),
MediaQuery.removePadding(
removeTop: true,
removeBottom: true,
context: context,
child: Padding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
),
child: FutureBuilder(
future: _memberSeasonsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
Map data = snapshot.data as Map;
if (data['data'].seasonsList.isEmpty) {
return commonWidget('用户没有设置专栏');
} else {
return MemberSeasonsPanel(data: data['data']);
}
} else {
// 请求错误
return const SizedBox();
}
} else {
return const SizedBox();
}
},
),
),
),
/// 收藏
/// 追番
/// 最近投币
Obx(
() => _memberController.recentCoinsList.isNotEmpty
? ListTile(
onTap: () {},
title: const Text('最近投币的视频'),
// trailing: const Icon(Icons.arrow_forward_outlined,
// size: 19),
)
: const SizedBox(),
),
MediaQuery.removePadding(
removeTop: true,
removeBottom: true,
context: context,
child: Padding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
),
child: FutureBuilder(
future: _memberCoinsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
Map data = snapshot.data as Map;
return MemberCoinsPanel(data: data['data']);
} else {
// 请求错误
return const SizedBox();
}
} else {
return const SizedBox();
}
},
),
),
),
// 最近点赞
// ListTile(
// onTap: () {},
// title: const Text('最近点赞的视频'),
// trailing:
// const Icon(Icons.arrow_forward_outlined, size: 19),
// ),
],
),
),
),
),
],
),
);
}
Widget profileWidget(bool isHorizontal) {
return Padding(
padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
Map data = snapshot.data!;
if (data['status']) {
return Obx(
() => Stack(
alignment: AlignmentDirectional.center,
children: [profilePanelAndDetailInfo(isHorizontal, false)]),
);
} else {
return const SizedBox();
}
} else {
// 骨架屏
return profilePanelAndDetailInfo(isHorizontal, true);
}
},
),
);
}
Widget profilePanelAndDetailInfo(bool isHorizontal, bool loadingStatus) {
if (isHorizontal) {
return Row(
children: [
Expanded(
child: ProfilePanel(
ctr: _memberController, loadingStatus: loadingStatus)),
const SizedBox(width: 20),
Expanded(child: profileDetailInfo()),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePanel(ctr: _memberController, loadingStatus: loadingStatus),
const SizedBox(height: 20),
profileDetailInfo(),
],
);
}
Widget profileDetailInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
_memberController.memberInfo.value.name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.bold,
color: _memberController.memberInfo.value.vip?.status !=
null &&
_memberController.memberInfo.value.vip!.status! >
0 &&
_memberController.memberInfo.value.vip!.type == 2
? context.vipColor
: Theme.of(context).colorScheme.onSurface,
),
)),
const SizedBox(width: 2),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.venus,
size: 14,
color: Colors.pink,
semanticLabel: '',
),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.mars,
size: 14,
color: Colors.blue,
semanticLabel: '',
),
const SizedBox(width: 4),
if (_memberController.memberInfo.value.level != null)
Image.asset(
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
height: 11,
semanticLabel: '等级${_memberController.memberInfo.value.level}',
),
const SizedBox(width: 6),
if (_memberController.memberInfo.value.vip?.status == 1) ...[
if (_memberController
.memberInfo.value.vip?.label?['img_label_uri_hans'] !=
'')
CachedNetworkImage(
imageUrl: (_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans'] as String)
.http2https,
height: 20,
// semanticLabel:
// _memberController.memberInfo.value.vip!.label!['text'],
)
else if (_memberController.memberInfo.value.vip
?.label?['img_label_uri_hans_static'] !=
'')
CachedNetworkImage(
imageUrl: (_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans_static'] as String)
.http2https,
height: 20,
// semanticLabel:
// _memberController.memberInfo.value.vip!.label!['text'],
),
],
const SizedBox(width: 5),
GestureDetector(
onTap: () {
Utils.copyText(_memberController.mid.toString());
},
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 2.5),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Text(
'UID: ${_memberController.mid}',
style: TextStyle(
height: 1,
fontSize: 12,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
strutStyle: const StrutStyle(
height: 1,
leading: 0,
fontSize: 12,
),
),
),
),
],
),
if (_memberController.memberInfo.value.official != null &&
_memberController.memberInfo.value.official!['title'] != '') ...[
const SizedBox(height: 6),
Text.rich(
maxLines: 2,
TextSpan(
text: _memberController.memberInfo.value.official!['role'] == 1
? '个人认证:'
: '机构认证:',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
children: [
TextSpan(
text: _memberController.memberInfo.value.official!['title'],
),
],
),
softWrap: true,
),
],
const SizedBox(height: 6),
SelectableText(
_memberController.memberInfo.value.sign ?? '',
),
],
);
}
Widget commonWidget(msg) {
return Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 30,
),
child: Center(
child: Text(
msg,
style: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Theme.of(context).colorScheme.outline),
),
),
);
}
}
class ReportPanel extends StatefulWidget {
const ReportPanel({
super.key,
required this.name,
required this.mid,
});
final dynamic name;
final dynamic mid;
@override
State<ReportPanel> createState() => _ReportPanelState();
}
class _ReportPanelState extends State<ReportPanel> {
final List<bool> _reasonList = List.generate(3, (_) => false).toList();
final Set<int> _reason = {};
int? _reasonV2;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'举报: ${widget.name}',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 4),
Text('uid: ${widget.mid}'),
const SizedBox(height: 10),
const Text('举报内容(必选,可多选)'),
...List.generate(
3,
(index) => _checkBoxWidget(
_reasonList[index],
(value) {
setState(() => _reasonList[index] = value);
if (value) {
_reason.add(index + 1);
} else {
_reason.remove(index + 1);
}
},
['头像违规', '昵称违规', '签名违规'][index],
),
),
const Text('举报理由(单选,非必选)'),
...List.generate(
5,
(index) => RadioWidget<int>(
value: index,
groupValue: _reasonV2,
onChanged: (value) {
setState(() => _reasonV2 = value);
},
title: const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
if (_reason.isEmpty) {
SmartDialog.showToast('至少选择一项作为举报内容');
} else {
Get.back();
dynamic result = await MemberHttp.reportMember(
widget.mid,
reason: _reason.join(','),
reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null,
);
if (result['msg'] is String && result['msg'].isNotEmpty) {
SmartDialog.showToast(result['msg']);
} else {
SmartDialog.showToast('举报失败');
}
}
},
child: const Text('确定'),
),
],
),
],
),
);
}
}
Widget _checkBoxWidget(
bool defValue,
ValueChanged onChanged,
String title,
) {
return InkWell(
onTap: () => onChanged(!defValue),
child: Row(
children: [
Checkbox(
value: defValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}

View File

@@ -1,31 +0,0 @@
import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/member_coin/widgets/item.dart';
class MemberCoinsPanel extends StatelessWidget {
final List<MemberCoinsDataModel>? data;
const MemberCoinsPanel({super.key, this.data});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, boxConstraints) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // Use a fixed count for GridView
crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.safeSpace,
childAspectRatio: 0.94,
),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: data!.length,
itemBuilder: (context, i) {
return MemberCoinsItem(coinItem: data![i]);
},
);
},
);
}
}

View File

@@ -1,272 +0,0 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/member/info.dart';
import 'package:PiliPlus/utils/utils.dart';
class ProfilePanel extends StatelessWidget {
final dynamic ctr;
final bool loadingStatus;
const ProfilePanel({
super.key,
required this.ctr,
this.loadingStatus = false,
});
@override
Widget build(BuildContext context) {
MemberInfoModel memberInfo = ctr.memberInfo.value;
return Builder(
builder: ((context) {
return Row(
children: [
Hero(
tag: !loadingStatus ? memberInfo.face : ctr.face.value,
child: Stack(
children: [
GestureDetector(
onTap: () => context.imageView(
imgList: [
SourceModel(
url:
!loadingStatus ? memberInfo.face : ctr.face.value,
)
],
),
child: NetworkImgLayer(
width: 90,
height: 90,
type: 'avatar',
src: !loadingStatus ? memberInfo.face : ctr.face.value,
),
),
if (!loadingStatus &&
memberInfo.liveRoom != null &&
memberInfo.liveRoom!.liveStatus == 1)
Positioned(
bottom: 0,
left: 14,
child: GestureDetector(
onTap: () {
// LiveItemModel liveItem = LiveItemModel.fromJson({
// 'title': memberInfo.liveRoom!.title,
// 'uname': memberInfo.name,
// 'face': memberInfo.face,
// 'roomid': memberInfo.liveRoom!.roomId,
// 'watched_show': memberInfo.liveRoom!.watchedShow,
// });
Get.toNamed(
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
);
},
child: Container(
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius:
const BorderRadius.all(Radius.circular(10)),
),
child: Row(children: [
Image.asset(
'assets/images/live.gif',
height: 10,
),
Text(
' 直播中',
style: TextStyle(
color: Colors.white,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize),
)
]),
),
),
)
],
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding:
const EdgeInsets.only(top: 10, left: 10, right: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Get.toNamed(
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['following'].toString()
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold),
),
Text(
'关注',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
),
InkWell(
onTap: () {
Get.toNamed(
'/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['follower'] != null
? Utils.numFormat(
ctr.userStat!['follower'],
)
: '-'
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold)),
Text(
'粉丝',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
),
InkWell(
onTap: null,
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['likes'] != null
? Utils.numFormat(
ctr.userStat!['likes'],
)
: '-'
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold)),
Text(
'获赞',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
)),
],
),
),
const SizedBox(height: 10),
if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[
Row(
children: [
Obx(
() => Expanded(
child: TextButton(
onPressed: () => ctr.actionRelationMod(context),
style: TextButton.styleFrom(
foregroundColor: ctr.attribute.value == -1
? Colors.transparent
: ctr.attribute.value != 0
? Theme.of(context).colorScheme.outline
: Theme.of(context)
.colorScheme
.onPrimary,
backgroundColor: ctr.attribute.value != 0
? Theme.of(context)
.colorScheme
.onInverseSurface
: Theme.of(context).colorScheme.primary,
),
child: Obx(() => Text(ctr.attributeText.value)),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextButton(
onPressed: () {
Get.toNamed(
'/whisperDetail',
parameters: {
'talkerId': ctr.mid.toString(),
'name': memberInfo.name!,
'face': memberInfo.face!,
'mid': ctr.mid.toString(),
},
);
},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context)
.colorScheme
.onInverseSurface,
),
child: const Text('发消息'),
),
)
],
)
],
if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[
TextButton(
onPressed: () {
Get.toNamed('/webview', parameters: {
'url': 'https://account.bilibili.com/account/home',
'pageTitle': '个人中心(建议浏览器打开)',
'type': 'url'
});
},
style: TextButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
child: const Text(' 个人中心(web) '),
)
],
if (ctr.ownerMid == -1) ...[
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.outline,
backgroundColor:
Theme.of(context).colorScheme.onInverseSurface,
),
child: const Text(' 未登录 '),
)
]
],
),
),
],
);
}),
);
}
}

View File

@@ -1,86 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/models/member/seasons.dart';
import 'package:PiliPlus/pages/member_seasons/widgets/item.dart';
import '../../../utils/grid.dart';
class MemberSeasonsPanel extends StatelessWidget {
final MemberSeasonsDataModel? data;
const MemberSeasonsPanel({super.key, this.data});
@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: data!.seasonsList!.length,
itemBuilder: (context, index) {
MemberSeasonsList item = data!.seasonsList![index];
return Padding(
padding: const EdgeInsets.only(bottom: 12, right: 4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 12, left: 4),
child: Row(
children: [
Text(
item.meta!.name!,
maxLines: 1,
style: Theme.of(context).textTheme.titleSmall!,
),
const SizedBox(width: 10),
PBadge(
stack: 'relative',
size: 'small',
text: item.meta!.total.toString(),
),
SizedBox(
width: 30,
height: 30,
child: IconButton(
tooltip: '前往',
onPressed: () => Get.toNamed(
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
icon: const Icon(
Icons.arrow_forward_ios,
size: 20,
),
),
)
],
),
),
LayoutBuilder(
builder: (context, boxConstraints) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace,
maxCrossAxisExtent: Grid.smallCardWidth,
childAspectRatio: 0.94,
),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: item.archives!.length,
itemBuilder: (context, i) {
return MemberSeasonsItem(seasonItem: item.archives![i]);
},
);
},
),
],
),
);
},
);
}
}

View File

@@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/member/archive.dart';
class MemberArchiveController extends GetxController {
final ScrollController scrollController = ScrollController();
late int mid;
late String wwebid;
int pn = 1;
int count = 0;
RxMap<String, String> currentOrder = <String, String>{}.obs;
static const List<Map<String, String>> orderList = [
{'type': 'pubdate', 'label': '最新发布'},
{'type': 'click', 'label': '最多播放'},
{'type': 'stow', 'label': '最多收藏'},
];
RxList<VListItemModel> archivesList = <VListItemModel>[].obs;
@override
void onInit() {
super.onInit();
mid = int.parse(Get.parameters['mid']!);
wwebid = Get.parameters['wwebid']!;
currentOrder.value = orderList.first;
}
// 获取用户投稿
Future getMemberArchive(type) async {
if (type == 'init') {
pn = 1;
}
var res = await MemberHttp.memberArchive(
mid: mid,
pn: pn,
order: currentOrder['type']!,
wwebid: wwebid,
);
if (res['status']) {
if (type == 'init') {
archivesList.value = res['data'].list.vlist;
} else if (type == 'onLoad') {
archivesList.addAll(res['data'].list.vlist);
}
count = res['data'].page['count'];
pn += 1;
} else {
SmartDialog.showToast(res['msg']);
}
return res;
}
toggleSort() {
List<String> typeList = orderList.map((e) => e['type']!).toList();
int index = typeList.indexOf(currentOrder['type']!);
if (index == orderList.length - 1) {
currentOrder.value = orderList.first;
} else {
currentOrder.value = orderList[index + 1];
}
getMemberArchive('init');
}
// 上拉加载
Future onLoad() => getMemberArchive('onLoad');
@override
void onClose() {
scrollController.dispose();
super.onClose();
}
}

View File

@@ -1,4 +0,0 @@
library member_archive;
export './controller.dart';
export './view.dart';

View File

@@ -1,137 +0,0 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/video_card_h.dart';
import 'package:PiliPlus/utils/utils.dart';
import '../../common/constants.dart';
import '../../common/widgets/http_error.dart';
import '../../utils/grid.dart';
import 'controller.dart';
class MemberArchivePage extends StatefulWidget {
const MemberArchivePage({super.key});
@override
State<MemberArchivePage> createState() => _MemberArchivePageState();
}
class _MemberArchivePageState extends State<MemberArchivePage> {
late MemberArchiveController _memberArchivesController;
late Future _futureBuilderFuture;
late int mid;
@override
void dispose() {
_memberArchivesController.scrollController.removeListener(listener);
super.dispose();
}
@override
void initState() {
super.initState();
mid = int.parse(Get.parameters['mid']!);
final String heroTag = Utils.makeHeroTag(mid);
_memberArchivesController =
Get.put(MemberArchiveController(), tag: heroTag);
_futureBuilderFuture = _memberArchivesController.getMemberArchive('init');
_memberArchivesController.scrollController.addListener(listener);
}
void listener() {
if (_memberArchivesController.scrollController.position.pixels >=
_memberArchivesController.scrollController.position.maxScrollExtent -
200) {
EasyThrottle.throttle(
'member_archives', const Duration(milliseconds: 500), () {
_memberArchivesController.onLoad();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Ta的投稿'),
actions: [
Obx(
() => TextButton.icon(
icon: const Icon(Icons.sort, size: 20),
onPressed: _memberArchivesController.toggleSort,
label: Text(_memberArchivesController.currentOrder['label']!),
),
),
const SizedBox(width: 6),
],
),
body: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _memberArchivesController.scrollController,
slivers: [
SliverPadding(
padding:
const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data != null) {
// TODO: refactor
if (snapshot.data is! Map) {
return HttpError(
callback: () => setState(() {
_futureBuilderFuture = _memberArchivesController
.getMemberArchive('init');
}),
);
}
Map data = snapshot.data as Map;
final list = _memberArchivesController.archivesList;
if (data['status']) {
return Obx(
() => list.isNotEmpty
? SliverGrid(
gridDelegate:
SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio:
StyleString.aspectRatio * 2.4,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, index) {
return VideoCardH(
videoItem: list[index],
showOwner: false,
showPubdate: true,
);
},
childCount: list.length,
),
)
: const SliverToBoxAdapter(),
);
} else {
return HttpError(
errMsg: snapshot.data['msg'],
callback: () {},
);
}
} else {
return HttpError(
errMsg: "投稿页出现错误",
callback: () {},
);
}
} else {
return const SliverToBoxAdapter();
}
},
),
)
],
),
);
}
}

View File

@@ -22,13 +22,15 @@ class MemberSearchController extends GetxController
int archivePn = 1; int archivePn = 1;
RxInt archiveCount = (-1).obs; RxInt archiveCount = (-1).obs;
bool isEndArchive = false; bool isEndArchive = false;
Rx<LoadingState> archiveState = LoadingState.loading().obs; Rx<LoadingState<List<VListItemModel>?>> archiveState =
LoadingState<List<VListItemModel>?>.loading().obs;
String offset = ''; String offset = '';
int dynamicPn = 1; int dynamicPn = 1;
RxInt dynamicCount = (-1).obs; RxInt dynamicCount = (-1).obs;
bool isEndDynamic = false; bool isEndDynamic = false;
Rx<LoadingState> dynamicState = LoadingState.loading().obs; Rx<LoadingState<List<DynamicItemModel>?>> dynamicState =
LoadingState<List<DynamicItemModel>?>.loading().obs;
dynamic wwebid; dynamic wwebid;

View File

@@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card_h.dart'; import 'package:PiliPlus/common/widgets/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/member/archive.dart';
import 'package:PiliPlus/pages/member_search/controller.dart'; import 'package:PiliPlus/pages/member_search/controller.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
@@ -29,10 +30,11 @@ class _SearchArchiveState extends State<SearchArchive>
return Obx(() => _buildBody(context, widget.ctr.archiveState.value)); return Obx(() => _buildBody(context, widget.ctr.archiveState.value));
} }
Widget _buildBody(BuildContext context, LoadingState loadingState) { Widget _buildBody(
BuildContext context, LoadingState<List<VListItemModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator( ? refreshIndicator(
onRefresh: () async { onRefresh: () async {
await widget.ctr.refreshArchive(); await widget.ctr.refreshArchive();
@@ -46,24 +48,20 @@ class _SearchArchiveState extends State<SearchArchive>
bottom: MediaQuery.paddingOf(context).bottom + 80, bottom: MediaQuery.paddingOf(context).bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
EasyThrottle.throttle('searchArchives', EasyThrottle.throttle('searchArchives',
const Duration(milliseconds: 500), () { const Duration(milliseconds: 500), () {
widget.ctr.searchArchives(false); widget.ctr.searchArchives(false);
}); });
} }
return VideoCardH( return VideoCardH(
videoItem: loadingState.response[index], videoItem: loadingState.response![index],
); );
}, },
childCount: loadingState.response.length, childCount: loadingState.response!.length,
), ),
), ),
), ),

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
import 'package:PiliPlus/pages/member_search/controller.dart'; import 'package:PiliPlus/pages/member_search/controller.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
@@ -31,12 +32,13 @@ class _SearchDynamicState extends State<SearchDynamic>
return Obx(() => _buildBody(context, widget.ctr.dynamicState.value)); return Obx(() => _buildBody(context, widget.ctr.dynamicState.value));
} }
Widget _buildBody(BuildContext context, LoadingState loadingState) { Widget _buildBody(BuildContext context,
LoadingState<List<DynamicItemModel>?> loadingState) {
bool dynamicsWaterfallFlow = GStorage.setting bool dynamicsWaterfallFlow = GStorage.setting
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true); .get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator( ? refreshIndicator(
onRefresh: () async { onRefresh: () async {
await widget.ctr.refreshDynamic(); await widget.ctr.refreshDynamic();
@@ -54,13 +56,13 @@ class _SearchDynamicState extends State<SearchDynamic>
crossAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.safeSpace, mainAxisSpacing: StyleString.safeSpace,
lastChildLayoutTypeBuilder: (index) { lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
EasyThrottle.throttle('member_dynamics', EasyThrottle.throttle('member_dynamics',
const Duration(milliseconds: 1000), () { const Duration(milliseconds: 1000), () {
widget.ctr.searchDynamic(false); widget.ctr.searchDynamic(false);
}); });
} }
return index == loadingState.response.length return index == loadingState.response!.length
? LastChildLayoutType.foot ? LastChildLayoutType.foot
: LastChildLayoutType.none; : LastChildLayoutType.none;
}, },
@@ -77,7 +79,7 @@ class _SearchDynamicState extends State<SearchDynamic>
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == if (index ==
loadingState.response.length - 1) { loadingState.response!.length - 1) {
EasyThrottle.throttle('member_dynamics', EasyThrottle.throttle('member_dynamics',
const Duration(milliseconds: 1000), const Duration(milliseconds: 1000),
() { () {
@@ -85,10 +87,10 @@ class _SearchDynamicState extends State<SearchDynamic>
}); });
} }
return DynamicPanel( return DynamicPanel(
item: loadingState.response[index], item: loadingState.response![index],
); );
}, },
childCount: loadingState.response.length, childCount: loadingState.response!.length,
), ),
), ),
), ),

View File

@@ -1,53 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/member/seasons.dart';
class MemberSeasonsController extends GetxController {
final ScrollController scrollController = ScrollController();
late int mid;
late int seasonId;
int pn = 1;
int ps = 30;
int count = 0;
RxList<MemberArchiveItem> seasonsList = <MemberArchiveItem>[].obs;
late Map page;
@override
void onInit() {
super.onInit();
mid = int.parse(Get.parameters['mid']!);
seasonId = int.parse(Get.parameters['seasonId']!);
}
// 获取专栏详情
Future getSeasonDetail(type) async {
if (type == 'onRefresh') {
pn = 1;
}
var res = await MemberHttp.getSeasonDetail(
mid: mid,
seasonId: seasonId,
pn: pn,
ps: ps,
sortReverse: false,
);
if (res['status']) {
seasonsList.addAll(res['data'].archives);
page = res['data'].page;
pn += 1;
}
return res;
}
// 上拉加载
Future onLoad() async {
getSeasonDetail('onLoad');
}
@override
void onClose() {
scrollController.dispose();
super.onClose();
}
}

View File

@@ -1,4 +0,0 @@
library member_seasons;
export 'controller.dart';
export 'view.dart';

View File

@@ -1,120 +0,0 @@
import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/models/member/seasons.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/constants.dart';
import '../../utils/grid.dart';
import 'controller.dart';
import 'widgets/item.dart';
class MemberSeasonsPage extends StatefulWidget {
const MemberSeasonsPage({super.key});
@override
State<MemberSeasonsPage> createState() => _MemberSeasonsPageState();
}
class _MemberSeasonsPageState extends State<MemberSeasonsPage> {
final MemberSeasonsController _memberSeasonsController =
Get.put(MemberSeasonsController());
late Future _futureBuilderFuture;
@override
void dispose() {
_memberSeasonsController.scrollController.removeListener(listener);
super.dispose();
}
@override
void initState() {
super.initState();
_futureBuilderFuture =
_memberSeasonsController.getSeasonDetail('onRefresh');
_memberSeasonsController.scrollController.addListener(listener);
}
void listener() {
if (_memberSeasonsController.scrollController.position.pixels >=
_memberSeasonsController.scrollController.position.maxScrollExtent -
200) {
EasyThrottle.throttle(
'member_archives', const Duration(milliseconds: 500), () {
_memberSeasonsController.onLoad();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Ta的专栏')),
body: Padding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
),
child: SingleChildScrollView(
controller: _memberSeasonsController.scrollController,
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data != null) {
// TODO: refactor
if (snapshot.data is! Map) {
return HttpError(
isSliver: false,
callback: () => setState(() {
_futureBuilderFuture = _memberSeasonsController
.getSeasonDetail('onRefresh');
}),
);
}
Map data = snapshot.data as Map;
List<MemberArchiveItem> list =
_memberSeasonsController.seasonsList;
if (data['status']) {
return Obx(
() => list.isNotEmpty
? LayoutBuilder(
builder: (context, boxConstraints) {
return GridView.builder(
gridDelegate:
SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace,
maxCrossAxisExtent: Grid.smallCardWidth,
childAspectRatio: 0.94,
),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _memberSeasonsController
.seasonsList.length,
itemBuilder: (context, i) {
return MemberSeasonsItem(
seasonItem: _memberSeasonsController
.seasonsList[i],
);
},
);
},
)
: const SizedBox(),
);
} else {
return const SizedBox();
}
} else {
return const SizedBox();
}
} else {
return const SizedBox();
}
},
),
),
),
);
}
}

View File

@@ -1,101 +0,0 @@
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/member/seasons.dart';
import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/utils/utils.dart';
class MemberSeasonsItem extends StatelessWidget {
final MemberArchiveItem seasonItem;
const MemberSeasonsItem({
super.key,
required this.seasonItem,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 0,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onTap: () async {
int cid =
await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid);
Utils.toViewPage(
'bvid=${seasonItem.bvid}&cid=$cid',
arguments: {
'videoItem': seasonItem,
'heroTag': Utils.makeHeroTag(seasonItem.aid)
},
);
},
child: Column(
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: seasonItem.pic,
width: maxWidth,
height: maxHeight,
),
if (seasonItem.pubdate != null)
PBadge(
bottom: 6,
right: 6,
type: 'gray',
text: Utils.customStampStr(
timestamp: seasonItem.pubdate, date: 'YY-MM-DD'),
)
],
);
}),
),
Padding(
padding: const EdgeInsets.fromLTRB(5, 6, 0, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
seasonItem.title!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
StatView(
context: context,
value: Utils.numFormat(seasonItem.view!),
theme: 'gray',
),
const Spacer(),
Text(
Utils.customStampStr(
timestamp: seasonItem.pubdate, date: 'YY-MM-DD'),
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.outline,
),
),
const SizedBox(width: 6)
],
),
],
),
),
],
),
),
);
}
}

View File

@@ -57,11 +57,7 @@ class _ZonePageState extends CommonPageState<ZonePage, ZoneController>
Widget _buildSkeleton() { Widget _buildSkeleton() {
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -76,11 +72,7 @@ class _ZonePageState extends CommonPageState<ZonePage, ZoneController>
Loading() => _buildSkeleton(), Loading() => _buildSkeleton(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverGrid( ? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return VideoCardH( return VideoCardH(

View File

@@ -76,6 +76,7 @@ class _SearchPageState extends State<SearchPage> {
), ),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: MediaQuery.paddingOf(context).copyWith(top: 0),
child: Column( child: Column(
children: [ children: [
// 搜索建议 // 搜索建议
@@ -89,10 +90,16 @@ class _SearchPageState extends State<SearchPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (_searchController.enableHotKey) if (_searchController.enableHotKey)
Expanded(child: hotSearch()), Expanded(
child: Column(
children: [
hotSearch(),
if (_searchController.enableSearchRcmd)
hotSearch(false)
],
),
),
Expanded(child: _history()), Expanded(child: _history()),
if (_searchController.enableSearchRcmd)
Expanded(child: hotSearch(false)),
], ],
), ),
], ],
@@ -143,7 +150,7 @@ class _SearchPageState extends State<SearchPage> {
.copyWith(height: 1, fontWeight: FontWeight.bold), .copyWith(height: 1, fontWeight: FontWeight.bold),
); );
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(10, 25, 4, 25), padding: EdgeInsets.fromLTRB(10, isHot ? 25 : 4, 4, 25),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [

View File

@@ -67,19 +67,15 @@ class _SearchPanelState extends State<SearchPanel>
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
SliverGrid( SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: widget.searchType == SearchType.media_bangumi ||
mainAxisSpacing: 2, widget.searchType == SearchType.media_ft
maxCrossAxisExtent: (widget.searchType == SearchType.video || ? SliverGridDelegateWithExtentAndRatio(
widget.searchType == SearchType.article mainAxisSpacing: 2,
? Grid.mediumCardWidth maxCrossAxisExtent: Grid.smallCardWidth * 2,
: Grid.smallCardWidth) * childAspectRatio: StyleString.aspectRatio * 1.5,
2, minHeight: MediaQuery.textScalerOf(context).scale(155),
childAspectRatio: StyleString.aspectRatio * )
(widget.searchType == SearchType.media_bangumi || : Grid.videoCardHDelegate(context),
widget.searchType == SearchType.media_ft
? 1.5
: 2.2),
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
switch (widget.searchType) { switch (widget.searchType) {

View File

@@ -3,7 +3,6 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/pages/search_panel/controller.dart'; import 'package:PiliPlus/pages/search_panel/controller.dart';
@@ -82,7 +81,6 @@ Widget searchArticlePanel(
), ),
), ),
switch (loadingState) { switch (loadingState) {
Loading() => errorWidget(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
@@ -90,11 +88,7 @@ Widget searchArticlePanel(
MediaQuery.of(context).padding.bottom, MediaQuery.of(context).padding.bottom,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -11,7 +11,6 @@ import '../../../utils/grid.dart';
Widget searchLivePanel( Widget searchLivePanel(
BuildContext context, ctr, LoadingState<List<dynamic>?> loadingState) { BuildContext context, ctr, LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget,
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? GridView.builder( ? GridView.builder(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),

View File

@@ -13,7 +13,6 @@ Widget searchBangumiPanel(
context, ctr, LoadingState<List<dynamic>?> loadingState) { context, ctr, LoadingState<List<dynamic>?> loadingState) {
late TextStyle style = TextStyle(fontSize: 13); late TextStyle style = TextStyle(fontSize: 13);
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget,
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? CustomScrollView( ? CustomScrollView(
controller: ctr.scrollController, controller: ctr.scrollController,
@@ -146,10 +145,10 @@ Widget searchBangumiPanel(
), ),
], ],
) )
: errorWidget( : scrollErrorWidget(
callback: ctr.onReload, callback: ctr.onReload,
), ),
Error() => errorWidget( Error() => scrollErrorWidget(
errMsg: loadingState.errMsg, errMsg: loadingState.errMsg,
callback: ctr.onReload, callback: ctr.onReload,
), ),

View File

@@ -2,7 +2,6 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/pages/search_panel/controller.dart'; import 'package:PiliPlus/pages/search_panel/controller.dart';
@@ -80,7 +79,6 @@ Widget searchUserPanel(
), ),
), ),
switch (loadingState) { switch (loadingState) {
Loading() => errorWidget(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
@@ -89,7 +87,7 @@ Widget searchUserPanel(
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: Grid.smallCardWidth * 2, maxCrossAxisExtent: Grid.smallCardWidth * 2,
mainAxisExtent: 56, mainAxisExtent: 66,
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -2,7 +2,6 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -13,7 +12,6 @@ import 'package:PiliPlus/models/common/search_type.dart';
import 'package:PiliPlus/pages/search_panel/index.dart'; import 'package:PiliPlus/pages/search_panel/index.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../common/constants.dart';
import '../../../utils/grid.dart'; import '../../../utils/grid.dart';
Widget searchVideoPanel( Widget searchVideoPanel(
@@ -92,18 +90,13 @@ Widget searchVideoPanel(
), ),
), ),
switch (loadingState) { switch (loadingState) {
Loading() => errorWidget(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {

View File

@@ -22,7 +22,7 @@ import 'package:PiliPlus/models/video/play/subtitle.dart';
import 'package:PiliPlus/pages/home/controller.dart'; import 'package:PiliPlus/pages/home/controller.dart';
import 'package:PiliPlus/pages/hot/controller.dart'; import 'package:PiliPlus/pages/hot/controller.dart';
import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/pages/rcmd/controller.dart'; import 'package:PiliPlus/pages/rcmd/controller.dart';
import 'package:PiliPlus/pages/setting/pages/color_select.dart'; import 'package:PiliPlus/pages/setting/pages/color_select.dart';
@@ -247,7 +247,7 @@ List<SettingsModel> get styleSettings => [
builder: (context) { builder: (context) {
return SlideDialog( return SlideDialog(
title: '中卡最大列宽度默认280dp', title: '中卡最大列宽度默认280dp',
value: GStorage.mediumCardWidth, value: GStorage.smallCardWidth,
min: 150.0, min: 150.0,
max: 500.0, max: 500.0,
divisions: 35, divisions: 35,
@@ -255,7 +255,7 @@ List<SettingsModel> get styleSettings => [
); );
}); });
if (result != null) { if (result != null) {
await GStorage.setting.put(SettingBoxKey.mediumCardWidth, result); await GStorage.setting.put(SettingBoxKey.smallCardWidth, result);
SmartDialog.showToast('重启生效'); SmartDialog.showToast('重启生效');
setState(); setState();
} }
@@ -263,7 +263,7 @@ List<SettingsModel> get styleSettings => [
leading: const Icon(Icons.calendar_view_week_outlined), leading: const Icon(Icons.calendar_view_week_outlined),
title: '中卡列表宽度dp限制', title: '中卡列表宽度dp限制',
getSubtitle: () => getSubtitle: () =>
'当前:${GStorage.mediumCardWidth.toInt()}dp屏幕宽度:${MediaQuery.of(Get.context!).size.width.toPrecision(2)}dp。宽度越小列数越多。', '当前:${GStorage.smallCardWidth.toInt()}dp屏幕宽度:${MediaQuery.of(Get.context!).size.width.toPrecision(2)}dp。宽度越小列数越多。',
), ),
SettingsModel( SettingsModel(
settingsType: SettingsType.sw1tch, settingsType: SettingsType.sw1tch,

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -45,11 +44,7 @@ class _SubPageState extends State<SubPage> {
Widget _buildBody(LoadingState<List<SubFolderItemData>?> loadingState) { Widget _buildBody(LoadingState<List<SubFolderItemData>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) => const VideoCardHSkeleton(), (context, index) => const VideoCardHSkeleton(),
childCount: 10, childCount: 10,
@@ -57,11 +52,7 @@ class _SubPageState extends State<SubPage> {
), ),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverGrid( ? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: loadingState.response!.length, childCount: loadingState.response!.length,
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/models/user/sub_detail.dart'; import 'package:PiliPlus/models/user/sub_detail.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
@@ -227,13 +226,7 @@ class _SubDetailPageState extends State<SubDetailPage> {
MediaQuery.paddingOf(context).bottom + 80, MediaQuery.paddingOf(context).bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: gridDelegate: Grid.videoCardHDelegate(context),
SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio:
StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: subList.length, childCount: subList.length,
(BuildContext context, int index) { (BuildContext context, int index) {
@@ -258,11 +251,7 @@ class _SubDetailPageState extends State<SubDetailPage> {
} else { } else {
// 骨架屏 // 骨架屏
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) => const VideoCardHSkeleton(), (context, index) => const VideoCardHSkeleton(),
childCount: 10, childCount: 10,

View File

@@ -75,15 +75,6 @@ class SubVideoCardH extends StatelessWidget {
bottom: 6.0, bottom: 6.0,
type: 'gray', type: 'gray',
), ),
// if (videoItem.ogv != null) ...[
// PBadge(
// text: videoItem.ogv['type_name'],
// top: 6.0,
// right: 6.0,
// bottom: null,
// left: null,
// ),
// ],
], ],
); );
}, },
@@ -107,20 +98,23 @@ class SubVideoCardH extends StatelessWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Expanded(
'${videoItem.title}', child: Text(
textAlign: TextAlign.start, '${videoItem.title}',
style: const TextStyle( textAlign: TextAlign.start,
letterSpacing: 0.3, style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
const Spacer(),
Text( Text(
Utils.dateFormat(videoItem.pubtime), Utils.dateFormat(videoItem.pubtime),
style: TextStyle( style: TextStyle(
fontSize: 11, color: Theme.of(context).colorScheme.outline), fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 2), padding: const EdgeInsets.only(top: 2),

View File

@@ -612,14 +612,13 @@ class _VideoInfoState extends State<VideoInfo> {
Stack( Stack(
children: [ children: [
Row( Row(
children: <Widget>[ children: [
StatView( StatView(
context: context, context: context,
theme: 'gray', theme: 'gray',
value: Utils.numFormat(!widget.loadingStatus value: Utils.numFormat(!widget.loadingStatus
? videoDetail.stat?.view ?? '-' ? videoDetail.stat?.view ?? '-'
: videoItem['stat']?.view ?? '-'), : videoItem['stat']?.view ?? '-'),
size: 'medium',
textColor: t.colorScheme.outline, textColor: t.colorScheme.outline,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
@@ -629,7 +628,6 @@ class _VideoInfoState extends State<VideoInfo> {
value: Utils.numFormat(!widget.loadingStatus value: Utils.numFormat(!widget.loadingStatus
? videoDetail.stat?.danmaku ?? '-' ? videoDetail.stat?.danmaku ?? '-'
: videoItem['stat']?.danmu ?? '-'), : videoItem['stat']?.danmu ?? '-'),
size: 'medium',
textColor: t.colorScheme.outline, textColor: t.colorScheme.outline,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@@ -1,211 +0,0 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/utils/utils.dart';
@Deprecated('deprecated')
class IntroDetail extends StatelessWidget {
const IntroDetail({
super.key,
this.videoDetail,
this.videoTags,
});
final dynamic videoDetail;
final dynamic videoTags;
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Column(
children: [
InkWell(
onTap: () => Get.back(),
child: Container(
height: 35,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius:
const BorderRadius.all(Radius.circular(3))),
),
),
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
videoDetail!.title,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 6),
Row(
children: [
StatView(
context: context,
theme: 'gray',
value: Utils.numFormat(videoDetail!.stat!.view),
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
context: context,
theme: 'gray',
value: Utils.numFormat(videoDetail!.stat!.danmu),
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(videoDetail!.pubdate,
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
if (videoTags is List && videoTags.isNotEmpty) ...[
const SizedBox(height: 10),
Wrap(
spacing: 8,
runSpacing: 8,
children: (videoTags as List)
.map(
(item) => SearchText(
fontSize: 13,
text: item['tag_name'],
onTap: (_) => Get.toNamed('/searchResult',
parameters: {'keyword': item['tag_name']}),
onLongPress: (_) =>
Utils.copyText(item['tag_name']),
),
)
.toList(),
)
],
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: SelectableRegion(
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
videoDetail!.bvid!,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 10),
Text.rich(
style: const TextStyle(
height: 1.4,
// fontSize: 13,
),
TextSpan(
children: [
buildContent(context, videoDetail!),
],
),
),
],
),
),
),
const SizedBox(height: 100),
],
),
),
)
],
),
),
);
}
InlineSpan buildContent(BuildContext context, content) {
final List descV2 = content.descV2;
// type
// 1 普通文本
// 2 @用户
final List<TextSpan> spanChildren = List.generate(descV2.length, (index) {
final currentDesc = descV2[index];
switch (currentDesc.type) {
case 1:
final List<InlineSpan> spanChildren = <InlineSpan>[];
final RegExp urlRegExp = RegExp(Constants.urlPattern);
final Iterable<Match> matches =
urlRegExp.allMatches(currentDesc.rawText);
int previousEndIndex = 0;
for (final Match match in matches) {
if (match.start > previousEndIndex) {
spanChildren.add(TextSpan(
text: currentDesc.rawText
.substring(previousEndIndex, match.start)));
}
spanChildren.add(
TextSpan(
text: match.group(0),
style: TextStyle(
color: Theme.of(context).colorScheme.primary), // 设置颜色为蓝色
recognizer: TapGestureRecognizer()
..onTap = () {
// 处理点击事件
try {
Utils.handleWebview(match.group(0)!);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
),
);
previousEndIndex = match.end;
}
if (previousEndIndex < currentDesc.rawText.length) {
spanChildren.add(TextSpan(
text: currentDesc.rawText.substring(previousEndIndex)));
}
final TextSpan result = TextSpan(children: spanChildren);
return result;
case 2:
final Color colorSchemePrimary =
Theme.of(context).colorScheme.primary;
final String heroTag = Utils.makeHeroTag(currentDesc.bizId);
return TextSpan(
text: '@${currentDesc.rawText}',
style: TextStyle(color: colorSchemePrimary),
recognizer: TapGestureRecognizer()
..onTap = () {
Get.toNamed(
'/member?mid=${currentDesc.bizId}',
arguments: {'face': '', 'heroTag': heroTag},
);
},
);
default:
return const TextSpan();
}
});
return TextSpan(children: spanChildren);
}
}

View File

@@ -4,7 +4,7 @@ import 'package:PiliPlus/models/member/info.dart';
import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/item.dart'; import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart' import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
@@ -191,11 +190,7 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
bottom: MediaQuery.of(context).padding.bottom + 80, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1 && if (index == loadingState.response.length - 1 &&

View File

@@ -36,11 +36,7 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) { Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
@@ -54,11 +50,7 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
bottom: MediaQuery.of(context).padding.bottom, bottom: MediaQuery.of(context).padding.bottom,
), ),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: Grid.videoCardHDelegate(context),
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return VideoCardH( return VideoCardH(
videoItem: loadingState.response![index], videoItem: loadingState.response![index],

File diff suppressed because it is too large Load Diff

View File

@@ -1,153 +0,0 @@
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/models/common/reply_type.dart';
import 'package:PiliPlus/models/video/reply/item.dart';
import 'package:PiliPlus/utils/feed_back.dart';
class ZanButton extends StatefulWidget {
const ZanButton({
super.key,
this.replyItem,
this.replyType,
});
final ReplyItemModel? replyItem;
final ReplyType? replyType;
@override
State<ZanButton> createState() => _ZanButtonState();
}
class _ZanButtonState extends State<ZanButton> {
Future onHateReply() async {
feedBack();
// SmartDialog.showLoading(msg: 'piliplus ...');
final ReplyItemModel replyItem = widget.replyItem!;
final int oid = replyItem.oid!;
final int rpid = replyItem.rpid!;
// 1 已点赞 2 不喜欢 0 未操作
final int action = replyItem.action != 2 ? 2 : 0;
final res = await ReplyHttp.hateReply(
type: widget.replyType!.index,
action: action == 2 ? 1 : 0,
oid: oid,
rpid: rpid,
);
// SmartDialog.dismiss();
if (res['status']) {
SmartDialog.showToast(replyItem.action != 2 ? '点踩成功' : '取消踩');
if (action == 2) {
if (replyItem.action == 1) {
replyItem.like = replyItem.like! - 1;
}
replyItem.action = 2;
} else {
// replyItem.like = replyItem.like! - 1;
replyItem.action = 0;
}
setState(() {});
} else {
SmartDialog.showToast(res['msg']);
}
}
// 评论点赞
Future onLikeReply() async {
feedBack();
// SmartDialog.showLoading(msg: 'piliplus ...');
final ReplyItemModel replyItem = widget.replyItem!;
final int oid = replyItem.oid!;
final int rpid = replyItem.rpid!;
// 1 已点赞 2 不喜欢 0 未操作
final int action = replyItem.action != 1 ? 1 : 0;
final res = await ReplyHttp.likeReply(
type: widget.replyType!.index, oid: oid, rpid: rpid, action: action);
// SmartDialog.dismiss();
if (res['status']) {
SmartDialog.showToast(replyItem.action != 1 ? '点赞成功' : '取消赞');
if (action == 1) {
replyItem.like = replyItem.like! + 1;
replyItem.action = 1;
} else {
replyItem.like = replyItem.like! - 1;
replyItem.action = 0;
}
setState(() {});
} else {
SmartDialog.showToast(res['msg']);
}
}
bool isProcessing = false;
void handleState(Future Function() action) async {
if (isProcessing.not) {
isProcessing = true;
await action();
isProcessing = false;
}
}
@override
Widget build(BuildContext context) {
final ThemeData t = Theme.of(context);
final Color color = t.colorScheme.outline;
final Color primary = t.colorScheme.primary;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 32,
child: TextButton(
onPressed: () => handleState(onHateReply),
child: Icon(
widget.replyItem!.action == 2
? FontAwesomeIcons.solidThumbsDown
: FontAwesomeIcons.thumbsDown,
size: 16,
color: widget.replyItem!.action == 2 ? primary : color,
semanticLabel: widget.replyItem!.action == 2 ? '已踩' : '点踩',
),
),
),
SizedBox(
height: 32,
child: TextButton(
onPressed: () => handleState(onLikeReply),
child: Row(
children: [
Icon(
widget.replyItem!.action == 1
? FontAwesomeIcons.solidThumbsUp
: FontAwesomeIcons.thumbsUp,
size: 16,
color: widget.replyItem!.action == 1 ? primary : color,
semanticLabel: widget.replyItem!.action == 1 ? '已赞' : '点赞',
),
const SizedBox(width: 4),
AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: Text(
Utils.numFormat(widget.replyItem!.like),
key: ValueKey<int>(widget.replyItem!.like!),
style: TextStyle(
color: widget.replyItem!.action == 1 ? primary : color,
fontSize: t.textTheme.labelSmall!.fontSize,
),
),
),
],
),
),
),
],
);
}
}

Some files were not shown because too many files have changed in this diff Show More