Merge branch 'main' into feature-m3Design
This commit is contained in:
@ -148,6 +148,15 @@ class Api {
|
|||||||
// 获取历史记录
|
// 获取历史记录
|
||||||
static const String historyList = '/x/web-interface/history/cursor';
|
static const String historyList = '/x/web-interface/history/cursor';
|
||||||
|
|
||||||
|
// 暂停历史记录
|
||||||
|
static const String pauseHistory = '/x/v2/history/shadow/set';
|
||||||
|
|
||||||
|
// 查询历史记录暂停状态
|
||||||
|
static const String historyStatus = '/x/v2/history/shadow?jsonp=jsonp';
|
||||||
|
|
||||||
|
// 清空历史记录
|
||||||
|
static const String clearHistory = '/x/v2/history/clear';
|
||||||
|
|
||||||
// 热搜
|
// 热搜
|
||||||
static const String hotSearchList =
|
static const String hotSearchList =
|
||||||
'https://s.search.bilibili.com/main/hotword';
|
'https://s.search.bilibili.com/main/hotword';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:pilipala/http/api.dart';
|
import 'package:pilipala/http/api.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
|
import 'package:pilipala/models/video/reply/data.dart';
|
||||||
|
|
||||||
class ReplyHttp {
|
class ReplyHttp {
|
||||||
static Future replyList({
|
static Future replyList({
|
||||||
@ -17,7 +18,7 @@ class ReplyHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': res.data['data'],
|
'data': ReplyData.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
Map errMap = {
|
Map errMap = {
|
||||||
|
@ -112,4 +112,36 @@ class UserHttp {
|
|||||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 暂停观看历史
|
||||||
|
static Future pauseHistory(bool switchStatus) async {
|
||||||
|
// 暂停switchStatus传true 否则false
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.pauseHistory,
|
||||||
|
queryParameters: {
|
||||||
|
'switch': switchStatus,
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 观看历史暂停状态
|
||||||
|
static Future historyStatus() async {
|
||||||
|
var res = await Request().get(Api.historyStatus);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空历史记录
|
||||||
|
static Future clearHistory() async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.clearHistory,
|
||||||
|
queryParameters: {
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import 'package:pilipala/pages/search/index.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/router/app_pages.dart';
|
import 'package:pilipala/router/app_pages.dart';
|
||||||
import 'package:pilipala/pages/main/view.dart';
|
import 'package:pilipala/pages/main/view.dart';
|
||||||
|
import 'package:pilipala/utils/data.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@ -16,6 +17,7 @@ void main() async {
|
|||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
await GStrorage.init();
|
await GStrorage.init();
|
||||||
await Request.setCookie();
|
await Request.setCookie();
|
||||||
|
await Data.init();
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/pages/fav/index.dart';
|
import 'package:pilipala/pages/fav/index.dart';
|
||||||
|
import 'package:pilipala/pages/fav/widgets/item.dart';
|
||||||
|
|
||||||
class FavPage extends StatefulWidget {
|
class FavPage extends StatefulWidget {
|
||||||
const FavPage({super.key});
|
const FavPage({super.key});
|
||||||
@ -18,7 +19,11 @@ class _FavPageState extends State<FavPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: const Text('我的收藏'),
|
titleSpacing: 0,
|
||||||
|
title: Text(
|
||||||
|
'我的收藏',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: _favController.queryFavFolder(),
|
future: _favController.queryFavFolder(),
|
||||||
@ -30,31 +35,9 @@ class _FavPageState extends State<FavPage> {
|
|||||||
() => ListView.builder(
|
() => ListView.builder(
|
||||||
itemCount: _favController.favFolderData.value.list!.length,
|
itemCount: _favController.favFolderData.value.list!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return ListTile(
|
return FavItem(
|
||||||
onTap: () => Get.toNamed(
|
favFolderItem:
|
||||||
'/favDetail',
|
_favController.favFolderData.value.list![index]);
|
||||||
arguments:
|
|
||||||
_favController.favFolderData.value.list![index],
|
|
||||||
parameters: {
|
|
||||||
'mediaId': _favController
|
|
||||||
.favFolderData.value.list![index].id
|
|
||||||
.toString(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
leading: const Icon(Icons.folder_special_outlined),
|
|
||||||
minLeadingWidth: 0,
|
|
||||||
title: Text(_favController
|
|
||||||
.favFolderData.value.list![index].title!),
|
|
||||||
subtitle: Text(
|
|
||||||
'${_favController.favFolderData.value.list![index].mediaCount}个内容',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelSmall!
|
|
||||||
.fontSize),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
96
lib/pages/fav/widgets/item.dart
Normal file
96
lib/pages/fav/widgets/item.dart
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
class FavItem extends StatelessWidget {
|
||||||
|
var favFolderItem;
|
||||||
|
FavItem({super.key, required this.favFolderItem});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
String heroTag = Utils.makeHeroTag(favFolderItem.fid);
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => Get.toNamed(
|
||||||
|
'/favDetail',
|
||||||
|
arguments: favFolderItem,
|
||||||
|
parameters: {
|
||||||
|
'heroTag': heroTag,
|
||||||
|
'mediaId': favFolderItem.id.toString(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 7, 12, 7),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, boxConstraints) {
|
||||||
|
double width =
|
||||||
|
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||||
|
return SizedBox(
|
||||||
|
height: width / StyleString.aspectRatio,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: StyleString.aspectRatio,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, boxConstraints) {
|
||||||
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
|
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
return Hero(
|
||||||
|
tag: heroTag,
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: favFolderItem.cover + '@.webp',
|
||||||
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VideoContent(favFolderItem: favFolderItem)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoContent extends StatelessWidget {
|
||||||
|
final favFolderItem;
|
||||||
|
const VideoContent({super.key, required this.favFolderItem});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
favFolderItem.title,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||||
|
fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${favFolderItem.mediaCount}个内容',
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,30 +4,42 @@ import 'package:pilipala/http/user.dart';
|
|||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/user/fav_detail.dart';
|
import 'package:pilipala/models/user/fav_detail.dart';
|
||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
|
||||||
|
|
||||||
class FavDetailController extends GetxController {
|
class FavDetailController extends GetxController {
|
||||||
FavFolderItemData? item;
|
FavFolderItemData? item;
|
||||||
Rx<FavDetailData> favDetailData = FavDetailData().obs;
|
Rx<FavDetailData> favDetailData = FavDetailData().obs;
|
||||||
|
|
||||||
int? mediaId;
|
int? mediaId;
|
||||||
|
late String heroTag;
|
||||||
|
int currentPage = 1;
|
||||||
|
bool isLoadingMore = false;
|
||||||
|
RxMap favInfo = {}.obs;
|
||||||
|
RxList<FavDetailItemData> favList = [FavDetailItemData()].obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
item = Get.arguments;
|
item = Get.arguments;
|
||||||
if (Get.parameters.keys.isNotEmpty) {
|
if (Get.parameters.keys.isNotEmpty) {
|
||||||
mediaId = int.parse(Get.parameters['mediaId']!);
|
mediaId = int.parse(Get.parameters['mediaId']!);
|
||||||
|
heroTag = Get.parameters['heroTag']!;
|
||||||
}
|
}
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> queryUserFavFolderDetail() async {
|
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
||||||
print('🐯🐯虎');
|
|
||||||
var res = await await UserHttp.userFavFolderDetail(
|
var res = await await UserHttp.userFavFolderDetail(
|
||||||
pn: 1,
|
pn: currentPage,
|
||||||
ps: 15,
|
ps: 20,
|
||||||
mediaId: mediaId!,
|
mediaId: mediaId!,
|
||||||
);
|
);
|
||||||
favDetailData.value = res['data'];
|
if (res['status']) {
|
||||||
|
favInfo.value = res['data'].info;
|
||||||
|
if (currentPage == 1 && type == 'init') {
|
||||||
|
favList.value = res['data'].medias;
|
||||||
|
} else if (type == 'onload') {
|
||||||
|
favList.addAll(res['data'].medias);
|
||||||
|
}
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,4 +61,8 @@ class FavDetailController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
queryUserFavFolderDetail(type: 'onload');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,14 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
} else if (_controller.offset <= 160) {
|
} else if (_controller.offset <= 160) {
|
||||||
titleStreamC.add(false);
|
titleStreamC.add(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_controller.position.pixels >=
|
||||||
|
_controller.position.maxScrollExtent - 200) {
|
||||||
|
if (!_favDetailController.isLoadingMore) {
|
||||||
|
_favDetailController.isLoadingMore = true;
|
||||||
|
_favDetailController.onLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -109,9 +117,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Hero(
|
||||||
width: 180,
|
tag: _favDetailController.heroTag,
|
||||||
height: 110,
|
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
width: 180,
|
width: 180,
|
||||||
height: 110,
|
height: 110,
|
||||||
@ -156,7 +163,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'共${_favDetailController.favDetailData.value.medias != null ? _favDetailController.favDetailData.value.medias!.length : '-'}条视频',
|
'共${_favDetailController.favInfo['media_count'] ?? '-'}条视频',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize:
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
@ -184,12 +191,9 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
() => SliverList(
|
() => SliverList(
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
return FavVideoCardH(
|
return FavVideoCardH(
|
||||||
videoItem: _favDetailController
|
videoItem: _favDetailController.favList[index],
|
||||||
.favDetailData.value.medias![index],
|
|
||||||
);
|
);
|
||||||
},
|
}, childCount: _favDetailController.favList.length),
|
||||||
childCount: _favDetailController
|
|
||||||
.favDetailData.value.medias!.length),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
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:hive/hive.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/models/user/history.dart';
|
import 'package:pilipala/models/user/history.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class HistoryController extends GetxController {
|
class HistoryController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
RxList<HisListItem> historyList = [HisListItem()].obs;
|
RxList<HisListItem> historyList = [HisListItem()].obs;
|
||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
|
RxBool pauseStatus = false.obs;
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
historyStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryHistoryList({type = 'init'}) async {
|
Future queryHistoryList({type = 'init'}) async {
|
||||||
@ -40,4 +46,77 @@ class HistoryController extends GetxController {
|
|||||||
Future onRefresh() async {
|
Future onRefresh() async {
|
||||||
queryHistoryList(type: 'onRefresh');
|
queryHistoryList(type: 'onRefresh');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 暂停观看历史
|
||||||
|
Future onPauseHistory() async {
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content:
|
||||||
|
Text(!pauseStatus.value ? '啊叻?你要暂停历史记录功能吗?' : '啊叻?要恢复历史记录功能吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: const Text('取消')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
SmartDialog.showLoading(msg: '请求中');
|
||||||
|
var res = await UserHttp.pauseHistory(!pauseStatus.value);
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
SmartDialog.showToast(
|
||||||
|
!pauseStatus.value ? '暂停观看历史' : '恢复观看历史');
|
||||||
|
pauseStatus.value = !pauseStatus.value;
|
||||||
|
}
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
},
|
||||||
|
child: Text(!pauseStatus.value ? '确认暂停' : '确认恢复'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 观看历史暂停状态
|
||||||
|
Future historyStatus() async {
|
||||||
|
var res = await UserHttp.historyStatus();
|
||||||
|
pauseStatus.value = res.data['data'];
|
||||||
|
localCache.put(LocalCacheKey.historyStatus, res.data['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空观看历史
|
||||||
|
Future onClearHistory() 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: const Text('取消')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
SmartDialog.showLoading(msg: '请求中');
|
||||||
|
var res = await UserHttp.clearHistory();
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
SmartDialog.showToast('清空观看历史');
|
||||||
|
}
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
historyList.clear();
|
||||||
|
},
|
||||||
|
child: const Text('确认清空'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,43 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('观看记录'),
|
titleSpacing: 0,
|
||||||
centerTitle: false,
|
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;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'pause',
|
||||||
|
child: Obx(
|
||||||
|
() => Text(!_historyController.pauseStatus.value
|
||||||
|
? '暂停观看记录'
|
||||||
|
: '恢复观看记录'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const PopupMenuItem<String>(
|
||||||
|
value: 'clear',
|
||||||
|
child: Text('清空观看记录'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
@ -57,13 +92,23 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
Map data = snapshot.data;
|
Map data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => SliverList(
|
() => _historyController.historyList.isEmpty
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
? const SliverToBoxAdapter(
|
||||||
return HistoryItem(
|
child: Center(
|
||||||
videoItem: _historyController.historyList[index],
|
child: Text('没数据'),
|
||||||
);
|
),
|
||||||
}, childCount: _historyController.historyList.length),
|
)
|
||||||
),
|
: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
return HistoryItem(
|
||||||
|
videoItem:
|
||||||
|
_historyController.historyList[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount:
|
||||||
|
_historyController.historyList.length),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return HttpError(
|
return HttpError(
|
||||||
|
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class MediaPage extends StatelessWidget {
|
class MediaPage extends StatelessWidget {
|
||||||
const MediaPage({super.key});
|
const MediaPage({super.key});
|
||||||
@ -169,12 +170,14 @@ class FavFolderItem extends StatelessWidget {
|
|||||||
int? index;
|
int? index;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
String heroTag = Utils.makeHeroTag(item!.fid);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14),
|
margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => Get.toNamed('/favDetail', arguments: item, parameters: {
|
onTap: () => Get.toNamed('/favDetail',
|
||||||
'mediaId': item!.id.toString(),
|
arguments: item,
|
||||||
}),
|
parameters: {'mediaId': item!.id.toString(), 'heroTag': heroTag}),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -199,10 +202,13 @@ class FavFolderItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, BoxConstraints box) {
|
builder: (context, BoxConstraints box) {
|
||||||
return NetworkImgLayer(
|
return Hero(
|
||||||
src: item!.cover,
|
tag: heroTag,
|
||||||
width: box.maxWidth,
|
child: NetworkImgLayer(
|
||||||
height: box.maxHeight,
|
src: item!.cover,
|
||||||
|
width: box.maxWidth,
|
||||||
|
height: box.maxHeight,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -54,6 +54,7 @@ class VideoDetailController extends GetxController
|
|||||||
|
|
||||||
RxString bgCover = ''.obs;
|
RxString bgCover = ''.obs;
|
||||||
Box user = GStrorage.user;
|
Box user = GStrorage.user;
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -149,6 +150,9 @@ class VideoDetailController extends GetxController
|
|||||||
if (user.get(UserBoxKey.userMid) == null) {
|
if (user.get(UserBoxKey.userMid) == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (localCache.get(LocalCacheKey.historyStatus) == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Duration progress = meeduPlayerController.position.value;
|
Duration progress = meeduPlayerController.position.value;
|
||||||
await VideoHttp.heartBeat(
|
await VideoHttp.heartBeat(
|
||||||
bvid: bvid,
|
bvid: bvid,
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
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:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
|
||||||
import 'package:pilipala/models/common/reply_sort_type.dart';
|
import 'package:pilipala/models/common/reply_sort_type.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/data.dart';
|
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
|
|
||||||
class VideoReplyController extends GetxController {
|
class VideoReplyController extends GetxController {
|
||||||
VideoReplyController(
|
VideoReplyController(
|
||||||
this.aid,
|
this.aid,
|
||||||
this.rpid,
|
this.rpid,
|
||||||
this.level,
|
this.replyLevel,
|
||||||
);
|
);
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
// 视频aid 请求时使用的oid
|
// 视频aid 请求时使用的oid
|
||||||
int? aid;
|
int? aid;
|
||||||
// 层级 2为楼中楼
|
// 层级 2为楼中楼
|
||||||
String? level;
|
String? replyLevel;
|
||||||
// rpid 请求楼中楼回复
|
// rpid 请求楼中楼回复
|
||||||
String? rpid;
|
String? rpid;
|
||||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||||
@ -30,12 +25,6 @@ class VideoReplyController extends GetxController {
|
|||||||
RxString noMore = ''.obs;
|
RxString noMore = ''.obs;
|
||||||
// 当前回复的回复
|
// 当前回复的回复
|
||||||
ReplyItemModel? currentReplyItem;
|
ReplyItemModel? currentReplyItem;
|
||||||
// 回复来源
|
|
||||||
String replySource = 'main';
|
|
||||||
// 根评论 id 回复楼中楼回复使用
|
|
||||||
int? rPid;
|
|
||||||
// 默认回复主楼
|
|
||||||
String replyLevel = '0';
|
|
||||||
|
|
||||||
ReplySortType sortType = ReplySortType.time;
|
ReplySortType sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
@ -43,51 +32,44 @@ class VideoReplyController extends GetxController {
|
|||||||
|
|
||||||
Future queryReplyList({type = 'init'}) async {
|
Future queryReplyList({type = 'init'}) async {
|
||||||
isLoadingMore = true;
|
isLoadingMore = true;
|
||||||
var res = level == '1'
|
var res = replyLevel == '1'
|
||||||
? await ReplyHttp.replyList(
|
? await ReplyHttp.replyList(
|
||||||
oid: aid!,
|
oid: aid!,
|
||||||
pageNum: currentPage + 1,
|
pageNum: ++currentPage,
|
||||||
type: ReplyType.video.index,
|
type: ReplyType.video.index,
|
||||||
sort: sortType.index,
|
sort: sortType.index,
|
||||||
)
|
)
|
||||||
: await ReplyHttp.replyReplyList(
|
: await ReplyHttp.replyReplyList(
|
||||||
oid: aid!, root: rpid!, pageNum: currentPage + 1, type: 1);
|
oid: aid!,
|
||||||
|
root: rpid!,
|
||||||
|
pageNum: ++currentPage,
|
||||||
|
type: ReplyType.video.index,
|
||||||
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
res['data'] = ReplyData.fromJson(res['data']);
|
List<ReplyItemModel> replies = res['data'].replies;
|
||||||
if (res['data'].replies.isNotEmpty) {
|
if (replies.isNotEmpty) {
|
||||||
currentPage = currentPage + 1;
|
|
||||||
noMore.value = '加载中';
|
noMore.value = '加载中';
|
||||||
if (replyList.length == res['data'].page.acount) {
|
if (replyList.length == res['data'].page.acount) {
|
||||||
noMore.value = '没有更多了';
|
noMore.value = '没有更多了';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (currentPage == 0) {
|
// 未登录状态replies可能返回null
|
||||||
noMore.value = '还没有评论';
|
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
|
||||||
} else {
|
|
||||||
noMore.value = '没有更多了';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
List<ReplyItemModel> replies = res['data'].replies;
|
|
||||||
// 添加置顶回复
|
// 添加置顶回复
|
||||||
if (res['data'].upper.top != null) {
|
if (res['data'].upper.top != null) {
|
||||||
bool flag = false;
|
bool flag = res['data']
|
||||||
for (var i = 0; i < res['data'].topReplies.length; i++) {
|
.topReplies
|
||||||
if (res['data'].topReplies[i].rpid == res['data'].upper.top.rpid) {
|
.any((reply) => reply.rpid == res['data'].upper.top.rpid);
|
||||||
flag = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!flag) {
|
if (!flag) {
|
||||||
replies.insert(0, res['data'].upper.top);
|
replies.insert(0, res['data'].upper.top);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replies.insertAll(0, res['data'].topReplies);
|
replies.insertAll(0, res['data'].topReplies);
|
||||||
res['data'].replies = replies;
|
replyList.value = replies;
|
||||||
replyList.value = res['data'].replies!;
|
|
||||||
} else {
|
} else {
|
||||||
replyList.addAll(res['data'].replies!);
|
replyList.addAll(replies);
|
||||||
res['data'].replies.addAll(replyList);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isLoadingMore = false;
|
isLoadingMore = false;
|
||||||
|
@ -6,7 +6,6 @@ import 'package:get/get.dart';
|
|||||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
@ -14,14 +13,14 @@ import 'controller.dart';
|
|||||||
import 'widgets/reply_item.dart';
|
import 'widgets/reply_item.dart';
|
||||||
|
|
||||||
class VideoReplyPanel extends StatefulWidget {
|
class VideoReplyPanel extends StatefulWidget {
|
||||||
String? bvid;
|
final String? bvid;
|
||||||
int rpid;
|
final int rpid;
|
||||||
String? level;
|
final String? replyLevel;
|
||||||
Key? key;
|
|
||||||
VideoReplyPanel({
|
const VideoReplyPanel({
|
||||||
this.bvid,
|
this.bvid,
|
||||||
this.rpid = 0,
|
this.rpid = 0,
|
||||||
this.level,
|
this.replyLevel,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,18 +45,13 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
void initState() {
|
void initState() {
|
||||||
int oid = widget.bvid != null ? IdUtils.bv2av(widget.bvid!) : 0;
|
int oid = widget.bvid != null ? IdUtils.bv2av(widget.bvid!) : 0;
|
||||||
super.initState();
|
super.initState();
|
||||||
replyLevel = widget.level ?? '1';
|
replyLevel = widget.replyLevel ?? '1';
|
||||||
if (widget.level != null && widget.level == '2') {
|
if (replyLevel == '2') {
|
||||||
_videoReplyController = Get.put(
|
_videoReplyController = Get.put(
|
||||||
VideoReplyController(oid, widget.rpid.toString(), '2'),
|
VideoReplyController(oid, widget.rpid.toString(), replyLevel),
|
||||||
tag: widget.rpid.toString());
|
tag: widget.rpid.toString());
|
||||||
_videoReplyController.rPid = widget.rpid;
|
|
||||||
} else {
|
} else {
|
||||||
// fix 评论加载不对称
|
_videoReplyController = Get.put(VideoReplyController(oid, '', replyLevel),
|
||||||
// int oid = Get.parameters['bvid'] != null
|
|
||||||
// ? IdUtils.bv2av(Get.parameters['bvid']!)
|
|
||||||
// : 0;
|
|
||||||
_videoReplyController = Get.put(VideoReplyController(oid, '', '1'),
|
|
||||||
tag: Get.arguments['heroTag']);
|
tag: Get.arguments['heroTag']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,20 +95,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showReply(source, {ReplyItemModel? replyItem, replyLevel}) async {
|
|
||||||
// source main 直接回复 floor 楼中楼回复
|
|
||||||
if (source == 'floor') {
|
|
||||||
_videoReplyController.currentReplyItem = replyItem;
|
|
||||||
_videoReplyController.replySource = source;
|
|
||||||
_videoReplyController.replyLevel = replyLevel ?? '1';
|
|
||||||
} else {
|
|
||||||
_videoReplyController.replyLevel = '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// await Future.delayed(const Duration(microseconds: 100));
|
|
||||||
// _videoReplyController.wakeUpReply();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 展示二级回复
|
// 展示二级回复
|
||||||
void replyReply(replyItem) {
|
void replyReply(replyItem) {
|
||||||
VideoDetailController videoDetailCtr =
|
VideoDetailController videoDetailCtr =
|
||||||
@ -136,7 +116,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
setState(() {});
|
|
||||||
_videoReplyController.currentPage = 0;
|
_videoReplyController.currentPage = 0;
|
||||||
return await _videoReplyController.queryReplyList();
|
return await _videoReplyController.queryReplyList();
|
||||||
},
|
},
|
||||||
@ -204,18 +183,15 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
: SliverList(
|
: SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(context, index) {
|
(context, index) {
|
||||||
|
double bottom =
|
||||||
|
MediaQuery.of(context).padding.bottom;
|
||||||
if (index ==
|
if (index ==
|
||||||
_videoReplyController
|
_videoReplyController
|
||||||
.replyList.length) {
|
.replyList.length) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(
|
padding:
|
||||||
bottom: MediaQuery.of(context)
|
EdgeInsets.only(bottom: bottom),
|
||||||
.padding
|
height: bottom + 100,
|
||||||
.bottom),
|
|
||||||
height: MediaQuery.of(context)
|
|
||||||
.padding
|
|
||||||
.bottom +
|
|
||||||
100,
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Obx(() => Text(
|
child: Obx(() => Text(
|
||||||
_videoReplyController
|
_videoReplyController
|
||||||
@ -265,8 +241,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: Tween<Offset>(
|
position: Tween<Offset>(
|
||||||
begin: const Offset(0, 2),
|
begin: const Offset(0, 2),
|
||||||
// 评论内容为空/不足一屏
|
|
||||||
// begin: const Offset(0, 0),
|
|
||||||
end: const Offset(0, 0),
|
end: const Offset(0, 0),
|
||||||
).animate(CurvedAnimation(
|
).animate(CurvedAnimation(
|
||||||
parent: fabAnimationCtr,
|
parent: fabAnimationCtr,
|
||||||
|
@ -21,11 +21,6 @@ class VideoReplyReplyController extends GetxController {
|
|||||||
// 当前回复的回复
|
// 当前回复的回复
|
||||||
ReplyItemModel? currentReplyItem;
|
ReplyItemModel? currentReplyItem;
|
||||||
|
|
||||||
// 根评论 id 回复楼中楼回复使用
|
|
||||||
int? rPid;
|
|
||||||
// 默认回复主楼
|
|
||||||
String replyLevel = '0';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
@ -11,14 +11,14 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
class VideoReplyReplyPanel extends StatefulWidget {
|
class VideoReplyReplyPanel extends StatefulWidget {
|
||||||
int? oid;
|
final int? oid;
|
||||||
int? rpid;
|
final int? rpid;
|
||||||
Function? closePanel;
|
final Function? closePanel;
|
||||||
ReplyItemModel? firstFloor;
|
final ReplyItemModel? firstFloor;
|
||||||
String? source;
|
final String? source;
|
||||||
ReplyType? replyType;
|
final ReplyType? replyType;
|
||||||
|
|
||||||
VideoReplyReplyPanel({
|
const VideoReplyReplyPanel({
|
||||||
this.oid,
|
this.oid,
|
||||||
this.rpid,
|
this.rpid,
|
||||||
this.closePanel,
|
this.closePanel,
|
||||||
@ -91,7 +91,6 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_videoReplyReplyController.currentPage = 0;
|
_videoReplyReplyController.currentPage = 0;
|
||||||
_videoReplyReplyController.rPid = 0;
|
|
||||||
widget.closePanel!();
|
widget.closePanel!();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
|
16
lib/utils/data.dart
Normal file
16
lib/utils/data.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
|
|
||||||
|
import 'storage.dart';
|
||||||
|
|
||||||
|
class Data {
|
||||||
|
static Future init() async {
|
||||||
|
await historyStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future historyStatus() async {
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
var res = await UserHttp.historyStatus();
|
||||||
|
localCache.put(LocalCacheKey.historyStatus, res.data['data']);
|
||||||
|
}
|
||||||
|
}
|
@ -59,3 +59,8 @@ class UserBoxKey {
|
|||||||
class SettingBoxKey {
|
class SettingBoxKey {
|
||||||
static const String themeMode = 'themeMode';
|
static const String themeMode = 'themeMode';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LocalCacheKey {
|
||||||
|
// 历史记录暂停状态 默认false
|
||||||
|
static const String historyStatus = 'historyStatus';
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user