mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-28 03:58:39 +00:00
63
lib/pages/member_video_web/archive/controller.dart
Normal file
63
lib/pages/member_video_web/archive/controller.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/member.dart';
|
||||
import 'package:PiliPlus/models/common/member/archive_order_type_web.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/data.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/slist.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/vlist.dart';
|
||||
import 'package:PiliPlus/pages/member_video_web/base/controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberVideoWebCtr
|
||||
extends
|
||||
BaseVideoWebCtr<
|
||||
SearchArchiveData,
|
||||
VListItemModel,
|
||||
ArchiveOrderTypeWeb
|
||||
> {
|
||||
int? _totalCount;
|
||||
@override
|
||||
final Rx<ArchiveOrderTypeWeb> order = Rx(.pubdate);
|
||||
|
||||
int tid = 0;
|
||||
String? specialType;
|
||||
List<ListTag>? tags;
|
||||
|
||||
@override
|
||||
List<VListItemModel>? getDataList(SearchArchiveData response) {
|
||||
return response.list?.vlist;
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(
|
||||
bool isRefresh,
|
||||
Success<SearchArchiveData> response,
|
||||
) {
|
||||
if (isRefresh) {
|
||||
final data = response.response;
|
||||
if (data.page?.count case final count?) {
|
||||
if (tid == 0 && specialType == null) {
|
||||
_totalCount = count;
|
||||
}
|
||||
this.count = count;
|
||||
totalPage = (count / ps).ceil();
|
||||
}
|
||||
final tags = data.list?.tags;
|
||||
if (tags?.isNotEmpty ?? false) {
|
||||
this.tags = tags!
|
||||
..insert(0, ListTag(tid: 0, name: '全部类型', count: _totalCount));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState<SearchArchiveData>> customGetData() =>
|
||||
MemberHttp.searchArchive(
|
||||
mid: mid,
|
||||
ps: ps,
|
||||
pn: page,
|
||||
order: order.value,
|
||||
tid: tid,
|
||||
specialType: specialType,
|
||||
);
|
||||
}
|
||||
87
lib/pages/member_video_web/archive/view.dart
Normal file
87
lib/pages/member_video_web/archive/view.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
|
||||
import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart';
|
||||
import 'package:PiliPlus/models/common/member/archive_order_type_web.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/data.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/vlist.dart';
|
||||
import 'package:PiliPlus/pages/member_video_web/archive/controller.dart';
|
||||
import 'package:PiliPlus/pages/member_video_web/base/view.dart';
|
||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberVideoWeb extends StatefulWidget {
|
||||
const MemberVideoWeb({super.key});
|
||||
|
||||
@override
|
||||
State<MemberVideoWeb> createState() => _MemberVideoWebState();
|
||||
|
||||
static Future<void>? toMemberVideoWeb({
|
||||
required Object mid,
|
||||
required String name,
|
||||
}) {
|
||||
return Get.toNamed(
|
||||
'/videoWeb',
|
||||
arguments: {
|
||||
'mid': mid,
|
||||
'name': name,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MemberVideoWebState
|
||||
extends
|
||||
BaseVideoWebState<
|
||||
MemberVideoWeb,
|
||||
SearchArchiveData,
|
||||
VListItemModel,
|
||||
ArchiveOrderTypeWeb
|
||||
>
|
||||
with GridMixin {
|
||||
@override
|
||||
late final MemberVideoWebCtr controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.put(MemberVideoWebCtr(), tag: name);
|
||||
}
|
||||
|
||||
@override
|
||||
List<ArchiveOrderTypeWeb> get values => ArchiveOrderTypeWeb.values;
|
||||
|
||||
@override
|
||||
Widget? buildTags(ColorScheme colorScheme) {
|
||||
if (controller.tags case final tags?) {
|
||||
return SliverPinnedHeader(
|
||||
backgroundColor: colorScheme.surface,
|
||||
child: SelfSizedHorizontalList(
|
||||
itemCount: tags.length,
|
||||
padding: const .fromLTRB(10, 0, 10, 8),
|
||||
itemBuilder: (context, index) {
|
||||
final item = tags[index];
|
||||
final isCurr = controller.specialType != null
|
||||
? item.specialType == controller.specialType
|
||||
: item.tid == controller.tid;
|
||||
return SearchText(
|
||||
padding: const .symmetric(horizontal: 8, vertical: 4),
|
||||
text: '${item.name!} ${item.count}',
|
||||
bgColor: isCurr ? colorScheme.secondaryContainer : null,
|
||||
textColor: isCurr ? colorScheme.onSecondaryContainer : null,
|
||||
onTap: (_) {
|
||||
if (isCurr) return;
|
||||
controller
|
||||
..tid = item.tid ?? 0
|
||||
..specialType = item.specialType
|
||||
..onReload();
|
||||
},
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
50
lib/pages/member_video_web/base/controller.dart
Normal file
50
lib/pages/member_video_web/base/controller.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart' show ReloadMixin;
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
const int ps = 30;
|
||||
|
||||
abstract class BaseVideoWebCtr<R, T, V> extends CommonListController<R, T>
|
||||
with ReloadMixin {
|
||||
final Object mid = Get.arguments['mid'];
|
||||
|
||||
int? totalPage;
|
||||
int? count;
|
||||
Rx<V> get order;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
void checkIsEnd(int length) {
|
||||
if (totalPage != null && page >= totalPage!) {
|
||||
isEnd = true;
|
||||
} else if (count != null && length >= count!) {
|
||||
isEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
void queryBySort(V value) {
|
||||
if (isLoading) return;
|
||||
order.value = value;
|
||||
onReload();
|
||||
}
|
||||
|
||||
void jumpToPage(int page) {
|
||||
isEnd = false;
|
||||
reload = true;
|
||||
this.page = page;
|
||||
loadingState.value = LoadingState.loading();
|
||||
queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onReload() {
|
||||
reload = true;
|
||||
return super.onReload();
|
||||
}
|
||||
}
|
||||
217
lib/pages/member_video_web/base/view.dart
Normal file
217
lib/pages/member_video_web/base/view.dart
Normal file
@@ -0,0 +1,217 @@
|
||||
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||
import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
import 'package:PiliPlus/models/model_video.dart';
|
||||
import 'package:PiliPlus/pages/member_video_web/base/controller.dart';
|
||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
abstract class BaseVideoWebState<
|
||||
S extends StatefulWidget,
|
||||
R,
|
||||
T extends BaseVideoItemModel,
|
||||
V extends EnumWithLabel
|
||||
>
|
||||
extends State<S>
|
||||
with GridMixin {
|
||||
late final String name;
|
||||
BaseVideoWebCtr<R, T, V> get controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final args = Get.arguments;
|
||||
name = args['name'];
|
||||
}
|
||||
|
||||
List<V> get values;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(name),
|
||||
actions: [
|
||||
Obx(
|
||||
() {
|
||||
final order = controller.order.value;
|
||||
return PopupMenuButton<V>(
|
||||
tooltip: '排序',
|
||||
icon: const Icon(Icons.sort),
|
||||
initialValue: order,
|
||||
onSelected: controller.queryBySort,
|
||||
itemBuilder: (_) => values
|
||||
.map((e) => PopupMenuItem(value: e, child: Text(e.label)))
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
),
|
||||
body: refreshIndicator(
|
||||
onRefresh: controller.onRefresh,
|
||||
child: CustomScrollView(
|
||||
physics: ReloadScrollPhysics(controller: controller),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: .only(
|
||||
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
|
||||
),
|
||||
sliver: Obx(
|
||||
() => buildBody(colorScheme, controller.loadingState.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBody(
|
||||
ColorScheme colorScheme,
|
||||
LoadingState<List<T>?> loadingState,
|
||||
) {
|
||||
return switch (loadingState) {
|
||||
Loading() => gridSkeleton,
|
||||
Success(:final response) =>
|
||||
response != null && response.isNotEmpty
|
||||
? SliverMainAxisGroup(
|
||||
slivers: [
|
||||
buildHeader(colorScheme),
|
||||
?buildTags(colorScheme),
|
||||
SliverGrid.builder(
|
||||
gridDelegate: gridDelegate,
|
||||
itemCount: response.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == response.length - 1) {
|
||||
controller.onLoadMore();
|
||||
}
|
||||
return VideoCardH(videoItem: response[index]);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: HttpError(onReload: controller.onReload),
|
||||
Error(:final errMsg) => HttpError(
|
||||
errMsg: errMsg,
|
||||
onReload: controller.onReload,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Widget? buildTags(ColorScheme colorScheme) => null;
|
||||
|
||||
Widget buildHeader(ColorScheme colorScheme) {
|
||||
return SliverPinnedHeader(
|
||||
backgroundColor: colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const .fromLTRB(14, 0, 8, 4),
|
||||
child: Stack(
|
||||
alignment: .centerLeft,
|
||||
children: [
|
||||
?buildCount(),
|
||||
Center(child: buildPageBtn(colorScheme)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? buildCount() {
|
||||
final count = controller.count;
|
||||
if (count == null) return null;
|
||||
return Text(
|
||||
'共 $count 视频',
|
||||
style: const TextStyle(height: 1),
|
||||
strutStyle: const StrutStyle(leading: 0, height: 1),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? buildPageBtn(ColorScheme colorScheme) {
|
||||
final totalPage = controller.totalPage;
|
||||
if (totalPage == null) return null;
|
||||
final page = controller.page - 1;
|
||||
final canBackward = page > 1;
|
||||
final canForward = page < totalPage;
|
||||
const size = 30.0;
|
||||
const iconSize = 24.0;
|
||||
|
||||
final backwardBtn = iconButton(
|
||||
size: size,
|
||||
iconSize: iconSize,
|
||||
tooltip: canBackward ? '上一页' : null,
|
||||
icon: const Icon(Icons.keyboard_arrow_left),
|
||||
onPressed: canBackward ? () => controller.jumpToPage(page - 1) : null,
|
||||
);
|
||||
|
||||
final forwardBtn = iconButton(
|
||||
size: size,
|
||||
iconSize: iconSize,
|
||||
tooltip: canForward ? '下一页' : null,
|
||||
icon: const Icon(Icons.keyboard_arrow_right),
|
||||
onPressed: canForward ? () => controller.jumpToPage(page + 1) : null,
|
||||
);
|
||||
|
||||
final pageIndicator = SearchText(
|
||||
height: 1,
|
||||
text: '$page / $totalPage',
|
||||
borderRadius: const .all(.circular(4)),
|
||||
padding: const .symmetric(horizontal: 10, vertical: 5),
|
||||
onTap: (_) => showJumpDialog(page),
|
||||
);
|
||||
|
||||
return Row(
|
||||
spacing: 6,
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
backwardBtn,
|
||||
pageIndicator,
|
||||
forwardBtn,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void showJumpDialog(int page) {
|
||||
var pageStr = page.toString();
|
||||
|
||||
void onSubmit([_]) {
|
||||
try {
|
||||
controller.jumpToPage(int.parse(pageStr));
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
title: const Text('跳至: '),
|
||||
content: TextFormField(
|
||||
autofocus: true,
|
||||
initialValue: pageStr,
|
||||
onChanged: (value) => pageStr = value,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '页数',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onFieldSubmitted: (_) {
|
||||
Get.back();
|
||||
onSubmit();
|
||||
},
|
||||
),
|
||||
onConfirm: onSubmit,
|
||||
);
|
||||
}
|
||||
}
|
||||
55
lib/pages/member_video_web/season_series/controller.dart
Normal file
55
lib/pages/member_video_web/season_series/controller.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/member.dart';
|
||||
import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart';
|
||||
import 'package:PiliPlus/models/common/member/web_ss_type.dart';
|
||||
import 'package:PiliPlus/models_new/member/season_web/archive.dart';
|
||||
import 'package:PiliPlus/models_new/member/season_web/data.dart';
|
||||
import 'package:PiliPlus/pages/member_video_web/base/controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberSSWebCtr
|
||||
extends BaseVideoWebCtr<SeasonWebData, SeasonArchive, ArchiveSortTypeApp> {
|
||||
@override
|
||||
final Rx<ArchiveSortTypeApp> order = Rx(.desc);
|
||||
late final WebSsType _type;
|
||||
late final Object _id;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
final args = Get.arguments;
|
||||
_type = args['type'];
|
||||
_id = args['id'];
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
List<SeasonArchive>? getDataList(SeasonWebData response) {
|
||||
return response.archives;
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(
|
||||
bool isRefresh,
|
||||
Success<SeasonWebData> response,
|
||||
) {
|
||||
if (isRefresh) {
|
||||
final data = response.response;
|
||||
if (data.page?.total case final total?) {
|
||||
count = total;
|
||||
totalPage = (total / ps).ceil();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState<SeasonWebData>> customGetData() =>
|
||||
MemberHttp.seasonSeriesWeb(
|
||||
type: _type,
|
||||
mid: mid,
|
||||
id: _id,
|
||||
ps: ps,
|
||||
pn: page,
|
||||
sort: order.value,
|
||||
);
|
||||
}
|
||||
55
lib/pages/member_video_web/season_series/view.dart
Normal file
55
lib/pages/member_video_web/season_series/view.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart';
|
||||
import 'package:PiliPlus/models/common/member/web_ss_type.dart';
|
||||
import 'package:PiliPlus/models_new/member/season_web/archive.dart';
|
||||
import 'package:PiliPlus/models_new/member/season_web/data.dart';
|
||||
import 'package:PiliPlus/pages/member_video_web/base/view.dart';
|
||||
import 'package:PiliPlus/pages/member_video_web/season_series/controller.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberSSWeb extends StatefulWidget {
|
||||
const MemberSSWeb({super.key});
|
||||
|
||||
@override
|
||||
State<MemberSSWeb> createState() => _MemberSSWebState();
|
||||
|
||||
static Future<void>? toMemberSSWeb({
|
||||
required WebSsType type,
|
||||
required Object id,
|
||||
required Object mid,
|
||||
required String name,
|
||||
}) {
|
||||
return Get.toNamed(
|
||||
'/ssWeb',
|
||||
arguments: {
|
||||
'type': type,
|
||||
'id': id,
|
||||
'mid': mid,
|
||||
'name': name,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MemberSSWebState
|
||||
extends
|
||||
BaseVideoWebState<
|
||||
MemberSSWeb,
|
||||
SeasonWebData,
|
||||
SeasonArchive,
|
||||
ArchiveSortTypeApp
|
||||
>
|
||||
with GridMixin {
|
||||
@override
|
||||
late final MemberSSWebCtr controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.put(MemberSSWebCtr(), tag: name);
|
||||
}
|
||||
|
||||
@override
|
||||
List<ArchiveSortTypeApp> get values => ArchiveSortTypeApp.values;
|
||||
}
|
||||
Reference in New Issue
Block a user