mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-24 12:32:40 +08:00
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user