feat: 历史记录多选删除
This commit is contained in:
@ -79,6 +79,7 @@ class HisListItem {
|
||||
this.kid,
|
||||
this.tagName,
|
||||
this.liveStatus,
|
||||
this.checked,
|
||||
});
|
||||
|
||||
String? title;
|
||||
@ -105,6 +106,7 @@ class HisListItem {
|
||||
int? kid;
|
||||
String? tagName;
|
||||
int? liveStatus;
|
||||
bool? checked;
|
||||
|
||||
HisListItem.fromJson(Map<String, dynamic> json) {
|
||||
title = json['title'];
|
||||
@ -131,6 +133,7 @@ class HisListItem {
|
||||
kid = json['kid'];
|
||||
tagName = json['tag_name'];
|
||||
liveStatus = json['live_status'];
|
||||
checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class HistoryController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
RxList<HisListItem> historyList = [HisListItem()].obs;
|
||||
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||
RxBool isLoadingMore = false.obs;
|
||||
RxBool pauseStatus = false.obs;
|
||||
Box localCache = GStrorage.localCache;
|
||||
RxBool isLoading = false.obs;
|
||||
RxBool enableMultiple = false.obs;
|
||||
RxInt checkedCount = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -140,6 +142,7 @@ class HistoryController extends GetxController {
|
||||
|
||||
// 删除已看历史记录
|
||||
Future onDelHistory() async {
|
||||
/// TODO 优化
|
||||
List<HisListItem> result =
|
||||
historyList.where((e) => e.progress == -1).toList();
|
||||
for (HisListItem i in result) {
|
||||
@ -149,4 +152,46 @@ class HistoryController extends GetxController {
|
||||
}
|
||||
SmartDialog.showToast('操作完成');
|
||||
}
|
||||
|
||||
// 删除选中的记录
|
||||
Future onDelCheckedHistory() async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('确认删除所选历史记录吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
/// TODO 优化
|
||||
await SmartDialog.dismiss();
|
||||
SmartDialog.showLoading(msg: '请求中');
|
||||
List<HisListItem> result =
|
||||
historyList.where((e) => e.checked!).toList();
|
||||
for (HisListItem i in result) {
|
||||
String resKid = 'archive_${i.kid}';
|
||||
await UserHttp.delHistory(resKid);
|
||||
historyList.removeWhere((e) => e.kid == i.kid);
|
||||
}
|
||||
checkedCount.value = 0;
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,20 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
);
|
||||
}
|
||||
|
||||
// 选中
|
||||
onChoose(index) {
|
||||
_historyController.historyList[index].checked =
|
||||
!_historyController.historyList[index].checked!;
|
||||
_historyController.checkedCount.value =
|
||||
_historyController.historyList.where((item) => item.checked!).length;
|
||||
_historyController.historyList.refresh();
|
||||
}
|
||||
|
||||
// 更新多选状态
|
||||
onUpdateMultiple() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.removeListener(() {});
|
||||
@ -48,51 +62,115 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
title: Text(
|
||||
'观看记录',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (String type) {
|
||||
// 处理菜单项选择的逻辑
|
||||
switch (type) {
|
||||
case 'pause':
|
||||
_historyController.onPauseHistory();
|
||||
break;
|
||||
case 'clear':
|
||||
_historyController.onClearHistory();
|
||||
break;
|
||||
case 'del':
|
||||
_historyController.onDelHistory();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'pause',
|
||||
child: Obx(
|
||||
() => Text(!_historyController.pauseStatus.value
|
||||
? '暂停观看记录'
|
||||
: '恢复观看记录'),
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'clear',
|
||||
child: Text('清空观看记录'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'del',
|
||||
child: Text('删除已看记录'),
|
||||
),
|
||||
],
|
||||
appBar: AppBarWidget(
|
||||
visible: _historyController.enableMultiple.value,
|
||||
child1: AppBar(
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
leading: IconButton(
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.arrow_back_outlined),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
title: Text(
|
||||
'观看记录',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
actions: [
|
||||
// TextButton(
|
||||
// onPressed: () {
|
||||
// _historyController.enableMultiple.value = true;
|
||||
// setState(() {});
|
||||
// },
|
||||
// child: const Text('多选'),
|
||||
// ),
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (String type) {
|
||||
// 处理菜单项选择的逻辑
|
||||
switch (type) {
|
||||
case 'pause':
|
||||
_historyController.onPauseHistory();
|
||||
break;
|
||||
case 'clear':
|
||||
_historyController.onClearHistory();
|
||||
break;
|
||||
case 'del':
|
||||
_historyController.onDelHistory();
|
||||
break;
|
||||
case 'multiple':
|
||||
_historyController.enableMultiple.value = true;
|
||||
setState(() {});
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'pause',
|
||||
child: Obx(
|
||||
() => Text(!_historyController.pauseStatus.value
|
||||
? '暂停观看记录'
|
||||
: '恢复观看记录'),
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'clear',
|
||||
child: Text('清空观看记录'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'del',
|
||||
child: Text('删除已看记录'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'multiple',
|
||||
child: Text('多选删除'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
),
|
||||
child2: AppBar(
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
_historyController.enableMultiple.value = false;
|
||||
for (var item in _historyController.historyList) {
|
||||
item.checked = false;
|
||||
}
|
||||
_historyController.checkedCount.value = 0;
|
||||
setState(() {});
|
||||
},
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
),
|
||||
title: Obx(
|
||||
() => Text(
|
||||
'已选择${_historyController.checkedCount.value}项',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
for (var item in _historyController.historyList) {
|
||||
item.checked = true;
|
||||
}
|
||||
_historyController.checkedCount.value =
|
||||
_historyController.historyList.length;
|
||||
_historyController.historyList.refresh();
|
||||
},
|
||||
child: const Text('全选'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _historyController.onDelCheckedHistory(),
|
||||
child: Text(
|
||||
'删除',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
@ -120,6 +198,8 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
videoItem:
|
||||
_historyController.historyList[index],
|
||||
ctr: _historyController,
|
||||
onChoose: () => onChoose(index),
|
||||
onUpdateMultiple: () => onUpdateMultiple(),
|
||||
);
|
||||
},
|
||||
childCount:
|
||||
@ -155,6 +235,36 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
// bottomNavigationBar: BottomAppBar(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
|
||||
const AppBarWidget({
|
||||
required this.child1,
|
||||
required this.child2,
|
||||
required this.visible,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final PreferredSizeWidget child1;
|
||||
final PreferredSizeWidget child2;
|
||||
final bool visible;
|
||||
@override
|
||||
Size get preferredSize => child1.preferredSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: !visible ? child1 : child2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,22 @@ import 'package:pilipala/models/common/business_type.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
import 'package:pilipala/pages/history/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class HistoryItem extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final HistoryController? ctr;
|
||||
const HistoryItem({super.key, required this.videoItem, this.ctr});
|
||||
final Function? onChoose;
|
||||
final Function? onUpdateMultiple;
|
||||
const HistoryItem({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.ctr,
|
||||
this.onChoose,
|
||||
this.onUpdateMultiple,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -27,6 +36,11 @@ class HistoryItem extends StatelessWidget {
|
||||
String heroTag = Utils.makeHeroTag(aid);
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
if (ctr!.enableMultiple.value) {
|
||||
feedBack();
|
||||
onChoose!();
|
||||
return;
|
||||
}
|
||||
if (videoItem.history.business.contains('article')) {
|
||||
int cid = videoItem.history.cid ??
|
||||
// videoItem.history.oid ??
|
||||
@ -117,6 +131,14 @@ class HistoryItem extends StatelessWidget {
|
||||
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (!ctr!.enableMultiple.value) {
|
||||
feedBack();
|
||||
ctr!.enableMultiple.value = true;
|
||||
onChoose!();
|
||||
onUpdateMultiple!();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
@ -132,51 +154,103 @@ class HistoryItem extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: (videoItem.cover != ''
|
||||
? videoItem.cover
|
||||
: videoItem.covers.first),
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: (videoItem.cover != ''
|
||||
? videoItem.cover
|
||||
: videoItem.covers.first),
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
if (!BusinessType
|
||||
.hiddenDurationType.hiddenDurationType
|
||||
.contains(videoItem.history.business))
|
||||
PBadge(
|
||||
text: videoItem.progress == -1
|
||||
? '已看完'
|
||||
: '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}',
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
// 右上角
|
||||
if (BusinessType.showBadge.showBadge
|
||||
.contains(
|
||||
videoItem.history.business) ||
|
||||
videoItem.history.business ==
|
||||
BusinessType.live.type)
|
||||
PBadge(
|
||||
text: videoItem.badge,
|
||||
top: 6.0,
|
||||
right: 6.0,
|
||||
bottom: null,
|
||||
left: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Positioned.fill(
|
||||
child: AnimatedOpacity(
|
||||
opacity: ctr!.enableMultiple.value ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.black.withOpacity(0.6),
|
||||
),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith(
|
||||
(states) {
|
||||
return Colors.white
|
||||
.withOpacity(0.8);
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
onChoose!();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.done_all_outlined,
|
||||
color: videoItem.checked
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!BusinessType
|
||||
.hiddenDurationType.hiddenDurationType
|
||||
.contains(videoItem.history.business))
|
||||
PBadge(
|
||||
text: videoItem.progress == -1
|
||||
? '已看完'
|
||||
: '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}',
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
// 右上角
|
||||
if (BusinessType.showBadge.showBadge
|
||||
.contains(videoItem.history.business) ||
|
||||
videoItem.history.business ==
|
||||
BusinessType.live.type)
|
||||
PBadge(
|
||||
text: videoItem.badge,
|
||||
top: 6.0,
|
||||
right: 6.0,
|
||||
bottom: null,
|
||||
left: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
VideoContent(videoItem: videoItem, ctr: ctr)
|
||||
],
|
||||
|
Reference in New Issue
Block a user