mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-15 23:10:09 +08:00
@@ -4,11 +4,9 @@
|
||||
|
||||
import 'dart:ui' show SemanticsRole;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||
import 'package:flutter/material.dart' hide TabBarView, PageView;
|
||||
import 'package:flutter/material.dart' hide TabBarView;
|
||||
|
||||
/// A page view that displays the widget which corresponds to the currently
|
||||
/// selected tab.
|
||||
@@ -357,7 +355,7 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
|
||||
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: _handleScrollNotification,
|
||||
child: PageView<CustomHorizontalDragGestureRecognizer>(
|
||||
child: PageView(
|
||||
scrollDirection: widget.scrollDirection,
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
@@ -365,8 +363,6 @@ class _CustomTabBarViewState extends State<CustomTabBarView> {
|
||||
physics: widget.physics == null
|
||||
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
|
||||
: const PageScrollPhysics().applyTo(widget.physics),
|
||||
horizontalDragGestureRecognizer:
|
||||
CustomHorizontalDragGestureRecognizer(),
|
||||
children: _childrenWithKey,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ class CustomHorizontalDragGestureRecognizer
|
||||
globalDistanceMoved.abs(),
|
||||
gestureSettings,
|
||||
pointerDeviceKind,
|
||||
_initialPosition!,
|
||||
_initialPosition,
|
||||
lastPosition.global,
|
||||
);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ bool _computeHitSlop(
|
||||
double globalDistanceMoved,
|
||||
DeviceGestureSettings? settings,
|
||||
PointerDeviceKind kind,
|
||||
Offset initialPosition,
|
||||
Offset? initialPosition,
|
||||
Offset lastPosition,
|
||||
) {
|
||||
switch (kind) {
|
||||
@@ -49,13 +49,13 @@ bool _computeHitSlop(
|
||||
case PointerDeviceKind.unknown:
|
||||
case PointerDeviceKind.touch:
|
||||
return globalDistanceMoved > touchSlopH &&
|
||||
_cacl(initialPosition, lastPosition);
|
||||
_calc(initialPosition!, lastPosition);
|
||||
case PointerDeviceKind.trackpad:
|
||||
return globalDistanceMoved > (settings?.touchSlop ?? kTouchSlop);
|
||||
}
|
||||
}
|
||||
|
||||
bool _cacl(Offset initialPosition, Offset lastPosition) {
|
||||
bool _calc(Offset initialPosition, Offset lastPosition) {
|
||||
final offset = lastPosition - initialPosition;
|
||||
return offset.dx.abs() > offset.dy.abs() * 3;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ class CustomGridView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
static BorderRadius borderRadius(
|
||||
static BorderRadius _borderRadius(
|
||||
int col,
|
||||
int length,
|
||||
int index, {
|
||||
@@ -255,7 +255,7 @@ class CustomGridView extends StatelessWidget {
|
||||
height: imageHeight,
|
||||
children: List.generate(length, (index) {
|
||||
final item = picArr[index];
|
||||
final radius = borderRadius(column, length, index);
|
||||
final borderRadius = _borderRadius(column, length, index);
|
||||
return LayoutId(
|
||||
id: index,
|
||||
child: GestureDetector(
|
||||
@@ -274,17 +274,14 @@ class CustomGridView extends StatelessWidget {
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: radius,
|
||||
child: NetworkImgLayer(
|
||||
type: .emote,
|
||||
src: item.url,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
alignment: item.isLongPic ? .topCenter : .center,
|
||||
cacheWidth: item.width <= item.height,
|
||||
getPlaceHolder: () => placeHolder,
|
||||
),
|
||||
NetworkImgLayer(
|
||||
src: item.url,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
borderRadius: borderRadius,
|
||||
alignment: item.isLongPic ? .topCenter : .center,
|
||||
cacheWidth: item.width <= item.height,
|
||||
getPlaceHolder: () => placeHolder,
|
||||
),
|
||||
if (item.isLivePhoto)
|
||||
const PBadge(
|
||||
|
||||
@@ -239,6 +239,8 @@ class PageInfo {
|
||||
final String? downloadTitle;
|
||||
final String? downloadSubtitle;
|
||||
|
||||
bool get cacheWidth => width <= height;
|
||||
|
||||
PageInfo({
|
||||
required this.cid,
|
||||
required this.page,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:PiliPlus/models_new/later/bangumi.dart';
|
||||
import 'package:PiliPlus/models_new/later/page.dart';
|
||||
import 'package:PiliPlus/models_new/later/rights.dart';
|
||||
import 'package:PiliPlus/models_new/later/stat.dart';
|
||||
import 'package:PiliPlus/models_new/video/video_detail/dimension.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select/base.dart';
|
||||
|
||||
class LaterItemModel with MultiSelectData {
|
||||
@@ -27,6 +28,7 @@ class LaterItemModel with MultiSelectData {
|
||||
bool? isPugv;
|
||||
int? seasonId;
|
||||
bool? isCharging;
|
||||
Dimension? dimension;
|
||||
|
||||
LaterItemModel({
|
||||
this.aid,
|
||||
@@ -50,6 +52,7 @@ class LaterItemModel with MultiSelectData {
|
||||
this.isPugv,
|
||||
this.seasonId,
|
||||
this.isCharging,
|
||||
this.dimension,
|
||||
});
|
||||
|
||||
factory LaterItemModel.fromJson(Map<String, dynamic> json) => LaterItemModel(
|
||||
@@ -89,5 +92,8 @@ class LaterItemModel with MultiSelectData {
|
||||
isPugv: json['is_pugv'] as bool?,
|
||||
seasonId: json['season_id'] as int?,
|
||||
isCharging: json['charging_pay']?['level'] != null,
|
||||
dimension: json['dimension'] == null
|
||||
? null
|
||||
: Dimension.fromJson(json['dimension'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@ class Dimension {
|
||||
int? width;
|
||||
int? height;
|
||||
|
||||
bool? get cacheWidth {
|
||||
if (width != null && height != null) {
|
||||
return width! <= height!;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Dimension({this.width, this.height});
|
||||
|
||||
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
|
||||
|
||||
@@ -175,17 +175,24 @@ class DetailItem extends StatelessWidget {
|
||||
final cover = File(
|
||||
path.join(entry.entryDirPath, PathUtils.coverName),
|
||||
);
|
||||
final maxWidth = constraints.maxWidth;
|
||||
final maxHeight = constraints.maxHeight;
|
||||
int? cacheWidth, cacheHeight;
|
||||
if (entry.pageData?.cacheWidth ?? false) {
|
||||
cacheWidth = maxWidth.cacheSize(context);
|
||||
} else {
|
||||
cacheHeight = maxHeight.cacheSize(context);
|
||||
}
|
||||
return cover.existsSync()
|
||||
? ClipRRect(
|
||||
borderRadius: StyleString.mdRadius,
|
||||
child: Image.file(
|
||||
cover,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
fit: BoxFit.cover,
|
||||
cacheHeight: constraints.maxWidth.cacheSize(
|
||||
context,
|
||||
),
|
||||
cacheWidth: cacheWidth,
|
||||
cacheHeight: cacheHeight,
|
||||
colorBlendMode: NetworkImgLayer.reduce
|
||||
? BlendMode.modulate
|
||||
: null,
|
||||
@@ -196,8 +203,9 @@ class DetailItem extends StatelessWidget {
|
||||
)
|
||||
: NetworkImgLayer(
|
||||
src: entry.cover,
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
cacheWidth: entry.pageData?.cacheWidth,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -377,6 +377,7 @@ class _EpisodePanelState extends State<EpisodePanel>
|
||||
int? view;
|
||||
int? danmaku;
|
||||
bool? isCharging;
|
||||
bool? cacheWidth;
|
||||
|
||||
switch (episode) {
|
||||
case Part part:
|
||||
@@ -384,15 +385,21 @@ class _EpisodePanelState extends State<EpisodePanel>
|
||||
title = part.part!;
|
||||
duration = part.duration;
|
||||
pubdate = part.ctime;
|
||||
cacheWidth = part.dimension?.cacheWidth;
|
||||
break;
|
||||
case ugc.EpisodeItem item:
|
||||
title = item.title!;
|
||||
cover = item.arc?.pic;
|
||||
bvid = item.bvid;
|
||||
duration = item.arc?.duration;
|
||||
pubdate = item.arc?.pubdate;
|
||||
view = item.arc?.stat?.view;
|
||||
danmaku = item.arc?.stat?.danmaku;
|
||||
if (item.arc case final arc?) {
|
||||
cover = arc.pic;
|
||||
duration = arc.duration;
|
||||
pubdate = arc.pubdate;
|
||||
if (arc.stat case final stat?) {
|
||||
view = stat.view;
|
||||
danmaku = stat.danmaku;
|
||||
}
|
||||
cacheWidth = arc.dimension?.cacheWidth;
|
||||
}
|
||||
if (item.attribute == 8) {
|
||||
isCharging = true;
|
||||
}
|
||||
@@ -408,6 +415,7 @@ class _EpisodePanelState extends State<EpisodePanel>
|
||||
duration = item.duration == null ? null : item.duration! ~/ 1000;
|
||||
}
|
||||
pubdate = item.pubTime;
|
||||
cacheWidth = item.dimension?.cacheWidth;
|
||||
break;
|
||||
}
|
||||
late final Color primary = theme.colorScheme.primary;
|
||||
@@ -468,6 +476,7 @@ class _EpisodePanelState extends State<EpisodePanel>
|
||||
src: cover,
|
||||
width: 140.8,
|
||||
height: 88,
|
||||
cacheWidth: cacheWidth,
|
||||
),
|
||||
if (duration != null && duration > 0)
|
||||
PBadge(
|
||||
|
||||
@@ -37,9 +37,9 @@ class _LaterPageState extends State<LaterPage>
|
||||
);
|
||||
}
|
||||
|
||||
final sortKey = GlobalKey();
|
||||
final _sortKey = GlobalKey();
|
||||
void listener() {
|
||||
(sortKey.currentContext as Element?)?.markNeedsBuild();
|
||||
(_sortKey.currentContext as Element?)?.markNeedsBuild();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -77,9 +77,7 @@ class _LaterPageState extends State<LaterPage>
|
||||
appBar: _buildAppbar(enableMultiSelect),
|
||||
floatingActionButtonLocation: const CustomFabLocation(),
|
||||
floatingActionButton: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: kFloatingActionButtonMargin,
|
||||
),
|
||||
padding: const .only(right: kFloatingActionButtonMargin),
|
||||
child: Obx(
|
||||
() => currCtr().loadingState.value.isSuccess
|
||||
? AnimatedSlide(
|
||||
@@ -126,10 +124,8 @@ class _LaterPageState extends State<LaterPage>
|
||||
onTap: (_) {
|
||||
if (!_tabController.indexIsChanging) {
|
||||
currCtr().scrollController.animToTop();
|
||||
} else {
|
||||
if (enableMultiSelect) {
|
||||
currCtr(_tabController.previousIndex).handleSelect();
|
||||
}
|
||||
} else if (enableMultiSelect) {
|
||||
currCtr(_tabController.previousIndex).handleSelect();
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -164,9 +160,7 @@ class _LaterPageState extends State<LaterPage>
|
||||
ctr: currCtr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
style: TextButton.styleFrom(visualDensity: .compact),
|
||||
onPressed: () {
|
||||
final ctr = currCtr();
|
||||
RequestUtils.onCopyOrMove<LaterData, LaterItemModel>(
|
||||
@@ -179,15 +173,11 @@ class _LaterPageState extends State<LaterPage>
|
||||
},
|
||||
child: Text(
|
||||
'复制',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style: TextStyle(color: theme.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
style: TextButton.styleFrom(visualDensity: .compact),
|
||||
onPressed: () {
|
||||
final ctr = currCtr();
|
||||
RequestUtils.onCopyOrMove<LaterData, LaterItemModel>(
|
||||
@@ -200,9 +190,7 @@ class _LaterPageState extends State<LaterPage>
|
||||
},
|
||||
child: Text(
|
||||
'移动',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style: TextStyle(color: theme.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -226,113 +214,97 @@ class _LaterPageState extends State<LaterPage>
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
type: MaterialType.transparency,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
child: Builder(
|
||||
key: sortKey,
|
||||
builder: (context) {
|
||||
final value = currCtr().asc.value;
|
||||
return PopupMenuButton(
|
||||
initialValue: value,
|
||||
tooltip: '排序',
|
||||
onSelected: (value) {
|
||||
currCtr()
|
||||
..asc.value = value
|
||||
..onReload();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
Builder(
|
||||
key: _sortKey,
|
||||
builder: (context) {
|
||||
final value = currCtr().asc.value;
|
||||
return PopupMenuButton(
|
||||
initialValue: value,
|
||||
tooltip: '排序',
|
||||
onSelected: (value) => currCtr()
|
||||
..asc.value = value
|
||||
..onReload(),
|
||||
borderRadius: const .all(.circular(20)),
|
||||
child: Padding(
|
||||
padding: const .symmetric(horizontal: 12, vertical: 6),
|
||||
child: Text.rich(
|
||||
style: TextStyle(fontSize: 14, height: 1, color: color),
|
||||
strutStyle: const StrutStyle(
|
||||
leading: 0,
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
),
|
||||
child: Text.rich(
|
||||
style: TextStyle(fontSize: 14, height: 1, color: color),
|
||||
strutStyle: const StrutStyle(
|
||||
leading: 0,
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: value ? '最早添加' : '最近添加'),
|
||||
WidgetSpan(
|
||||
alignment: .middle,
|
||||
child: Icon(
|
||||
size: 14,
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
color: color,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: value ? '最早添加' : '最近添加'),
|
||||
WidgetSpan(
|
||||
alignment: .middle,
|
||||
child: Icon(
|
||||
size: 14,
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
color: color,
|
||||
),
|
||||
],
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
],
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
const PopupMenuItem(
|
||||
value: false,
|
||||
child: Text('最近添加'),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: true,
|
||||
child: Text('最早添加'),
|
||||
),
|
||||
itemBuilder: (_) => [
|
||||
const PopupMenuItem(
|
||||
value: false,
|
||||
child: Text('最近添加'),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: true,
|
||||
child: Text('最早添加'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuButton(
|
||||
tooltip: '清空',
|
||||
borderRadius: const .all(.circular(20)),
|
||||
child: Padding(
|
||||
padding: const .symmetric(horizontal: 12, vertical: 6),
|
||||
child: Text.rich(
|
||||
style: TextStyle(fontSize: 14, height: 1, color: color),
|
||||
strutStyle: const StrutStyle(
|
||||
leading: 0,
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(text: '清空'),
|
||||
WidgetSpan(
|
||||
alignment: .middle,
|
||||
child: Icon(
|
||||
size: 14,
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
type: MaterialType.transparency,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
child: PopupMenuButton(
|
||||
tooltip: '清空',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Text.rich(
|
||||
style: TextStyle(fontSize: 14, height: 1, color: color),
|
||||
strutStyle: const StrutStyle(
|
||||
leading: 0,
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(text: '清空'),
|
||||
WidgetSpan(
|
||||
alignment: .middle,
|
||||
child: Icon(
|
||||
size: 14,
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context, 1),
|
||||
child: const Text('清空失效'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context, 2),
|
||||
child: const Text('清空看完'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context),
|
||||
child: const Text('清空全部'),
|
||||
),
|
||||
],
|
||||
),
|
||||
itemBuilder: (_) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context, 1),
|
||||
child: const Text('清空失效'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context, 2),
|
||||
child: const Text('清空看完'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => currCtr().toViewClear(context),
|
||||
child: const Text('清空全部'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
|
||||
@@ -97,6 +97,7 @@ class VideoCardHLater extends StatelessWidget {
|
||||
src: videoItem.pic,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
cacheWidth: videoItem.dimension?.cacheWidth,
|
||||
),
|
||||
if (videoItem.isCharging == true)
|
||||
const PBadge(
|
||||
|
||||
@@ -48,17 +48,17 @@ class LiveController extends CommonListController with AccountMixin {
|
||||
bool customHandleResponse(bool isRefresh, Success response) {
|
||||
if (isRefresh) {
|
||||
final res = response.response;
|
||||
if (res case final LiveIndexData data) {
|
||||
if (data.hasMore == 0) {
|
||||
if (res is LiveIndexData) {
|
||||
if (res.hasMore == 0) {
|
||||
isEnd = true;
|
||||
}
|
||||
topState.value = Pair(
|
||||
first: data.followItem,
|
||||
second: data.areaItem,
|
||||
first: res.followItem,
|
||||
second: res.areaItem,
|
||||
);
|
||||
} else if (res case final LiveSecondData data) {
|
||||
count = data.count;
|
||||
newTags = data.newTags;
|
||||
} else if (res is LiveSecondData) {
|
||||
count = res.count;
|
||||
newTags = res.newTags;
|
||||
if (sortType != null) {
|
||||
tagIndex.value =
|
||||
newTags?.indexWhere((e) => e.sortType == sortType) ?? -1;
|
||||
|
||||
@@ -95,43 +95,41 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
|
||||
child: SizedBox(
|
||||
// 10+14*textScaler
|
||||
height: 10.0 + textScaler.scale(14),
|
||||
child: ListView.separated(
|
||||
scrollDirection: .horizontal,
|
||||
padding: const .only(right: 8),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, index) {
|
||||
late final item = list[index - 1];
|
||||
return Obx(
|
||||
() {
|
||||
final isCurr =
|
||||
index == controller.areaIndex.value;
|
||||
return SearchText(
|
||||
fontSize: 14,
|
||||
height: 1,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 5,
|
||||
),
|
||||
text: index == 0 ? '推荐' : '${item.title}',
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: null,
|
||||
onTap: (value) {
|
||||
controller.onSelectArea(
|
||||
index,
|
||||
index == 0 ? null : item,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: list.length + 1,
|
||||
),
|
||||
child: Obx(() {
|
||||
final areaIndex = controller.areaIndex.value;
|
||||
return ListView.separated(
|
||||
scrollDirection: .horizontal,
|
||||
padding: const .only(right: 8),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
separatorBuilder: (_, _) =>
|
||||
const SizedBox(width: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final isFirst = index == 0;
|
||||
late final item = list[index - 1];
|
||||
final isCurr = index == areaIndex;
|
||||
return SearchText(
|
||||
fontSize: 14,
|
||||
height: 1,
|
||||
padding: const .symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 5,
|
||||
),
|
||||
text: isFirst ? '推荐' : item.title!,
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: null,
|
||||
onTap: (_) => controller.onSelectArea(
|
||||
index,
|
||||
isFirst ? null : item,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: list.length + 1,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
iconButton(
|
||||
@@ -189,42 +187,34 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
|
||||
child: SizedBox(
|
||||
// 8+10+13*textScaler
|
||||
height: 18.0 + textScaler.scale(13),
|
||||
child: ListView.separated(
|
||||
scrollDirection: .horizontal,
|
||||
padding: const .only(bottom: 8),
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
late final item = newTags[index];
|
||||
return Obx(
|
||||
() {
|
||||
final isCurr = index == controller.tagIndex.value;
|
||||
return SearchText(
|
||||
height: 1,
|
||||
fontSize: 13,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 5,
|
||||
),
|
||||
text: '${item.name}',
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: null,
|
||||
onTap: (value) {
|
||||
controller.onSelectTag(
|
||||
index,
|
||||
item.sortType,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: newTags.length,
|
||||
),
|
||||
child: Obx(() {
|
||||
final tagIndex = controller.tagIndex.value;
|
||||
return ListView.separated(
|
||||
scrollDirection: .horizontal,
|
||||
padding: const .only(bottom: 8),
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final item = newTags[index];
|
||||
final isCurr = index == tagIndex;
|
||||
return SearchText(
|
||||
height: 1,
|
||||
fontSize: 13,
|
||||
padding: const .symmetric(horizontal: 8, vertical: 5),
|
||||
text: item.name!,
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: null,
|
||||
onTap: (value) =>
|
||||
controller.onSelectTag(index, item.sortType),
|
||||
);
|
||||
},
|
||||
itemCount: newTags.length,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
response != null && response.isNotEmpty
|
||||
|
||||
@@ -81,39 +81,31 @@ class _LiveAreaChildPageState extends State<LiveAreaChildPage>
|
||||
slivers: [
|
||||
if (_controller.newTags?.isNotEmpty == true)
|
||||
SliverToBoxAdapter(
|
||||
child: SelfSizedHorizontalList(
|
||||
padding: const .only(bottom: 12),
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, index) {
|
||||
late final item = _controller.newTags![index];
|
||||
return Obx(
|
||||
() {
|
||||
final isCurr = index == _controller.tagIndex.value;
|
||||
return SearchText(
|
||||
fontSize: 14,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 3,
|
||||
),
|
||||
text: '${item.name}',
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: null,
|
||||
onTap: (value) {
|
||||
_controller.onSelectTag(
|
||||
index,
|
||||
item.sortType,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: _controller.newTags!.length,
|
||||
),
|
||||
child: Obx(() {
|
||||
final tagIndex = _controller.tagIndex.value;
|
||||
return SelfSizedHorizontalList(
|
||||
padding: const .only(bottom: 12),
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final item = _controller.newTags![index];
|
||||
final isCurr = index == tagIndex;
|
||||
return SearchText(
|
||||
fontSize: 14,
|
||||
padding: const .symmetric(horizontal: 8, vertical: 3),
|
||||
text: item.name!,
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: null,
|
||||
onTap: (value) =>
|
||||
_controller.onSelectTag(index, item.sortType),
|
||||
);
|
||||
},
|
||||
itemCount: _controller.newTags!.length,
|
||||
);
|
||||
}),
|
||||
),
|
||||
response != null && response.isNotEmpty
|
||||
? SliverGrid.builder(
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/tabs.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/models/common/nav_bar_config.dart';
|
||||
import 'package:PiliPlus/pages/home/view.dart';
|
||||
@@ -20,7 +18,7 @@ import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/storage_key.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart' hide PageView;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
@@ -395,11 +393,9 @@ class _MainAppState extends PopScopeState<MainApp>
|
||||
children: _mainController.navigationBars.map((i) => i.page).toList(),
|
||||
);
|
||||
} else {
|
||||
child = PageView<CustomHorizontalDragGestureRecognizer>(
|
||||
child = PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _mainController.controller,
|
||||
horizontalDragGestureRecognizer:
|
||||
CustomHorizontalDragGestureRecognizer(),
|
||||
children: _mainController.navigationBars.map((i) => i.page).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,58 +103,45 @@ class _PgcIndexPageState extends State<PgcIndexPage>
|
||||
ThemeData theme,
|
||||
int index,
|
||||
PgcIndexConditionData data,
|
||||
item,
|
||||
Object item,
|
||||
Map<String, dynamic> indexParams,
|
||||
) {
|
||||
if (item case final PgcConditionOrder e) {
|
||||
return Obx(
|
||||
() {
|
||||
final isCurr = _ctr.indexParams['order'] == e.field;
|
||||
return SearchText(
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
text: e.name!,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 3,
|
||||
),
|
||||
onTap: (_) => _ctr
|
||||
..indexParams['order'] = e.field
|
||||
..onReload(),
|
||||
);
|
||||
},
|
||||
if (item is PgcConditionOrder) {
|
||||
final isCurr = indexParams['order'] == item.field;
|
||||
return SearchText(
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
text: item.name!,
|
||||
padding: const .symmetric(horizontal: 6, vertical: 3),
|
||||
onTap: (_) => _ctr
|
||||
..indexParams['order'] = item.field
|
||||
..onReload(),
|
||||
);
|
||||
}
|
||||
if (item case final PgcConditionValue e) {
|
||||
if (item is PgcConditionValue) {
|
||||
final hasOrder = data.order?.isNotEmpty == true;
|
||||
if (hasOrder) index -= 1;
|
||||
final key = data.filter![index].field!;
|
||||
return Obx(
|
||||
() {
|
||||
final isCurr = _ctr.indexParams[key] == e.keyword;
|
||||
return SearchText(
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
text: e.name!,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 3,
|
||||
),
|
||||
onTap: (_) => _ctr
|
||||
..indexParams[key] = e.keyword
|
||||
..onReload(),
|
||||
);
|
||||
},
|
||||
final isCurr = indexParams[key] == item.keyword;
|
||||
return SearchText(
|
||||
bgColor: isCurr
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: Colors.transparent,
|
||||
textColor: isCurr
|
||||
? theme.colorScheme.onSecondaryContainer
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
text: item.name!,
|
||||
padding: const .symmetric(horizontal: 6, vertical: 3),
|
||||
onTap: (_) => _ctr
|
||||
..indexParams[key] = item.keyword
|
||||
..onReload(),
|
||||
);
|
||||
}
|
||||
throw UnsupportedError('${item.runtimeType}');
|
||||
throw UnsupportedError(item.runtimeType.toString());
|
||||
}
|
||||
|
||||
Widget _buildSortsWidget(
|
||||
@@ -179,16 +166,24 @@ class _PgcIndexPageState extends State<PgcIndexPage>
|
||||
: data.filter![index - 1].values
|
||||
: data.filter![index].values;
|
||||
if (item != null && item.isNotEmpty) {
|
||||
return SelfSizedHorizontalList(
|
||||
padding: isFirst
|
||||
? const .symmetric(horizontal: 12)
|
||||
: const .fromLTRB(12, 10, 12, 0),
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, childIndex) {
|
||||
return _buildSortWidget(theme, index, data, item[childIndex]);
|
||||
},
|
||||
itemCount: item.length,
|
||||
);
|
||||
return Obx(() {
|
||||
// ignore: invalid_use_of_protected_member
|
||||
final indexParams = _ctr.indexParams.value;
|
||||
return SelfSizedHorizontalList(
|
||||
padding: isFirst
|
||||
? const .symmetric(horizontal: 12)
|
||||
: const .fromLTRB(12, 10, 12, 0),
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, childIndex) => _buildSortWidget(
|
||||
theme,
|
||||
index,
|
||||
data,
|
||||
item[childIndex],
|
||||
indexParams,
|
||||
),
|
||||
itemCount: item.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
|
||||
@@ -48,10 +48,11 @@ class _RankPageState extends State<RankPage>
|
||||
width: 64,
|
||||
child: Obx(() {
|
||||
final tabIndex = _rankController.tabIndex.value;
|
||||
return ListView(
|
||||
return ListView.builder(
|
||||
padding: .only(bottom: MediaQuery.paddingOf(context).bottom + 105),
|
||||
children: RankType.values.map((e) {
|
||||
final index = e.index;
|
||||
itemCount: RankType.values.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = RankType.values[index];
|
||||
final isCurr = index == tabIndex;
|
||||
return IntrinsicHeight(
|
||||
child: Material(
|
||||
@@ -59,15 +60,11 @@ class _RankPageState extends State<RankPage>
|
||||
? theme.colorScheme.onInverseSurface
|
||||
: theme.colorScheme.surface,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (isCurr) {
|
||||
_rankController.animateToTop();
|
||||
} else {
|
||||
_rankController
|
||||
..tabIndex.value = index
|
||||
..tabController.animateTo(index);
|
||||
}
|
||||
},
|
||||
onTap: isCurr
|
||||
? _rankController.animateToTop
|
||||
: () => _rankController
|
||||
..tabIndex.value = index
|
||||
..tabController.animateTo(index),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -85,13 +82,13 @@ class _RankPageState extends State<RankPage>
|
||||
alignment: Alignment.center,
|
||||
padding: const .symmetric(vertical: 7),
|
||||
child: Text(
|
||||
RankType.values[index].label,
|
||||
style: TextStyle(
|
||||
color: isCurr
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.onSurface,
|
||||
fontSize: 15,
|
||||
),
|
||||
item.label,
|
||||
style: isCurr
|
||||
? TextStyle(
|
||||
fontSize: 15,
|
||||
color: theme.colorScheme.primary,
|
||||
)
|
||||
: const TextStyle(fontSize: 15),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -102,7 +99,7 @@ class _RankPageState extends State<RankPage>
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -332,9 +332,7 @@ class _DownloadPanelState extends State<DownloadPanel> {
|
||||
int? cid;
|
||||
|
||||
String? cover;
|
||||
int? width;
|
||||
int? height;
|
||||
bool cacheWidth = false;
|
||||
bool? cacheWidth;
|
||||
|
||||
switch (episode) {
|
||||
case Part part:
|
||||
@@ -343,10 +341,7 @@ class _DownloadPanelState extends State<DownloadPanel> {
|
||||
title = part.part ?? widget.videoDetail!.title!;
|
||||
duration = part.duration;
|
||||
pubdate = part.ctime;
|
||||
if (part.dimension case final dimension?) {
|
||||
width = dimension.width;
|
||||
height = dimension.height;
|
||||
}
|
||||
cacheWidth = part.dimension?.cacheWidth;
|
||||
break;
|
||||
case ugc.EpisodeItem item:
|
||||
cid = item.cid;
|
||||
@@ -359,10 +354,7 @@ class _DownloadPanelState extends State<DownloadPanel> {
|
||||
view = stat.view;
|
||||
danmaku = stat.danmaku;
|
||||
}
|
||||
if (arc.dimension case final dimension?) {
|
||||
width = dimension.width;
|
||||
height = dimension.height;
|
||||
}
|
||||
cacheWidth = arc.dimension?.cacheWidth;
|
||||
}
|
||||
if (item.attribute == 8) {
|
||||
isCharging = true;
|
||||
@@ -379,15 +371,9 @@ class _DownloadPanelState extends State<DownloadPanel> {
|
||||
duration = item.duration == null ? null : item.duration! ~/ 1000;
|
||||
}
|
||||
pubdate = item.pubTime;
|
||||
if (item.dimension case final dimension?) {
|
||||
width = dimension.width;
|
||||
height = dimension.height;
|
||||
}
|
||||
cacheWidth = item.dimension?.cacheWidth;
|
||||
break;
|
||||
}
|
||||
if (width != null && height != null) {
|
||||
cacheWidth = width <= height;
|
||||
}
|
||||
late final primary = theme.colorScheme.primary;
|
||||
|
||||
return Padding(
|
||||
|
||||
@@ -55,6 +55,7 @@ class _LocalIntroPanelState extends State<LocalIntroPanel>
|
||||
) {
|
||||
final outline = theme.colorScheme.outline;
|
||||
final cover = File(path.join(entry.entryDirPath, PathUtils.coverName));
|
||||
final cacheWidth = entry.pageData?.cacheWidth ?? false;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: SizedBox(
|
||||
@@ -87,7 +88,12 @@ class _LocalIntroPanelState extends State<LocalIntroPanel>
|
||||
width: 140.8,
|
||||
height: 88,
|
||||
fit: BoxFit.cover,
|
||||
cacheHeight: 140.8.cacheSize(context),
|
||||
cacheWidth: cacheWidth
|
||||
? 140.8.cacheSize(context)
|
||||
: null,
|
||||
cacheHeight: cacheWidth
|
||||
? null
|
||||
: 88.cacheSize(context),
|
||||
colorBlendMode: NetworkImgLayer.reduce
|
||||
? BlendMode.modulate
|
||||
: null,
|
||||
|
||||
Reference in New Issue
Block a user