refa: query data (#659)

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
dom
2025-04-10 21:43:01 +08:00
committed by GitHub
parent 99b19e7b03
commit e1b73f4766
128 changed files with 1493 additions and 1987 deletions

View File

@@ -50,7 +50,7 @@ class BangumiHttp {
}
}
static Future<LoadingState> bangumiList({
static Future<LoadingState<List<BangumiListItemModel>?>> bangumiList({
int? page,
int? indexType,
}) async {
@@ -67,7 +67,7 @@ class BangumiHttp {
}
}
static Future<LoadingState> bangumiFollowList({
static Future<LoadingState<BangumiListDataModel>> bangumiFollowList({
required dynamic mid,
required int type,
required int pn,
@@ -80,12 +80,8 @@ class BangumiHttp {
'pn': pn,
});
if (res.data['code'] == 0) {
BangumiListDataModel data =
BangumiListDataModel.fromJson(res.data['data']);
if (followStatus != null) {
return LoadingState.success(data.list);
}
return LoadingState.success(data);
return LoadingState.success(
BangumiListDataModel.fromJson(res.data['data']));
} else {
return LoadingState.error(res.data['message']);
}

View File

@@ -4,7 +4,8 @@ import '../models/user/black.dart';
import 'index.dart';
class BlackHttp {
static Future<LoadingState> blackList({required int pn, int? ps}) async {
static Future<LoadingState<BlackListDataModel>> blackList(
{required int pn, int? ps}) async {
var res = await Request().get(Api.blackLst, queryParameters: {
'pn': pn,
'ps': ps ?? 50,

View File

@@ -8,7 +8,7 @@ import '../models/dynamics/up.dart';
import 'index.dart';
class DynamicsHttp {
static Future<LoadingState> followDynamic({
static Future<LoadingState<DynamicsDataModel>> followDynamic({
String? type,
String? offset,
int? mid,

View File

@@ -4,7 +4,7 @@ import '../models/fans/result.dart';
import 'index.dart';
class FanHttp {
static Future<LoadingState> fans(
static Future<LoadingState<FansDataModel>> fans(
{int? vmid, int? pn, int? ps, String? orderType}) async {
var res = await Request().get(Api.fans, queryParameters: {
'vmid': vmid,

View File

@@ -10,13 +10,13 @@ import 'api.dart';
import 'init.dart';
class LiveHttp {
static Future<LoadingState> liveList(
static Future<LoadingState<List<LiveItemModel>?>> liveList(
{int? vmid, int? pn, int? ps, String? orderType}) async {
var res = await Request().get(Api.liveList,
queryParameters: {'page': pn, 'page_size': 30, 'platform': 'web'});
if (res.data['code'] == 0) {
List<LiveItemModel> list = res.data['data']['list']
.map<LiveItemModel>((e) => LiveItemModel.fromJson(e))
List<LiveItemModel>? list = (res.data['data']?['list'] as List?)
?.map<LiveItemModel>((e) => LiveItemModel.fromJson(e))
.toList();
return LoadingState.success(list);
} else {

View File

@@ -6,6 +6,9 @@ import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space_archive/data.dart' as space_archive;
import 'package:PiliPlus/models/space_article/data.dart' as space_article;
import 'package:PiliPlus/models/space/data.dart' as space_;
import 'package:PiliPlus/models/space_fav/space_fav.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType;
@@ -62,7 +65,7 @@ class MemberHttp {
}
}
static Future<LoadingState> spaceArticle({
static Future<LoadingState<space_article.Data>> spaceArticle({
required int mid,
required int page,
}) async {
@@ -138,13 +141,13 @@ class MemberHttp {
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']['items_lists']);
return LoadingState.success(res.data['data']?['items_lists']);
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> spaceArchive({
static Future<LoadingState<space_archive.Data>> spaceArchive({
required ContributeType type,
required int? mid,
String? aid,
@@ -241,7 +244,7 @@ class MemberHttp {
}
}
static Future<LoadingState> space({
static Future<LoadingState<space_.Data>> space({
int? mid,
dynamic fromViewAid,
}) async {
@@ -391,7 +394,7 @@ class MemberHttp {
}
// 用户动态
static Future<LoadingState> memberDynamic({
static Future<LoadingState<DynamicsDataModel>> memberDynamic({
String? offset,
int? mid,
}) async {
@@ -601,7 +604,8 @@ class MemberHttp {
}
// 最近投币
static Future<LoadingState> getRecentCoinVideo({required int mid}) async {
static Future<LoadingState<List<MemberCoinsDataModel>?>> getRecentCoinVideo(
{required int mid}) async {
Map params = await WbiSign.makSign({
'mid': mid,
'gaia_source': 'main_web',
@@ -618,16 +622,18 @@ class MemberHttp {
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList());
List<MemberCoinsDataModel>? list = (res.data['data'] as List?)
?.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList();
return LoadingState.success(list);
} else {
return LoadingState.error(res.data['message']);
}
}
// 最近点赞
static Future<LoadingState> getRecentLikeVideo({required int mid}) async {
static Future<LoadingState<List<MemberCoinsDataModel>?>> getRecentLikeVideo(
{required int mid}) async {
Map params = await WbiSign.makSign({
'mid': mid,
'gaia_source': 'main_web',
@@ -644,9 +650,10 @@ class MemberHttp {
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']['list']
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList());
List<MemberCoinsDataModel>? list = (res.data['data']?['list'] as List?)
?.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
.toList();
return LoadingState.success(list);
} else {
return LoadingState.error(res.data['message']);
}

View File

@@ -17,7 +17,7 @@ import 'api.dart';
import 'init.dart';
class MsgHttp {
static Future<LoadingState> msgFeedReplyMe(
static Future<LoadingState<MsgFeedReplyMe>> msgFeedReplyMe(
{int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedReply, queryParameters: {
'id': cursor == -1 ? null : cursor,
@@ -34,7 +34,7 @@ class MsgHttp {
}
}
static Future<LoadingState> msgFeedAtMe(
static Future<LoadingState<MsgFeedAtMe>> msgFeedAtMe(
{int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedAt, queryParameters: {
'id': cursor == -1 ? null : cursor,
@@ -51,7 +51,7 @@ class MsgHttp {
}
}
static Future<LoadingState> msgFeedLikeMe(
static Future<LoadingState<MsgFeedLikeMe>> msgFeedLikeMe(
{int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedLike, queryParameters: {
'id': cursor == -1 ? null : cursor,
@@ -68,7 +68,7 @@ class MsgHttp {
}
}
static Future<LoadingState> msgFeedNotify(
static Future<LoadingState<List<SystemNotifyList>?>> msgFeedNotify(
{int cursor = -1, int pageSize = 20}) async {
var res = await Request().get(Api.msgSysNotify, queryParameters: {
'cursor': cursor == -1 ? null : cursor,

View File

@@ -124,7 +124,7 @@ class ReplyHttp {
}
}
static Future<LoadingState> replyListGrpc({
static Future<LoadingState<MainListReply>> replyListGrpc({
int type = 1,
required int oid,
required CursorReq cursor,
@@ -387,7 +387,8 @@ class ReplyHttp {
}
}
static Future<LoadingState> getEmoteList({String? business}) async {
static Future<LoadingState<List<Packages>?>> getEmoteList(
{String? business}) async {
var res = await Request().get(Api.myEmote, queryParameters: {
'business': business ?? 'reply',
'web_location': '333.1245',

View File

@@ -160,7 +160,8 @@ class SearchHttp {
}
}
static Future<LoadingState> bangumiInfoNew({int? seasonId, int? epId}) async {
static Future<LoadingState<BangumiInfoModel>> bangumiInfoNew(
{int? seasonId, int? epId}) async {
final dynamic res = await Request().get(
Api.bangumiInfo,
queryParameters: {

View File

@@ -46,7 +46,7 @@ class UserHttp {
}
// 收藏夹
static Future<LoadingState> userfavFolder({
static Future<LoadingState<FavFolderData>> userfavFolder({
required int pn,
required int ps,
required dynamic mid,
@@ -170,7 +170,7 @@ class UserHttp {
}
}
static Future<LoadingState> userFavFolderDetail(
static Future<LoadingState<FavDetailData>> userFavFolderDetail(
{required int mediaId,
required int pn,
required int ps,
@@ -195,7 +195,7 @@ class UserHttp {
}
// 稍后再看
static Future<LoadingState> seeYouLater() async {
static Future<LoadingState<Map>> seeYouLater() async {
var res = await Request().get(Api.seeYouLater);
if (res.data['code'] == 0) {
if (res.data['data']['count'] == 0) {
@@ -220,7 +220,7 @@ class UserHttp {
}
// 观看历史
static Future<LoadingState> historyList({
static Future<LoadingState<HistoryData>> historyList({
required String type,
int? max,
int? viewAt,
@@ -426,7 +426,7 @@ class UserHttp {
}
// 我的订阅
static Future<LoadingState> userSubFolder({
static Future<LoadingState<List<SubFolderItemData>?>> userSubFolder({
required int mid,
required int pn,
required int ps,

View File

@@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/member/article.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
@@ -145,7 +146,7 @@ class VideoHttp {
}
// 最热视频
static Future<LoadingState> hotVideoList(
static Future<LoadingState<List<HotVideoItemModel>>> hotVideoList(
{required int pn, required int ps}) async {
var res = await Request().get(
Api.hotList,
@@ -345,15 +346,16 @@ class VideoHttp {
}
// 相关视频
static Future<LoadingState> relatedVideoList({required String bvid}) async {
static Future<LoadingState<List<HotVideoItemModel>?>> relatedVideoList(
{required String bvid}) async {
var res =
await Request().get(Api.relatedList, queryParameters: {'bvid': bvid});
if (res.data['code'] == 0) {
final items =
(res.data['data'] as List).map((i) => HotVideoItemModel.fromJson(i));
final items = (res.data['data'] as List?)
?.map((i) => HotVideoItemModel.fromJson(i));
final list = RecommendFilter.applyFilterToRelatedVideos
? items.where((i) => !RecommendFilter.filterAll(i)).toList()
: items.toList();
? items?.where((i) => !RecommendFilter.filterAll(i)).toList()
: items?.toList();
return LoadingState.success(list);
} else {
return LoadingState.error(res.data['message']);
@@ -1046,7 +1048,8 @@ class VideoHttp {
}
// 视频排行
static Future<LoadingState> getRankVideoList(int rid) async {
static Future<LoadingState<List<HotVideoItemModel>>> getRankVideoList(
int rid) async {
var rankApi = "${Api.getRankApi}?rid=$rid&type=all";
var res = await Request().get(rankApi);
if (res.data['code'] == 0) {
@@ -1094,7 +1097,7 @@ class VideoHttp {
}
}
static Future<LoadingState> noteList({
static Future<LoadingState<List<FavArticleModel>?>> noteList({
required int page,
}) async {
var res = await Request().get(
@@ -1106,13 +1109,16 @@ class VideoHttp {
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']?['list']);
List<FavArticleModel>? list = (res.data['data']?['list'] as List?)
?.map((e) => FavArticleModel.fromJson(e))
.toList();
return LoadingState.success(list);
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> userNoteList({
static Future<LoadingState<List<FavArticleModel>?>> userNoteList({
required int page,
}) async {
var res = await Request().get(
@@ -1124,7 +1130,10 @@ class VideoHttp {
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']?['list']);
List<FavArticleModel>? list = (res.data['data']?['list'] as List?)
?.map((e) => FavArticleModel.fromJson(e))
.toList();
return LoadingState.success(list);
} else {
return LoadingState.error(res.data['message']);
}

View File

@@ -1,3 +1,6 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
class BangumiListDataModel {
BangumiListDataModel({
this.hasNext,
@@ -24,7 +27,7 @@ class BangumiListDataModel {
}
}
class BangumiListItemModel {
class BangumiListItemModel with MultiSelectData {
BangumiListItemModel({
this.badge,
this.badgeType,
@@ -66,8 +69,6 @@ class BangumiListItemModel {
Map? newEp;
String? progress;
bool? checked;
BangumiListItemModel.fromJson(Map<String, dynamic> json) {
badge = json['badge'] == '' ? null : json['badge'];
badgeType = json['badge_type'];

View File

@@ -23,8 +23,7 @@ class LiveFollowingModel {
count = json['count'];
list = (json['list'] as List?)
?.map((item) => LiveFollowingItemModel.fromJson(item))
.toList() ??
<LiveFollowingItemModel>[];
.toList();
liveCount = json['live_count'];
neverLivedCount = json['never_lived_count'];
neverLivedFaces = json['never_lived_faces'];

View File

@@ -0,0 +1,31 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
class FavArticleModel with MultiSelectData {
FavArticleModel({
this.webUrl,
this.title,
this.summary,
this.message,
this.pic,
this.cvid,
this.noteId,
});
String? webUrl;
String? title;
String? summary;
String? message;
String? pic;
dynamic cvid;
dynamic noteId;
FavArticleModel.fromJson(Map json) {
webUrl = json['web_url'];
title = json['title'];
summary = json['summary'];
message = json['message'];
pic = json['arc']?['pic'];
cvid = json['cvid'];
noteId = json['note_id'];
}
}

View File

@@ -1,23 +1,23 @@
class MemberTagItemModel {
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
class MemberTagItemModel with MultiSelectData {
MemberTagItemModel({
this.count,
this.name,
this.tagid,
this.tip,
this.checked,
});
int? count;
String? name;
int? tagid;
String? tip;
bool? checked;
MemberTagItemModel.fromJson(Map<String, dynamic> json) {
count = json['count'];
name = json['name'];
tagid = json['tagid'];
tip = json['tip'];
checked = false;
}
}

View File

@@ -1,9 +1,11 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'model_owner.dart';
import 'model_rec_video_item.dart';
import 'model_video.dart';
// 稍后再看, 排行榜等网页返回也使用该类
class HotVideoItemModel extends BaseRecVideoItemModel {
class HotVideoItemModel extends BaseRecVideoItemModel with MultiSelectData {
int? videos;
int? tid;
String? tname;
@@ -16,8 +18,6 @@ class HotVideoItemModel extends BaseRecVideoItemModel {
String? pgcLabel;
String? redirectUrl;
bool? checked; // 手动设置的
num? progress;
HotVideoItemModel.fromJson(Map<String, dynamic> json) {

View File

@@ -1,3 +1,6 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import '../model_owner.dart';
import '../model_video.dart';
import 'fav_folder.dart';
@@ -18,7 +21,7 @@ class FavDetailData {
}
}
class FavDetailItemData extends BaseVideoItemModel {
class FavDetailItemData extends BaseVideoItemModel with MultiSelectData {
int? id;
int? type;
int? page;
@@ -31,7 +34,6 @@ class FavDetailItemData extends BaseVideoItemModel {
int? favTime;
Map? ogv;
String? epId;
bool? checked;
FavDetailItemData.fromJson(Map<String, dynamic> json) {
id = json['id'];

View File

@@ -1,3 +1,6 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
class HistoryData {
HistoryData({
this.cursor,
@@ -59,7 +62,7 @@ class HisTabItem {
}
}
class HisListItem {
class HisListItem with MultiSelectData {
late String title;
String? longTitle;
String? cover;
@@ -84,7 +87,6 @@ class HisListItem {
int? kid;
String? tagName;
int? liveStatus;
bool? checked;
dynamic isFullScreen;
HisListItem.fromJson(Map<String, dynamic> json) {

View File

@@ -1,14 +1,15 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/list.dart';
import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/bangumi.dart';
import 'package:PiliPlus/utils/storage.dart';
class BangumiController extends CommonController {
class BangumiController extends CommonListController<
List<BangumiListItemModel>?, BangumiListItemModel> {
BangumiController({required this.tabType});
final TabType tabType;
@@ -75,7 +76,8 @@ class BangumiController extends CommonController {
}
@override
Future<LoadingState> customGetData() => BangumiHttp.bangumiList(
Future<LoadingState<List<BangumiListItemModel>?>> customGetData() =>
BangumiHttp.bangumiList(
page: currentPage,
indexType: tabType == TabType.cinema ? 102 : null, // TODO: sort
);

View File

@@ -3,7 +3,7 @@ import 'dart:convert';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
import 'package:PiliPlus/pages/video/detail/introduction/pay_coins_page.dart';
@@ -26,7 +26,8 @@ import 'package:PiliPlus/utils/storage.dart';
import 'package:html/parser.dart' as html_parser;
import 'package:html/dom.dart' as dom;
class BangumiIntroController extends CommonController {
class BangumiIntroController
extends CommonDataController<BangumiInfoModel, BangumiInfoModel> {
// 视频bvid
String bvid = Get.parameters['bvid'] ?? '';
var seasonId = Get.parameters['seasonId'] != null
@@ -125,14 +126,15 @@ class BangumiIntroController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
epId ??= response.response.episodes!.first.id;
bool customHandleResponse(
bool isRefresh, Success<BangumiInfoModel> response) {
epId ??= response.response.episodes?.firstOrNull?.id;
loadingState.value = response;
return true;
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<BangumiInfoModel>> customGetData() =>
SearchHttp.bangumiInfoNew(seasonId: seasonId, epId: epId);
// 获取点赞/投币/收藏状态
@@ -147,40 +149,15 @@ class BangumiIntroController extends CommonController {
}
}
// 获取点赞状态
// Future queryHasLikeVideo() async {
// var result = await VideoHttp.hasLikeVideo(bvid: bvid);
// // data num 被点赞标志 0未点赞 1已点赞
// hasLike.value = result["data"] == 1 ? true : false;
// }
// 获取投币状态
// Future queryHasCoinVideo() async {
// var result = await VideoHttp.hasCoinVideo(bvid: bvid);
// hasCoin.value = result["data"]['multiply'] == 0 ? false : true;
// }
// 获取收藏状态
// Future queryHasFavVideo() async {
// var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid));
// if (result['status']) {
// hasFav.value = result["data"]['favoured'];
// } else {
// hasFav.value = false;
// }
// }
// (取消)点赞
Future actionLikeVideo() async {
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
if (result['status']) {
SmartDialog.showToast(!hasLike.value ? result['data']['toast'] : '取消赞');
hasLike.value = !hasLike.value;
dynamic bangumiDetail = (loadingState.value as Success).response;
BangumiInfoModel bangumiDetail = (loadingState.value as Success).response;
bangumiDetail.stat!['likes'] =
bangumiDetail.stat!['likes'] + (!hasLike.value ? 1 : -1);
loadingState.value = LoadingState.success(bangumiDetail);
hasLike.refresh();
hasLike.value = !hasLike.value;
} else {
SmartDialog.showToast(result['msg']);
}
@@ -194,14 +171,13 @@ class BangumiIntroController extends CommonController {
);
if (res['status']) {
SmartDialog.showToast('投币成功');
hasCoin.value = true;
dynamic bangumiDetail = (loadingState.value as Success).response;
BangumiInfoModel bangumiDetail = (loadingState.value as Success).response;
bangumiDetail.stat!['coins'] = bangumiDetail.stat!['coins'] + coin;
if (selectLike && hasLike.value.not) {
hasLike.value = true;
bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] + 1;
}
loadingState.value = LoadingState.success(bangumiDetail);
hasCoin.value = true;
} else {
SmartDialog.showToast(res['msg']);
}
@@ -236,60 +212,6 @@ class BangumiIntroController extends CommonController {
},
),
);
// showDialog(
// context: Get.context!,
// builder: (context) {
// return AlertDialog(
// title: const Text('选择投币个数'),
// contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
// content: StatefulBuilder(builder: (context, StateSetter setState) {
// return Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// RadioListTile(
// value: 1,
// title: const Text('1枚'),
// groupValue: _tempThemeValue,
// onChanged: (value) {
// _tempThemeValue = value!;
// Get.appUpdate();
// },
// ),
// RadioListTile(
// value: 2,
// title: const Text('2枚'),
// groupValue: _tempThemeValue,
// onChanged: (value) {
// _tempThemeValue = value!;
// Get.appUpdate();
// },
// ),
// ],
// );
// }),
// actions: [
// TextButton(onPressed: () => Get.back(), child: const Text('取消')),
// TextButton(
// onPressed: () async {
// var res = await VideoHttp.coinVideo(
// bvid: bvid, multiply: _tempThemeValue);
// if (res['status']) {
// SmartDialog.showToast('投币成功');
// hasCoin.value = true;
// dynamic bangumiDetail =
// (loadingState.value as Success).response;
// bangumiDetail.stat!['coins'] =
// bangumiDetail.stat!['coins'] + _tempThemeValue;
// loadingState.value = LoadingState.success(bangumiDetail);
// } else {
// SmartDialog.showToast(res['msg']);
// }
// Get.back();
// },
// child: const Text('确定'))
// ],
// );
// });
}
// (取消)收藏 bangumi
@@ -582,8 +504,11 @@ class BangumiIntroController extends CommonController {
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
if ((loadingState.value as Success).response.episodes != null) {
episodes = (loadingState.value as Success).response.episodes!;
if ((loadingState.value as Success<BangumiInfoModel>).response.episodes !=
null) {
episodes = (loadingState.value as Success<BangumiInfoModel>)
.response
.episodes!;
} else {
if (playRepeat == PlayRepeat.autoPlayRelated) {
return playRelated();

View File

@@ -131,8 +131,7 @@ class BangumiInfo extends StatefulWidget {
State<BangumiInfo> createState() => _BangumiInfoState();
}
class _BangumiInfoState extends State<BangumiInfo>
with TickerProviderStateMixin {
class _BangumiInfoState extends State<BangumiInfo> {
late final BangumiIntroController bangumiIntroController;
late final VideoDetailController videoDetailCtr;
late final BangumiInfoModel? bangumiItem;

View File

@@ -1,11 +1,10 @@
import 'package:PiliPlus/http/bangumi.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/pgc_index/condition.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:get/get.dart' hide Condition;
class PgcIndexController extends CommonController {
class PgcIndexController extends CommonListController {
PgcIndexController(this.indexType);
int? indexType;
Rx<LoadingState> conditionState = LoadingState.loading().obs;
@@ -51,20 +50,16 @@ class PgcIndexController extends CommonController {
);
@override
bool customHandleResponse(Success response) {
List? getDataList(response) {
return response['list'];
}
@override
bool customHandleResponse(bool isRefresh, Success response) {
if (response.response['has_next'] == null ||
response.response['has_next'] == 0) {
isEnd = true;
}
if (isEnd.not && (response.response['list'] as List?).isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response['list'] ??= [];
response.response['list']!
.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(response.response['list']);
return true;
return false;
}
}

View File

@@ -207,10 +207,10 @@ class _PgcIndexPageState extends State<PgcIndexPage>
],
);
Widget _buildList(LoadingState loadingState) {
Widget _buildList(LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) {
Loading() => HttpError(errMsg: '加载中'),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
@@ -221,13 +221,13 @@ class _PgcIndexPageState extends State<PgcIndexPage>
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_ctr.onLoadMore();
}
return BangumiCardVPgcIndex(
bangumiItem: loadingState.response[index]);
bangumiItem: loadingState.response![index]);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
)
: HttpError(callback: _ctr.onReload),

View File

@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/list.dart';
import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
@@ -230,10 +231,10 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<BangumiListItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => const SliverToBoxAdapter(),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距
@@ -247,13 +248,13 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
controller.onLoadMore();
}
return BangumiCardV(
bangumiItem: loadingState.response[index]);
bangumiItem: loadingState.response![index]);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
)
: HttpError(

View File

@@ -0,0 +1,61 @@
import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/http/black.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/user/black.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class BlackListController
extends CommonListController<BlackListDataModel, BlackListItem> {
int pageSize = 50;
RxInt total = 0.obs;
@override
void onInit() {
super.onInit();
queryData();
}
@override
List<BlackListItem>? getDataList(BlackListDataModel response) {
return response.list;
}
@override
void checkIsEnd(int length) {
if (length >= total.value) {
isEnd = true;
}
}
@override
bool customHandleResponse(
bool isRefresh, Success<BlackListDataModel> response) {
total.value = response.response.total ?? 0;
return false;
}
Future onRemove(BuildContext context, int index, name, mid) async {
showConfirmDialog(
context: context,
title: '确定将 $name 移出黑名单?',
onConfirm: () async {
var result = await VideoHttp.relationMod(mid: mid, act: 6, reSrc: 11);
if (result['status']) {
List<BlackListItem> list = (loadingState.value as Success).response;
list.removeAt(index);
total.value -= 1;
loadingState.refresh();
SmartDialog.showToast('操作成功');
}
},
);
}
@override
Future<LoadingState<BlackListDataModel>> customGetData() =>
BlackHttp.blackList(pn: currentPage, ps: pageSize);
}

View File

@@ -1,16 +1,11 @@
import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/user/black.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/pages/blacklist/controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/http/black.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
@@ -50,36 +45,37 @@ class _BlackListPageState extends State<BlackListPage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<BlackListItem>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
controller: _blackListController.scrollController,
itemCount: loadingState.response.length,
itemCount: loadingState.response!.length,
itemBuilder: (BuildContext context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_blackListController.onLoadMore();
}
final item = loadingState.response![index];
return ListTile(
onTap: () {
Get.toNamed(
'/member?mid=${loadingState.response[index].mid}');
Get.toNamed('/member?mid=${item.mid}');
},
leading: NetworkImgLayer(
width: 45,
height: 45,
type: 'avatar',
src: loadingState.response[index].face,
src: item.face,
),
title: Text(
loadingState.response[index].uname!,
item.uname!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
Utils.dateFormat(loadingState.response[index].mtime),
Utils.dateFormat(item.mtime),
maxLines: 1,
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -87,10 +83,11 @@ class _BlackListPageState extends State<BlackListPage> {
),
dense: true,
trailing: TextButton(
onPressed: () => _blackListController.removeBlack(
onPressed: () => _blackListController.onRemove(
context,
loadingState.response[index].uname,
loadingState.response[index].mid,
index,
item.uname,
item.mid,
),
child: const Text('移除'),
),
@@ -108,56 +105,3 @@ class _BlackListPageState extends State<BlackListPage> {
};
}
}
class BlackListController extends CommonController {
int pageSize = 50;
RxInt total = 0.obs;
@override
void onInit() {
super.onInit();
queryData();
}
@override
bool customHandleResponse(Success response) {
total.value = response.response.total;
if ((response.response.list as List?).isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response.list ??= <BlackListItem>[];
response.response.list!
.insertAll(0, (loadingState.value as Success).response);
}
if (isEnd.not && response.response.list.length >= total.value) {
isEnd = true;
}
loadingState.value = LoadingState.success(response.response.list.isNotEmpty
? response.response.list
: <BlackListItem>[]);
return true;
}
Future removeBlack(context, name, mid) async {
showConfirmDialog(
context: context,
title: '确定将 $name 移出黑名单?',
onConfirm: () async {
var result = await VideoHttp.relationMod(mid: mid, act: 6, reSrc: 11);
if (result['status']) {
List list = (loadingState.value as Success).response;
list.removeWhere((e) => e.mid == mid);
total.value = total.value - 1;
loadingState.value =
LoadingState.success(list.isNotEmpty ? list : <BlackListItem>[]);
SmartDialog.showToast('操作成功');
}
},
);
}
@override
Future<LoadingState> customGetData() =>
BlackHttp.blackList(pn: currentPage, ps: pageSize);
}

View File

@@ -27,7 +27,7 @@ abstract mixin class ScrollOrRefreshMixin {
}
}
abstract class CommonController extends GetxController
abstract class CommonController<R, T> extends GetxController
with ScrollOrRefreshMixin {
@override
final ScrollController scrollController = ScrollController();
@@ -35,13 +35,13 @@ abstract class CommonController extends GetxController
late int currentPage = 1;
bool isLoading = false;
late bool isEnd = false;
Rx<LoadingState> loadingState = LoadingState.loading().obs;
Rx<LoadingState> get loadingState;
Future<LoadingState> customGetData();
Future<LoadingState<R>> customGetData();
void handleListResponse(List dataList) {}
bool customHandleResponse(Success response) {
bool customHandleResponse(bool isRefresh, Success<R> response) {
return false;
}
@@ -49,27 +49,36 @@ abstract class CommonController extends GetxController
return false;
}
List<T>? getDataList(R response) {
return response as List<T>?;
}
void checkIsEnd(int length) {}
Future queryData([bool isRefresh = true]) async {
if (isLoading || (isRefresh.not && isEnd)) return;
isLoading = true;
LoadingState response = await customGetData();
if (response is Success) {
if (!customHandleResponse(response)) {
List? dataList = response.response;
LoadingState<R> response = await customGetData();
if (response is Success<R>) {
if (!customHandleResponse(isRefresh, response)) {
List<T>? dataList = getDataList(response.response);
if (dataList.isNullOrEmpty) {
isEnd = true;
if (isRefresh) {
loadingState.value = response;
loadingState.value = LoadingState<List<T>?>.success(dataList);
}
isLoading = false;
return;
}
handleListResponse(dataList!);
if (isRefresh) {
loadingState.value = LoadingState.success(dataList);
checkIsEnd(dataList.length);
loadingState.value = LoadingState<List<T>?>.success(dataList);
} else if (loadingState.value is Success) {
List currentList = (loadingState.value as Success).response ?? [];
currentList.addAll(dataList);
loadingState.value = LoadingState.success(currentList);
List<T> list = (loadingState.value as Success).response;
list.addAll(dataList);
checkIsEnd(list.length);
loadingState.refresh();
}
}
currentPage++;

View File

@@ -0,0 +1,8 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:get/get.dart';
abstract class CommonDataController<R, T> extends CommonController<R, T> {
@override
Rx<LoadingState<T>> loadingState = LoadingState<T>.loading().obs;
}

View File

@@ -0,0 +1,9 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:get/get.dart';
abstract class CommonListController<R, T> extends CommonController<R, T> {
@override
Rx<LoadingState<List<T>?>> loadingState =
LoadingState<List<T>?>.loading().obs;
}

View File

@@ -1,32 +1,43 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/utils/extension.dart';
abstract class MultiSelectController extends CommonController {
mixin class MultiSelectData {
bool? checked;
}
abstract class MultiSelectController<R, T extends MultiSelectData>
extends CommonListController<R, T> {
late final RxBool enableMultiSelect = false.obs;
late final RxInt checkedCount = 0.obs;
late final allSelected = false.obs;
onSelect(int index) {
List list = (loadingState.value as Success).response;
list[index].checked = !(list[index]?.checked ?? false);
void onSelect(int index, [bool disableSelect = true]) {
List<T> list = (loadingState.value as Success).response;
list[index].checked = !(list[index].checked ?? false);
checkedCount.value = list.where((item) => item.checked == true).length;
loadingState.value = LoadingState.success(list);
loadingState.refresh();
if (disableSelect) {
if (checkedCount.value == 0) {
enableMultiSelect.value = false;
}
} else {
allSelected.value = checkedCount.value == list.length;
}
}
void handleSelect([bool checked = false]) {
void handleSelect([bool checked = false, bool disableSelect = true]) {
if (loadingState.value is Success) {
List list = (loadingState.value as Success).response;
if (list.isNotEmpty) {
loadingState.value = LoadingState.success(
list.map((item) => item..checked = checked).toList());
List<T>? list = (loadingState.value as Success).response;
if (list?.isNotEmpty == true) {
for (T item in list!) {
item.checked = checked;
}
loadingState.refresh();
checkedCount.value = checked ? list.length : 0;
}
}
if (checked.not) {
if (disableSelect && !checked) {
enableMultiSelect.value = false;
}
}

View File

@@ -6,7 +6,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/models/common/reply_type.dart';
import 'package:PiliPlus/models/video/reply/data.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/video/detail/reply_new/reply_page.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -16,12 +16,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/models/common/reply_sort_type.dart';
import 'package:PiliPlus/models/video/reply/item.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
abstract class ReplyController extends CommonController {
abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
String nextOffset = '';
RxInt count = (-1).obs;
@@ -31,6 +30,7 @@ abstract class ReplyController extends CommonController {
late final bool isLogin = Accounts.main.isLogin;
dynamic upMid;
CursorReply? cursor;
late Rx<Mode> mode = Mode.MAIN_LIST_HOT.obs;
late bool hasUpTop = false;
@@ -59,6 +59,29 @@ abstract class ReplyController extends CommonController {
}
}
@override
void checkIsEnd(int length) {
if (length >= count.value) {
isEnd = true;
}
}
@override
bool customHandleResponse(bool isRefresh, Success response) {
MainListReply data = response.response;
cursor = data.cursor;
count.value = data.subjectControl.count.toInt();
if (isRefresh) {
upMid ??= data.subjectControl.upMid;
hasUpTop = data.hasUpTop();
if (data.hasUpTop()) {
data.replies.insert(0, data.upTop);
}
}
isEnd = data.cursor.isEnd;
return false;
}
@override
Future onRefresh() {
cursor = null;
@@ -66,29 +89,6 @@ abstract class ReplyController extends CommonController {
return super.onRefresh();
}
@override
bool customHandleResponse(Success response) {
MainListReply replies = response.response;
if (cursor == null) {
count.value = replies.subjectControl.count.toInt();
hasUpTop = replies.hasUpTop();
if (replies.hasUpTop()) {
replies.replies.insert(0, replies.upTop);
}
}
cursor = replies.cursor;
if (currentPage != 1 && loadingState.value is Success) {
replies.replies
.insertAll(0, (loadingState.value as Success).response.replies);
}
isEnd = replies.replies.isEmpty ||
replies.cursor.isEnd ||
replies.replies.length >= count.value;
loadingState.value = LoadingState.success(replies);
return true;
}
// 排序搜索评论
queryBySort() {
EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () {
@@ -168,16 +168,24 @@ abstract class ReplyController extends CommonController {
if (res != null) {
savedReplies[key] = null;
ReplyInfo replyInfo = Utils.replyCast(res);
MainListReply response = loadingState.value is Success
? (loadingState.value as Success).response
: MainListReply();
if (oid != null) {
response.replies.insert(hasUpTop ? 1 : 0, replyInfo);
if (loadingState.value is Success) {
List<ReplyInfo>? list = (loadingState.value as Success).response;
if (list == null) {
loadingState.value = LoadingState.success([replyInfo]);
} else {
response.replies[index].replies.add(replyInfo);
if (oid != null) {
list.insert(hasUpTop ? 1 : 0, replyInfo);
} else {
list[index].replies.add(replyInfo);
}
loadingState.refresh();
}
} else {
loadingState.value = LoadingState.success([replyInfo]);
}
count.value += 1;
loadingState.value = LoadingState.success(response);
// check reply
if (enableCommAntifraud && context.mounted) {
checkReply(
context: context,
@@ -203,28 +211,19 @@ abstract class ReplyController extends CommonController {
);
}
onMDelete(rpid, frpid) {
MainListReply response = (loadingState.value as Success).response;
if (frpid == null) {
response.replies.removeWhere((item) {
return item.id.toInt() == rpid;
});
void onRemove(int index, int? subIndex) {
List<ReplyInfo> list = (loadingState.value as Success).response;
if (subIndex == null) {
list.removeAt(index);
} else {
response.replies.map((item) {
if (item.id == frpid) {
return item..replies.removeWhere((reply) => reply.id.toInt() == rpid);
} else {
return item;
}
}).toList();
list[index].replies.removeAt(subIndex);
}
count.value -= 1;
loadingState.value = LoadingState.success(response);
loadingState.refresh();
}
void onCheckReply(context, item) {
try {
if (item is ReplyInfo) {
checkReply(
context: context,
oid: item.oid.toInt(),
@@ -242,24 +241,6 @@ abstract class ReplyController extends CommonController {
//
isManual: true,
);
} else if (item is ReplyItemModel) {
checkReply(
context: context,
oid: item.oid,
rpid: item.root == 0 ? null : item.root,
replyType: item.type!,
replyId: item.rpid!,
message: item.content!.message!,
//
root: item.root,
parent: item.parent,
ctime: item.ctime,
pictures: item.content?.pictures,
mid: item.mid,
//
isManual: true,
);
}
} catch (e) {
SmartDialog.showToast(e.toString());
}
@@ -502,16 +483,14 @@ https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? rep
isUpTop: isUpTop,
);
if (res['status']) {
final data = (loadingState.value as Success).response;
if (data is MainListReply) {
data.replies[index].replyControl.isUpTop = !isUpTop;
List<ReplyInfo> list = (loadingState.value as Success).response;
list[index].replyControl.isUpTop = !isUpTop;
if (!isUpTop && index != 0) {
data.replies[0].replyControl.isUpTop = false;
final item = data.replies.removeAt(index);
data.replies.insert(0, item);
}
loadingState.value = LoadingState.success(data);
list[0].replyControl.isUpTop = false;
final item = list.removeAt(index);
list.insert(0, item);
}
loadingState.refresh();
SmartDialog.showToast('${isUpTop ? '取消' : ''}置顶成功');
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -10,7 +10,7 @@ import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:fixnum/fixnum.dart' as $fixnum;
class DynamicDetailController extends ReplyController {
class DynamicDetailController extends ReplyController<MainListReply> {
DynamicDetailController(this.oid, this.type);
int oid;
int type;
@@ -47,7 +47,13 @@ class DynamicDetailController extends ReplyController {
}
@override
Future<LoadingState> customGetData() => ReplyHttp.replyListGrpc(
List<ReplyInfo>? getDataList(MainListReply response) {
return response.replies;
}
@override
Future<LoadingState<MainListReply>> customGetData() =>
ReplyHttp.replyListGrpc(
type: type,
oid: oid,
cursor: CursorReq(

View File

@@ -775,7 +775,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
);
}
Widget replyList(LoadingState loadingState) {
Widget replyList(LoadingState<List<ReplyInfo>?> loadingState) {
return switch (loadingState) {
Loading() => SliverList(
delegate: SliverChildBuilderDelegate(
@@ -785,11 +785,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
childCount: 8,
),
),
Success() => (loadingState.response.replies as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.replies.length) {
if (index == loadingState.response!.length) {
_dynamicDetailController.onLoadMore();
return Container(
alignment: Alignment.center,
@@ -799,7 +799,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
child: Text(
_dynamicDetailController.isEnd.not
? '加载中...'
: loadingState.response.replies.isEmpty
: loadingState.response!.isEmpty
? '还没有评论'
: '没有更多了',
style: TextStyle(
@@ -810,19 +810,20 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
);
} else {
return ReplyItemGrpc(
replyItem: loadingState.response.replies[index],
replyItem: loadingState.response![index],
replyLevel: '1',
replyReply: (replyItem, id) =>
replyReply(context, replyItem, id),
onReply: () {
_dynamicDetailController.onReply(
context,
replyItem: loadingState.response.replies[index],
replyItem: loadingState.response![index],
index: index,
);
},
onDelete: _dynamicDetailController.onMDelete,
upMid: loadingState.response.subjectControl.upMid,
onDelete: (subIndex) =>
_dynamicDetailController.onRemove(index, subIndex),
upMid: _dynamicDetailController.upMid,
callback: _getImageCallback,
onCheckReply: (item) =>
_dynamicDetailController.onCheckReply(context, item),
@@ -837,7 +838,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
);
}
},
childCount: loadingState.response.replies.length + 1,
childCount: loadingState.response!.length + 1,
),
)
: HttpError(

View File

@@ -1,15 +1,15 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../../http/dynamics.dart';
class DynamicsTabController extends CommonController {
class DynamicsTabController
extends CommonListController<DynamicsDataModel, DynamicItemModel> {
DynamicsTabController({required this.dynamicsType});
final String dynamicsType;
String offset = '';
@@ -32,22 +32,20 @@ class DynamicsTabController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
offset = response.response.offset;
if ((response.response.items as List?).isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response.items ??= <DynamicItemModel>[];
response.response.items!
.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(response.response.items);
return true;
List<DynamicItemModel>? getDataList(DynamicsDataModel response) {
return response.items;
}
@override
Future<LoadingState> customGetData() => DynamicsHttp.followDynamic(
bool customHandleResponse(
bool isRefresh, Success<DynamicsDataModel> response) {
offset = response.response.offset ?? '';
return false;
}
@override
Future<LoadingState<DynamicsDataModel>> customGetData() =>
DynamicsHttp.followDynamic(
type: dynamicsType == "up" ? "all" : dynamicsType,
offset: offset,
mid: dynamicsType == "up" ? mid : -1,
@@ -56,9 +54,9 @@ class DynamicsTabController extends CommonController {
Future onRemove(dynamic dynamicId) async {
var res = await MsgHttp.removeDynamic(dynamicId);
if (res['status']) {
List list = (loadingState.value as Success).response;
List<DynamicItemModel> list = (loadingState.value as Success).response;
list.removeWhere((item) => item.idStr == dynamicId);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/utils/storage.dart';
@@ -137,10 +138,10 @@ class _DynamicsTabPageState
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<DynamicItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => skeleton(),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
@@ -153,23 +154,23 @@ class _DynamicsTabPageState
// mainAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
controller.onLoadMore();
}
return index == loadingState.response.length
return index == loadingState.response!.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: [
if (dynamicsController.tabController.index == 4 &&
dynamicsController.mid.value != -1) ...[
for (var i in loadingState.response)
for (var i in loadingState.response!)
DynamicPanel(
item: i,
onRemove: controller.onRemove,
),
] else ...[
for (var i in loadingState.response)
for (var i in loadingState.response!)
if (!dynamicsController.tempBannedList
.contains(i.modules?.moduleAuthor?.mid))
DynamicPanel(
@@ -187,23 +188,24 @@ class _DynamicsTabPageState
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index ==
loadingState.response!.length - 1) {
controller.onLoadMore();
}
final item = loadingState.response![index];
if ((dynamicsController.tabController.index ==
4 &&
dynamicsController.mid.value != -1) ||
!dynamicsController.tempBannedList.contains(
loadingState.response[index].modules
?.moduleAuthor?.mid)) {
item.modules?.moduleAuthor?.mid)) {
return DynamicPanel(
item: loadingState.response[index],
item: item,
onRemove: controller.onRemove,
);
}
return const SizedBox.shrink();
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
),

View File

@@ -1,293 +0,0 @@
import 'dart:math';
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart' as dyn;
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/utils.dart';
import '../../../http/constants.dart';
import '../controller.dart';
class AuthorPanelGrpc extends StatelessWidget {
final dyn.DynamicItem item;
final Function? addBannedList;
final String? source;
final Function? onRemove;
const AuthorPanelGrpc({
super.key,
required this.item,
this.addBannedList,
this.source,
this.onRemove,
});
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(item.modules.first.moduleAuthor.mid);
return Row(
children: [
GestureDetector(
onTap: () {
// 番剧
// if (item.modules.first.moduleAuthor.type == 'AUTHOR_TYPE_PGC') {
// return;
// }
feedBack();
Get.toNamed(
'/member?mid=${item.modules.first.moduleAuthor.author.mid}',
arguments: {
'face': item.modules.first.moduleAuthor.author.face,
'heroTag': heroTag
},
);
},
child: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: item.modules.first.moduleAuthor.author.face,
),
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
item.modules.first.moduleAuthor.author.name,
// semanticsLabel: "UP主${item.modules.moduleAuthor.name}",
style: TextStyle(
color: item.modules.first.moduleAuthor.author.vip.status >
0 &&
item.modules.first.moduleAuthor.author.vip.type == 2
? context.vipColor
: Theme.of(context).colorScheme.onSurface,
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
),
),
],
),
// DefaultTextStyle.merge(
// style: TextStyle(
// color: Theme.of(context).colorScheme.outline,
// fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
// ),
// child: Row(
// children: [
// Text(item.modules.first.moduleAuthor.pubTime),
// if (item.modules.first.moduleAuthor.pubTime != '' &&
// item.modules.first.moduleAuthor.pubAction != '')
// const Text(' '),
// Text(item.modules.first.moduleAuthor.pubAction),
// ],
// ),
// )
],
),
const Spacer(),
// if (source != 'detail' && item.modules.first?.moduleTag?.text != null)
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
// decoration: BoxDecoration(
// color: Theme.of(context).colorScheme.surface,
// borderRadius: const BorderRadius.all(Radius.circular(4)),
// border: Border.all(
// width: 1.25,
// color: Theme.of(context).colorScheme.primary,
// ),
// ),
// child: Text(
// item.modules.first.moduleTag.text,
// style: TextStyle(
// height: 1,
// fontSize: 12,
// color: Theme.of(context).colorScheme.primary,
// ),
// strutStyle: const StrutStyle(
// leading: 0,
// height: 1,
// fontSize: 12,
// ),
// ),
// ),
SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
constraints: BoxConstraints(
maxWidth: min(640, min(Get.width, Get.height)),
),
builder: (context) {
return MorePanel(
item: item,
onRemove: onRemove,
);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
),
),
],
);
}
}
class MorePanel extends StatelessWidget {
final dynamic item;
final Function? onRemove;
const MorePanel({
super.key,
required this.item,
this.onRemove,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: Get.back,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
),
child: Container(
height: 35,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: const BorderRadius.all(Radius.circular(3))),
),
),
),
),
if (item.type == 'DYNAMIC_TYPE_AV')
ListTile(
onTap: () async {
try {
String bvid = item.modules.moduleDynamic.major.archive.bvid;
var res = await UserHttp.toViewLater(bvid: bvid);
SmartDialog.showToast(res['msg']);
Get.back();
} catch (err) {
SmartDialog.showToast('出错了:${err.toString()}');
}
},
minLeadingWidth: 0,
// dense: true,
leading: const Icon(Icons.watch_later_outlined, size: 19),
title: Text(
'稍后再看',
style: Theme.of(context).textTheme.titleSmall,
),
),
ListTile(
title: Text(
'分享动态',
style: Theme.of(context).textTheme.titleSmall,
),
leading: const Icon(Icons.share_outlined, size: 19),
onTap: () {
Get.back();
Utils.shareText(
'${HttpString.dynamicShareBaseUrl}/${item.idStr}');
},
minLeadingWidth: 0,
),
ListTile(
title: Text(
'临时屏蔽:${item.modules.moduleAuthor.name}',
style: Theme.of(context).textTheme.titleSmall,
),
leading: const Icon(Icons.visibility_off_outlined, size: 19),
onTap: () {
Get.back();
DynamicsController dynamicsController =
Get.find<DynamicsController>();
dynamicsController.tempBannedList
.add(item.modules.moduleAuthor.mid);
SmartDialog.showToast(
'已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复');
},
minLeadingWidth: 0,
),
if (item.modules.moduleAuthor.mid == Accounts.main.mid)
ListTile(
onTap: () async {
Get.back();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确定删除该动态?'),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () {
Get.back();
onRemove?.call(item.idStr);
},
child: const Text('确定'),
),
],
));
},
minLeadingWidth: 0,
leading: Icon(Icons.delete_outline,
color: Theme.of(context).colorScheme.error, size: 19),
title: Text('删除',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: Theme.of(context).colorScheme.error)),
),
const Divider(thickness: 0.1, height: 1),
ListTile(
onTap: () => Get.back(),
minLeadingWidth: 0,
dense: true,
title: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
textAlign: TextAlign.center,
),
),
],
),
);
}
}

View File

@@ -1,82 +0,0 @@
// 内容
import 'package:PiliPlus/common/widgets/image_view.dart';
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart';
import 'package:flutter/material.dart';
import 'rich_node_panel.dart';
class ContentGrpc extends StatelessWidget {
final DynamicItem item;
final String? source;
const ContentGrpc({
super.key,
required this.item,
this.source,
});
InlineSpan picsNodes() {
return WidgetSpan(
child: LayoutBuilder(
builder: (context, constraints) => imageView(
constraints.maxWidth,
item.modules.first.moduleDynamic.dynDraw.items
.map(
(item) => ImageModel(
width: item.width,
height: item.height,
url: item.src,
),
)
.toList(),
),
),
);
}
@override
Widget build(BuildContext context) {
TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary);
InlineSpan? richNodes = richNode(item, context);
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.modules.first.moduleDynamic.hasDynTopicSet()) ...[
GestureDetector(
child: Text(
'#${item.modules.first.moduleDynamic.dynTopicSet.topics.first.topicName}',
style: authorStyle,
),
),
],
if (richNodes != null)
IgnorePointer(
// 禁用SelectableRegion的触摸交互功能
ignoring: source == 'detail' ? false : true,
child: SelectableRegion(
magnifierConfiguration: const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Text.rich(
/// fix 默认20px高度
style: const TextStyle(height: 0),
richNodes,
maxLines: source == 'detail' ? null : 6,
overflow: source == 'detail' ? null : TextOverflow.ellipsis,
),
),
),
if (item.modules.first.moduleDynamic.hasDynDraw())
Text.rich(
picsNodes(),
// semanticsLabel: '动态图片',
),
],
),
);
}
}

View File

@@ -1,65 +0,0 @@
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart';
import 'package:PiliPlus/pages/dynamics/widgets/author_panel_grpc.dart';
import 'package:PiliPlus/pages/dynamics/widgets/content_panel_grpc.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class DynamicPanelGrpc extends StatelessWidget {
final DynamicItem item;
final String? source;
final Function? onRemove;
const DynamicPanelGrpc({
required this.item,
this.source,
this.onRemove,
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
padding: source == 'detail'
? const EdgeInsets.only(bottom: 12)
: EdgeInsets.zero,
// decoration: BoxDecoration(
// border: Border(
// bottom: BorderSide(
// width: 8,
// color: Theme.of(context).dividerColor.withOpacity(0.05),
// ),
// ),
// ),
child: Material(
elevation: 0,
clipBehavior: Clip.hardEdge,
color: Theme.of(context).cardColor.withOpacity(0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
child: InkWell(
onTap: source == 'detail' && item.itemType == DynamicType.draw
? null
: () => Utils.pushDynDetail(item, 1),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 6),
child: AuthorPanelGrpc(
item: item,
source: source,
onRemove: onRemove,
),
),
ContentGrpc(item: item, source: source),
// forWard(item, context, _dynamicsController, source),
const SizedBox(height: 2),
// if (source == null) ActionPanel(item: item),
],
),
),
),
);
}
}

View File

@@ -1,11 +1,13 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/models/video/reply/emote.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../http/reply.dart';
class EmotePanelController extends CommonController
class EmotePanelController
extends CommonListController<List<Packages>?, Packages>
with GetTickerProviderStateMixin {
TabController? tabController;
@@ -16,15 +18,17 @@ class EmotePanelController extends CommonController
}
@override
bool customHandleResponse(Success response) {
bool customHandleResponse(bool isRefresh, Success<List<Packages>?> response) {
if (response.response?.isNotEmpty == true) {
tabController =
TabController(length: response.response.length, vsync: this);
TabController(length: response.response!.length, vsync: this);
}
loadingState.value = response;
return true;
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<List<Packages>?>> customGetData() =>
ReplyHttp.getEmoteList(business: 'reply');
@override

View File

@@ -29,16 +29,16 @@ class _EmotePanelState extends State<EmotePanel>
return Obx(() => _buildBody(_emotePanelController.loadingState.value));
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<Packages>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? Column(
children: [
Expanded(
child: tabBarView(
controller: _emotePanelController.tabController,
children: (loadingState.response as List<Packages>).map(
children: loadingState.response!.map(
(e) {
int size = e.emote!.first.meta!.size!;
int type = e.type!;
@@ -100,7 +100,7 @@ class _EmotePanelState extends State<EmotePanel>
dividerColor: Colors.transparent,
dividerHeight: 0,
isScrollable: true,
tabs: (loadingState.response as List<Packages>)
tabs: loadingState.response!
.map(
(e) => Padding(
padding: const EdgeInsets.all(8),

View File

@@ -1,12 +1,12 @@
import 'package:PiliPlus/http/fan.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/fans/result.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/utils/storage.dart';
class FansController extends CommonController {
class FansController
extends CommonListController<FansDataModel, FansItemModel> {
int ps = 20;
int total = 0;
late int? mid;
@@ -28,22 +28,12 @@ class FansController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
if ((currentPage == 1 && response.response.total < ps) ||
(response.response.list as List?).isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response.list ??= <FansItemModel>[];
response.response.list!
.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(response.response.list);
return true;
List<FansItemModel>? getDataList(FansDataModel response) {
return response.list;
}
@override
Future<LoadingState> customGetData() => FanHttp.fans(
Future<LoadingState<FansDataModel>> customGetData() => FanHttp.fans(
vmid: mid,
pn: currentPage,
ps: ps,

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/fans/result.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -48,23 +49,27 @@ class _FansPageState extends State<FansPage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<FansItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => HttpError(),
Success() => (loadingState.response as List?)?.isNotEmpty == true
? SliverGrid(
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
mainAxisExtent: 56,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_fansController.onLoadMore();
}
return fanItem(item: loadingState.response[index]);
return fanItem(item: loadingState.response![index]);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
)
: HttpError(

View File

@@ -1,9 +1,9 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class FavArticleController extends CommonController {
class FavArticleController extends CommonListController {
@override
void onInit() {
super.onInit();
@@ -19,7 +19,7 @@ class FavArticleController extends CommonController {
if (res['status']) {
List list = (loadingState.value as Success).response;
list.removeAt(index);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
SmartDialog.showToast('已取消收藏');
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -40,7 +40,7 @@ class _FavArticlePageState extends State<FavArticlePage>
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
@@ -55,7 +55,7 @@ class _FavArticlePageState extends State<FavArticlePage>
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
@@ -69,11 +69,11 @@ class _FavArticlePageState extends State<FavArticlePage>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_favArticleController.onLoadMore();
}
return FavArticleItem(
item: loadingState.response[index],
item: loadingState.response![index],
onDelete: () {
showConfirmDialog(
context: context,
@@ -81,13 +81,13 @@ class _FavArticlePageState extends State<FavArticlePage>
onConfirm: () {
_favArticleController.onRemove(
index,
loadingState.response[index]['opus_id'],
loadingState.response![index]['opus_id'],
);
});
},
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
)

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/member/article.dart';
import 'package:PiliPlus/pages/fav/note/controller.dart';
import 'package:PiliPlus/pages/fav/note/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -132,7 +133,7 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<FavArticleModel>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
@@ -147,7 +148,7 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
@@ -159,18 +160,18 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_favNoteController.onLoadMore();
}
return FavNoteItem(
item: loadingState.response[index],
item: loadingState.response![index],
ctr: _favNoteController,
onSelect: () {
_favNoteController.onSelect(index);
},
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
)

View File

@@ -1,16 +1,15 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/member/article.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class FavNoteController extends MultiSelectController {
class FavNoteController
extends MultiSelectController<List<FavArticleModel>?, FavArticleModel> {
FavNoteController(this.isPublish);
final bool isPublish;
late final allSelected = false.obs;
@override
void onInit() {
super.onInit();
@@ -18,48 +17,36 @@ class FavNoteController extends MultiSelectController {
}
@override
onSelect(int index) {
List list = (loadingState.value as Success).response;
list[index]['checked'] = !(list[index]['checked'] ?? false);
checkedCount.value = list.where((item) => item['checked'] == true).length;
loadingState.value = LoadingState.success(list);
allSelected.value = checkedCount.value == list.length;
if (checkedCount.value == 0) {
enableMultiSelect.value = false;
}
onSelect(int index, [bool disableSelect = true]) {
super.onSelect(index, false);
}
@override
void handleSelect([bool checked = false]) {
void handleSelect([bool checked = false, bool disableSelect = true]) {
allSelected.value = checked;
if (loadingState.value is Success) {
List list = (loadingState.value as Success).response;
if (list.isNotEmpty) {
loadingState.value = LoadingState.success(
list.map((item) => item..['checked'] = checked).toList());
checkedCount.value = checked ? list.length : 0;
}
}
super.handleSelect(checked, false);
}
@override
Future<LoadingState> customGetData() {
Future<LoadingState<List<FavArticleModel>?>> customGetData() {
return isPublish
? VideoHttp.userNoteList(page: currentPage)
: VideoHttp.noteList(page: currentPage);
}
void onRemove() async {
List dataList = (loadingState.value as Success).response as List;
Set removeList = dataList.where((item) => item['checked'] == true).toSet();
List<FavArticleModel> dataList = (loadingState.value as Success).response;
Set<FavArticleModel> removeList =
dataList.where((item) => item.checked == true).toSet();
final res = await VideoHttp.delNote(
isPublish: isPublish,
noteIds: removeList
.map((item) => isPublish ? item['cvid'] : item['note_id'])
.map((item) => isPublish ? item.cvid : item.noteId)
.toList(),
);
if (res['status']) {
List remainList = dataList.toSet().difference(removeList).toList();
List<FavArticleModel> remainList =
dataList.toSet().difference(removeList).toList();
loadingState.value = LoadingState.success(remainList);
enableMultiSelect.value = false;
SmartDialog.showToast('删除成功');

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/member/article.dart';
import 'package:PiliPlus/pages/fav/note/controller.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -12,7 +13,7 @@ class FavNoteItem extends StatelessWidget {
required this.onSelect,
});
final dynamic item;
final FavArticleModel item;
final FavNoteController ctr;
final VoidCallback onSelect;
@@ -26,10 +27,12 @@ class FavNoteItem extends StatelessWidget {
onSelect();
return;
}
if (item.webUrl?.isNotEmpty == true) {
Utils.handleWebview(
item['web_url'],
item.webUrl!,
inApp: true,
);
}
},
onLongPress: () {
if (!ctr.enableMultiSelect.value) {
@@ -53,7 +56,7 @@ class FavNoteItem extends StatelessWidget {
children: [
Expanded(
child: Text(
item['title'],
item.title ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -64,14 +67,14 @@ class FavNoteItem extends StatelessWidget {
),
),
Text(
item['summary'],
item.summary ?? '',
maxLines: 1,
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
Text(
item['message'],
item.message ?? '',
maxLines: 1,
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
@@ -80,7 +83,7 @@ class FavNoteItem extends StatelessWidget {
],
),
),
if (item['arc']?['pic'] != null) ...[
if (item.pic?.isNotEmpty == true) ...[
const SizedBox(width: 10),
AspectRatio(
aspectRatio: StyleString.aspectRatio,
@@ -91,7 +94,7 @@ class FavNoteItem extends StatelessWidget {
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: item['arc']?['pic'],
src: item.pic,
width: boxConstraints.maxWidth,
height: boxConstraints.maxHeight,
),
@@ -100,7 +103,7 @@ class FavNoteItem extends StatelessWidget {
child: LayoutBuilder(
builder: (context, constraints) =>
AnimatedOpacity(
opacity: item['checked'] == true ? 1 : 0,
opacity: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
@@ -115,7 +118,7 @@ class FavNoteItem extends StatelessWidget {
width: 34,
height: 34,
child: AnimatedScale(
scale: item['checked'] == true ? 1 : 0,
scale: item.checked == true ? 1 : 0,
duration:
const Duration(milliseconds: 250),
curve: Curves.easeInOut,

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/list.dart';
import 'package:PiliPlus/pages/fav/pgc/controller.dart';
import 'package:PiliPlus/pages/fav/pgc/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -126,7 +127,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
if (_favPgcController.checkedCount.value !=
0) {
_favPgcController
.onUpdate(item['followStatus']);
.onUpdateList(item['followStatus']);
}
},
child: Padding(
@@ -156,7 +157,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<BangumiListItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
@@ -171,7 +172,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
@@ -183,11 +184,12 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_favPgcController.onLoadMore();
}
final item = loadingState.response![index];
return FavPgcItem(
item: loadingState.response[index],
item: item,
ctr: _favPgcController,
onSelect: () {
_favPgcController.onSelect(index);
@@ -201,13 +203,13 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
if (followStatus == -1) {
_favPgcController.bangumiDel(
index,
loadingState.response[index].seasonId,
item.seasonId,
);
} else {
_favPgcController.bangumiUpdate(
_favPgcController.onUpdate(
index,
followStatus,
loadingState.response[index].seasonId,
item.seasonId,
);
}
},
@@ -215,7 +217,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
},
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
)

View File

@@ -8,14 +8,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class FavPgcController extends MultiSelectController {
class FavPgcController
extends MultiSelectController<BangumiListDataModel, BangumiListItemModel> {
final int type;
final int followStatus;
FavPgcController(this.type, this.followStatus);
late final allSelected = false.obs;
@override
void onInit() {
super.onInit();
@@ -23,34 +22,24 @@ class FavPgcController extends MultiSelectController {
}
@override
onSelect(int index) {
List<BangumiListItemModel> list = (loadingState.value as Success).response;
list[index].checked = !(list[index].checked ?? false);
checkedCount.value = list.where((item) => item.checked == true).length;
loadingState.value = LoadingState.success(list);
allSelected.value = checkedCount.value == list.length;
if (checkedCount.value == 0) {
enableMultiSelect.value = false;
}
onSelect(int index, [bool disableSelect = true]) {
super.onSelect(index, false);
}
@override
void handleSelect([bool checked = false]) {
void handleSelect([bool checked = false, bool disableSelect = true]) {
allSelected.value = checked;
if (loadingState.value is Success) {
List<BangumiListItemModel> list =
(loadingState.value as Success).response;
if (list.isNotEmpty) {
loadingState.value = LoadingState.success(list
.map<BangumiListItemModel>((item) => item..checked = checked)
.toList());
checkedCount.value = checked ? list.length : 0;
}
}
super.handleSelect(checked, false);
}
@override
Future<LoadingState> customGetData() => BangumiHttp.bangumiFollowList(
List<BangumiListItemModel>? getDataList(BangumiListDataModel response) {
return response.list;
}
@override
Future<LoadingState<BangumiListDataModel>> customGetData() =>
BangumiHttp.bangumiFollowList(
mid: Accounts.main.mid,
type: type,
followStatus: followStatus,
@@ -71,12 +60,12 @@ class FavPgcController extends MultiSelectController {
List<BangumiListItemModel> list =
(loadingState.value as Success).response;
list.removeAt(index);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
}
SmartDialog.showToast(result['msg']);
}
Future onUpdate(followStatus) async {
Future onUpdateList(followStatus) async {
List<BangumiListItemModel> dataList =
(loadingState.value as Success).response as List<BangumiListItemModel>;
Set<BangumiListItemModel> updateList =
@@ -96,7 +85,7 @@ class FavPgcController extends MultiSelectController {
List<BangumiListItemModel> list =
(ctr.loadingState.value as Success).response;
list.insertAll(0, updateList.map((item) => item..checked = null));
ctr.loadingState.value = LoadingState.success(list);
ctr.loadingState.refresh();
ctr.allSelected.value = false;
}
} catch (e) {
@@ -106,7 +95,7 @@ class FavPgcController extends MultiSelectController {
SmartDialog.showToast(res['msg']);
}
Future bangumiUpdate(index, followStatus, seasonId) async {
Future onUpdate(index, followStatus, seasonId) async {
var result = await VideoHttp.bangumiUpdate(
seasonId: [seasonId],
status: followStatus,
@@ -115,14 +104,14 @@ class FavPgcController extends MultiSelectController {
List<BangumiListItemModel> list =
(loadingState.value as Success).response;
final item = list.removeAt(index);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
try {
final ctr = Get.find<FavPgcController>(tag: '$type$followStatus');
if (ctr.loadingState.value is Success) {
List<BangumiListItemModel> list =
(ctr.loadingState.value as Success).response;
list.insert(0, item);
ctr.loadingState.value = LoadingState.success(list);
ctr.loadingState.refresh();
ctr.allSelected.value = false;
}
} catch (e) {

View File

@@ -1,11 +1,11 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
class FavController extends CommonController {
class FavController
extends CommonListController<FavFolderData, FavFolderItemData> {
late final dynamic mid = Accounts.main.mid;
@override
@@ -24,22 +24,20 @@ class FavController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
if (response.response.hasMore == false ||
(response.response.list as List?).isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response.list ??= <FavFolderItemData>[];
response.response.list!
.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(response.response.list);
return true;
List<FavFolderItemData>? getDataList(FavFolderData response) {
return response.list;
}
@override
Future<LoadingState> customGetData() => UserHttp.userfavFolder(
bool customHandleResponse(bool isRefresh, Success<FavFolderData> response) {
if (response.response.hasMore == false) {
isEnd = true;
}
return false;
}
@override
Future<LoadingState<FavFolderData>> customGetData() => UserHttp.userfavFolder(
pn: currentPage,
ps: 10,
mid: mid,

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -44,7 +45,7 @@ class _FavVideoPageState extends State<FavVideoPage>
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<FavFolderItemData>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -59,7 +60,7 @@ class _FavVideoPageState extends State<FavVideoPage>
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
@@ -72,33 +73,31 @@ class _FavVideoPageState extends State<FavVideoPage>
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate(
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
(BuildContext context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_favController.onLoadMore();
}
String heroTag =
Utils.makeHeroTag(loadingState.response[index].fid);
final item = loadingState.response![index];
String heroTag = Utils.makeHeroTag(item.fid);
return FavItem(
heroTag: heroTag,
favFolderItem: loadingState.response[index],
favFolderItem: item,
onTap: () async {
dynamic res = await Get.toNamed(
'/favDetail',
arguments: loadingState.response[index],
arguments: item,
parameters: {
'heroTag': heroTag,
'mediaId':
loadingState.response[index].id.toString(),
'mediaId': item.id.toString(),
},
);
if (res == true) {
List list =
List<FavFolderItemData> list =
(_favController.loadingState.value as Success)
.response;
list.removeAt(index);
_favController.loadingState.value =
LoadingState.success(list);
_favController.loadingState.refresh();
} else {
Future.delayed(const Duration(milliseconds: 255), () {
_favController.onRefresh();

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/fav/article/view.dart';
import 'package:PiliPlus/pages/fav/note/view.dart';
import 'package:PiliPlus/pages/fav/pgc/view.dart';
@@ -54,13 +55,13 @@ class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
Get.toNamed('/createFav')?.then(
(data) {
if (data != null) {
List list = _favController.loadingState.value is Success
List<FavFolderItemData> list =
_favController.loadingState.value is Success
? (_favController.loadingState.value as Success)
.response
: [];
: <FavFolderItemData>[];
list.insert(list.isNotEmpty ? 1 : 0, data);
_favController.loadingState.value =
LoadingState.success(list);
_favController.loadingState.refresh();
}
},
);
@@ -81,6 +82,7 @@ class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
'title': item.title,
'count': item.mediaCount,
'searchType': SearchType.fav,
'isOwner': true,
});
} catch (_) {}
}

View File

@@ -3,7 +3,6 @@ import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/user/fav_detail.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -11,7 +10,8 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/video.dart';
class FavDetailController extends MultiSelectController {
class FavDetailController
extends MultiSelectController<FavDetailData, FavDetailItemData> {
Rx<FavFolderItemData> item = FavFolderItemData().obs;
int? mediaId;
late String heroTag;
@@ -35,40 +35,39 @@ class FavDetailController extends MultiSelectController {
}
@override
bool customHandleResponse(Success response) {
List<FavDetailItemData>? getDataList(FavDetailData response) {
return response.list;
}
@override
void checkIsEnd(int length) {
if (item.value.mediaCount != null && length >= item.value.mediaCount!) {
isEnd = true;
}
}
@override
bool customHandleResponse(bool isRefresh, Success<FavDetailData> response) {
FavDetailData data = response.response;
if (currentPage == 1) {
if (isRefresh) {
item.value = data.info ?? FavFolderItemData();
isOwner.value = data.info?.mid == mid;
}
if (data.list.isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
data.list ??= <FavDetailItemData>[];
data.list!.insertAll(
0,
(loadingState.value as Success).response,
);
}
if (isEnd.not && (data.list?.length ?? 0) >= (data.info?.mediaCount ?? 0)) {
isEnd = true;
}
loadingState.value = LoadingState.success(data.list);
return true;
return false;
}
onCancelFav(int id, int type) async {
onCancelFav(int index, int id, int type) async {
var result = await VideoHttp.delFav(
ids: ['$id:$type'],
delIds: mediaId.toString(),
);
if (result['status']) {
List dataList = (loadingState.value as Success).response;
dataList.removeWhere((item) => item.id == id);
List<FavDetailItemData> dataList =
(loadingState.value as Success).response;
item.value.mediaCount = item.value.mediaCount! - 1;
item.refresh();
loadingState.value = LoadingState.success(dataList);
dataList.removeAt(index);
loadingState.refresh();
SmartDialog.showToast('取消收藏');
} else {
SmartDialog.showToast(result['msg']);
@@ -76,7 +75,8 @@ class FavDetailController extends MultiSelectController {
}
@override
Future<LoadingState> customGetData() => UserHttp.userFavFolderDetail(
Future<LoadingState<FavDetailData>> customGetData() =>
UserHttp.userFavFolderDetail(
pn: currentPage,
ps: 20,
mediaId: mediaId!,
@@ -110,8 +110,9 @@ class FavDetailController extends MultiSelectController {
delIds: mediaId.toString(),
);
if (result['status']) {
List dataList = (loadingState.value as Success).response;
List remainList =
List<FavDetailItemData> dataList =
(loadingState.value as Success).response;
List<FavDetailItemData> remainList =
dataList.toSet().difference(list.toSet()).toList();
item.value.mediaCount = item.value.mediaCount! - list.length;
item.refresh();
@@ -137,8 +138,7 @@ class FavDetailController extends MultiSelectController {
void toViewPlayAll() {
if (loadingState.value is Success) {
List<FavDetailItemData> list = List<FavDetailItemData>.from(
(loadingState.value as Success).response);
List<FavDetailItemData> list = (loadingState.value as Success).response;
for (FavDetailItemData element in list) {
if (element.cid == null) {
continue;

View File

@@ -22,7 +22,7 @@ class _FavSortPageState extends State<FavSortPage> {
FavDetailController get _favDetailController => widget.favDetailController;
final GlobalKey _key = GlobalKey();
late List<FavDetailItemData> list = List<FavDetailItemData>.from(
late List<FavDetailItemData> sortList = List<FavDetailItemData>.from(
(_favDetailController.loadingState.value as Success).response);
List<String> sort = <String>[];
@@ -39,7 +39,7 @@ class _FavSortPageState extends State<FavSortPage> {
if (_favDetailController.loadingState.value is Success) {
List<FavDetailItemData> list =
(_favDetailController.loadingState.value as Success).response;
this.list.addAll(list.sublist(this.list.length));
this.sortList.addAll(list.sublist(this.sortList.length));
if (mounted) {
setState(() {});
}
@@ -83,7 +83,7 @@ class _FavSortPageState extends State<FavSortPage> {
if (res['status']) {
SmartDialog.showToast('排序完成');
_favDetailController.loadingState.value =
LoadingState.success(list);
LoadingState.success(sortList);
Get.back();
} else {
SmartDialog.showToast(res['msg']);
@@ -103,14 +103,14 @@ class _FavSortPageState extends State<FavSortPage> {
newIndex -= 1;
}
final oldItem = list[oldIndex];
final oldItem = sortList[oldIndex];
final newItem =
list.getOrNull(oldIndex > newIndex ? newIndex - 1 : newIndex);
sortList.getOrNull(oldIndex > newIndex ? newIndex - 1 : newIndex);
sort.add(
'${newItem == null ? '0:0' : '${newItem.id}:${newItem.type}'}:${oldItem.id}:${oldItem.type}');
final tabsItem = list.removeAt(oldIndex);
list.insert(newIndex, tabsItem);
final tabsItem = sortList.removeAt(oldIndex);
sortList.insert(newIndex, tabsItem);
setState(() {});
}
@@ -124,7 +124,7 @@ class _FavSortPageState extends State<FavSortPage> {
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 80,
),
children: list
children: sortList
.map(
(item) => Stack(
key: Key(item.id.toString()),

View File

@@ -135,7 +135,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
visualDensity:
VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
onPressed: () =>
Utils.onCopyOrMove<FavDetailItemData>(
context: context,
isCopy: true,
ctr: _favDetailController,
@@ -155,7 +156,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
visualDensity:
VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
onPressed: () =>
Utils.onCopyOrMove<FavDetailItemData>(
context: context,
isCopy: false,
ctr: _favDetailController,
@@ -188,15 +190,18 @@ class _FavDetailPageState extends State<FavDetailPage> {
: [
IconButton(
tooltip: '搜索',
onPressed: () =>
Get.toNamed('/favSearch', arguments: {
onPressed: () => Get.toNamed(
'/favSearch',
arguments: {
'type': 0,
'mediaId': int.parse(mediaId),
'title': _favDetailController.item.value.title,
'count':
_favDetailController.item.value.mediaCount,
'searchType': SearchType.fav,
}),
'isOwner': _favDetailController.isOwner.value,
},
),
icon: const Icon(Icons.search_outlined),
),
// IconButton(
@@ -416,7 +421,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<FavDetailItemData>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -431,7 +436,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 85,
@@ -444,7 +449,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length) {
if (index == loadingState.response!.length) {
_favDetailController.onLoadMore();
return Container(
height: 60,
@@ -458,25 +463,26 @@ class _FavDetailPageState extends State<FavDetailPage> {
),
);
}
FavDetailItemData element = loadingState.response[index];
FavDetailItemData item = loadingState.response![index];
return Stack(
children: [
Positioned.fill(
child: FavVideoCardH(
videoItem: element,
videoItem: item,
callFn: () => _favDetailController.onCancelFav(
element.id!,
element.type!,
index,
item.id!,
item.type!,
),
onViewFav: () {
Utils.toViewPage(
'bvid=${element.bvid}&cid=${element.cid}',
'bvid=${item.bvid}&cid=${item.cid}',
arguments: {
'videoItem': element,
'heroTag': Utils.makeHeroTag(element.bvid),
'videoItem': item,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'fav',
'mediaId': _favDetailController.item.value.id,
'oid': element.id,
'oid': item.id,
'favTitle':
_favDetailController.item.value.title,
'count': _favDetailController
@@ -513,10 +519,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
child: LayoutBuilder(
builder: (context, constraints) =>
AnimatedOpacity(
opacity:
loadingState.response[index].checked == true
? 1
: 0,
opacity: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
@@ -531,11 +534,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
width: 34,
height: 34,
child: AnimatedScale(
scale: loadingState
.response[index].checked ==
true
? 1
: 0,
scale: item.checked == true ? 1 : 0,
duration:
const Duration(milliseconds: 250),
curve: Curves.easeInOut,
@@ -571,7 +570,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
],
);
},
childCount: loadingState.response.length + 1,
childCount: loadingState.response!.length + 1,
),
),
)

View File

@@ -1,6 +1,6 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -9,7 +9,7 @@ import 'package:PiliPlus/http/user.dart';
import '../../http/video.dart';
class FavSearchController extends CommonController {
class FavSearchController extends CommonListController {
final controller = TextEditingController();
final searchFocusNode = FocusNode();
@@ -17,6 +17,7 @@ class FavSearchController extends CommonController {
int? mediaId;
int? mid;
late SearchType searchType;
final bool? isOwner = Get.arguments['isOwner'];
@override
void onInit() {
@@ -45,23 +46,19 @@ class FavSearchController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
late List currentList = loadingState.value is Success
? (loadingState.value as Success).response
: [];
List? dataList = currentPage == 1
? response.response.list
: response.response.list != null
? currentList + response.response.list
: currentList;
List? getDataList(response) {
return response.list;
}
@override
bool customHandleResponse(bool isRefresh, Success response) {
isEnd = searchType == SearchType.fav
? response.response.hasMore == false
: response.response.list == null || response.response.list.isEmpty;
loadingState.value = LoadingState.success(dataList);
return true;
return false;
}
onCancelFav(int id, int type) async {
onCancelFav(int index, int id, int type) async {
var result = await VideoHttp.favVideo(
aid: id,
addIds: '',
@@ -70,8 +67,8 @@ class FavSearchController extends CommonController {
);
if (result['status']) {
List dataList = (loadingState.value as Success).response;
dataList.removeWhere((item) => item.id == id);
loadingState.value = LoadingState.success(dataList);
dataList.removeAt(index);
loadingState.refresh();
SmartDialog.showToast('取消收藏');
}
}
@@ -104,7 +101,7 @@ class FavSearchController extends CommonController {
super.onClose();
}
Future delHistory(kid, business) async {
Future onDelHistory(index, kid, business) async {
String resKid = 'archive_$kid';
if (business == 'live') {
resKid = 'live_$kid';
@@ -115,8 +112,8 @@ class FavSearchController extends CommonController {
var res = await UserHttp.delHistory([resKid]);
if (res['status']) {
List historyList = (loadingState.value as Success).response;
historyList.removeWhere((e) => e.kid == kid);
loadingState.value = LoadingState.success(historyList);
historyList.removeAt(index);
loadingState.refresh();
SmartDialog.showToast(res['msg']);
}
}

View File

@@ -61,12 +61,12 @@ class _FavSearchPageState extends State<FavSearchPage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) {
Loading() => errorWidget(),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? switch (_favSearchCtr.searchType) {
SearchType.fav => CustomScrollView(
SearchType.fav || SearchType.history => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController,
slivers: [
@@ -82,30 +82,34 @@ class _FavSearchPageState extends State<FavSearchPage> {
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore();
}
final element = loadingState.response[index];
return FavVideoCardH(
videoItem: element,
final item = loadingState.response![index];
return _favSearchCtr.searchType == SearchType.fav
? FavVideoCardH(
videoItem: item,
isOwner: _favSearchCtr.isOwner ?? false,
searchType: _favSearchCtr.type,
callFn: _favSearchCtr.type != 1
? () {
_favSearchCtr.onCancelFav(
element.id!,
element.type,
index,
item.id!,
item.type,
);
}
: null,
onViewFav: () {
Utils.toViewPage(
'bvid=${element.bvid}&cid=${element.cid}',
'bvid=${item.bvid}&cid=${item.cid}',
arguments: {
'videoItem': element,
'heroTag': Utils.makeHeroTag(element.bvid),
'videoItem': item,
'heroTag':
Utils.makeHeroTag(item.bvid),
'sourceType': 'fav',
'mediaId': Get.arguments['mediaId'],
'oid': element.id,
'oid': item.id,
'favTitle': Get.arguments['title'],
'count': Get.arguments['count'],
'desc': true,
@@ -113,9 +117,18 @@ class _FavSearchPageState extends State<FavSearchPage> {
},
);
},
)
: HistoryItem(
videoItem: item,
ctr: _favSearchCtr,
onChoose: null,
onDelete: (kid, business) {
_favSearchCtr.onDelHistory(
index, kid, business);
},
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
),
@@ -126,47 +139,16 @@ class _FavSearchPageState extends State<FavSearchPage> {
bottom: MediaQuery.of(context).padding.bottom + 80,
),
controller: _favSearchCtr.scrollController,
itemCount: loadingState.response.length,
itemCount: loadingState.response!.length,
itemBuilder: ((context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore();
}
return FollowItem(
item: loadingState.response[index],
item: loadingState.response![index],
);
}),
),
SearchType.history => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController,
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
_favSearchCtr.onLoadMore();
}
return HistoryItem(
videoItem: loadingState.response[index],
ctr: _favSearchCtr,
onChoose: null,
);
},
childCount: loadingState.response.length,
),
),
),
],
),
}
: errorWidget(
callback: _favSearchCtr.onReload,

View File

@@ -37,12 +37,16 @@ class _FollowPageState extends State<FollowPage> {
),
actions: [
IconButton(
onPressed: () => Get.toNamed('/favSearch', arguments: {
onPressed: () => Get.toNamed(
'/favSearch',
arguments: {
'mid': int.parse(mid),
'searchType': SearchType.follow,
}),
},
),
icon: const Icon(Icons.search_outlined),
tooltip: '搜索'),
tooltip: '搜索',
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[

View File

@@ -9,7 +9,7 @@ import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/user/history.dart';
import 'package:PiliPlus/utils/storage.dart';
class HistoryController extends MultiSelectController
class HistoryController extends MultiSelectController<HistoryData, HisListItem>
with GetTickerProviderStateMixin {
HistoryController(this.type);
@@ -37,25 +37,27 @@ class HistoryController extends MultiSelectController
}
@override
onSelect(int index) {
List list = (loadingState.value as Success).response;
list[index].checked = !(list[index]?.checked ?? false);
onSelect(int index, [bool disableSelect = true]) {
List<HisListItem> list = (loadingState.value as Success).response;
list[index].checked = !(list[index].checked ?? false);
baseCtr.checkedCount.value =
list.where((item) => item.checked == true).length;
loadingState.value = LoadingState.success(list);
loadingState.refresh();
if (baseCtr.checkedCount.value == 0) {
baseCtr.enableMultiSelect.value = false;
}
}
@override
void handleSelect([bool checked = false]) {
void handleSelect([bool checked = false, bool disableSelect = true]) {
if (loadingState.value is Success) {
List list = (loadingState.value as Success).response;
if (list.isNotEmpty) {
loadingState.value = LoadingState.success(
list.map((item) => item..checked = checked).toList());
List<HisListItem>? list = (loadingState.value as Success).response;
if (list?.isNotEmpty == true) {
for (HisListItem item in list!) {
item.checked = checked;
}
baseCtr.checkedCount.value = checked ? list.length : 0;
loadingState.refresh();
}
}
if (checked.not) {
@@ -64,26 +66,28 @@ class HistoryController extends MultiSelectController
}
@override
bool customHandleResponse(Success response) {
List<HisListItem>? getDataList(HistoryData response) {
return response.list;
}
@override
bool customHandleResponse(bool isRefresh, Success<HistoryData> response) {
HistoryData data = response.response;
isEnd = data.list.isNullOrEmpty;
max = data.list?.lastOrNull?.history.oid;
viewAt = data.list?.lastOrNull?.viewAt;
if (currentPage == 1) {
if (type == null && tabs.isEmpty && data.tab?.isNotEmpty == true) {
if (isRefresh && type == null) {
if (tabs.isEmpty && data.tab?.isNotEmpty == true) {
tabs.value = data.tab!;
tabController =
TabController(length: data.tab!.length + 1, vsync: this);
}
} else if (loadingState.value is Success) {
data.list ??= <HisListItem>[];
data.list!.insertAll(
0,
List<HisListItem>.from((loadingState.value as Success).response),
tabController = TabController(
length: data.tab!.length + 1,
vsync: this,
);
}
loadingState.value = LoadingState.success(data.list);
return true;
}
return false;
}
// 观看历史暂停状态
@@ -125,7 +129,8 @@ class HistoryController extends MultiSelectController
}).toList();
dynamic response = await UserHttp.delHistory(kidList);
if (response['status']) {
List remainList = ((loadingState.value as Success).response as List)
List<HisListItem> remainList =
((loadingState.value as Success).response as List<HisListItem>)
.toSet()
.difference(result.toSet())
.toList();
@@ -179,7 +184,7 @@ class HistoryController extends MultiSelectController
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<HistoryData>> customGetData() =>
UserHttp.historyList(type: type ?? 'all', max: max, viewAt: viewAt);
@override

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/user/history.dart';
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
import 'package:PiliPlus/pages/history/base_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -261,7 +262,7 @@ class _HistoryPageState extends State<HistoryPage>
),
);
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<HisListItem>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -276,7 +277,7 @@ class _HistoryPageState extends State<HistoryPage>
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
@@ -290,18 +291,18 @@ class _HistoryPageState extends State<HistoryPage>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_historyController.onLoadMore();
}
return HistoryItem(
videoItem: loadingState.response[index],
videoItem: loadingState.response![index],
ctr: _historyController.baseCtr,
onChoose: () => _historyController.onSelect(index),
onDelete: (kid, business) =>
_historyController.delHistory(kid, business),
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
)

View File

@@ -24,14 +24,14 @@ class HistoryItem extends StatelessWidget {
final HisListItem videoItem;
final dynamic ctr;
final Function? onChoose;
final Function? onDelete;
final Function(dynamic kid, dynamic business) onDelete;
const HistoryItem({
super.key,
required this.videoItem,
this.ctr,
this.onChoose,
this.onDelete,
required this.onDelete,
});
@override
@@ -380,10 +380,8 @@ class HistoryItem extends StatelessWidget {
),
),
PopupMenuItem<String>(
onTap: () => onDelete != null
? onDelete!(videoItem.kid, videoItem.history.business)
: ctr.delHistory(
videoItem.kid, videoItem.history.business),
onTap: () =>
onDelete(videoItem.kid, videoItem.history.business),
height: 35,
child: const Row(
children: [

View File

@@ -19,7 +19,7 @@ class HomePage extends StatefulWidget {
}
class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
with AutomaticKeepAliveClientMixin {
final HomeController _homeController = Get.put(HomeController());
final MainController _mainController = Get.put(MainController());

View File

@@ -1,10 +1,12 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get.dart';
class HotController extends CommonController {
class HotController
extends CommonListController<List<HotVideoItemModel>, HotVideoItemModel> {
// int idx = 0;
late RxBool showHotRcmd = GStorage.showHotRcmd.obs;
@@ -22,7 +24,8 @@ class HotController extends CommonController {
// }
@override
Future<LoadingState> customGetData() => VideoHttp.hotVideoList(
Future<LoadingState<List<HotVideoItemModel>>> customGetData() =>
VideoHttp.hotVideoList(
pn: currentPage,
ps: 20,
);

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/rank/view.dart';
import 'package:flutter/material.dart';
@@ -160,10 +161,10 @@ class _HotPageState extends CommonPageState<HotPage, HotController>
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => _buildSkeleton(),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
@@ -172,15 +173,15 @@ class _HotPageState extends CommonPageState<HotPage, HotController>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
controller.onLoadMore();
}
return VideoCardH(
videoItem: loadingState.response[index],
videoItem: loadingState.response![index],
showPubdate: true,
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
)
: HttpError(

View File

@@ -13,7 +13,7 @@ import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:fixnum/fixnum.dart' as $fixnum;
class HtmlRenderController extends ReplyController {
class HtmlRenderController extends ReplyController<MainListReply> {
late String id;
late String dynamicType;
late int type;
@@ -91,7 +91,12 @@ class HtmlRenderController extends ReplyController {
}
@override
Future<LoadingState> customGetData() {
List<ReplyInfo>? getDataList(MainListReply response) {
return response.replies;
}
@override
Future<LoadingState<MainListReply>> customGetData() {
return ReplyHttp.replyListGrpc(
type: type,
oid: oid.value,

View File

@@ -763,7 +763,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
);
}
Widget replyList(LoadingState loadingState) {
Widget replyList(LoadingState<List<ReplyInfo>?> loadingState) {
return switch (loadingState) {
Loading() => SliverList.builder(
itemCount: 5,
@@ -771,11 +771,11 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
return const VideoReplySkeleton();
},
),
Success() => (loadingState.response.replies as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverList.builder(
itemCount: loadingState.response.replies.length + 1,
itemCount: loadingState.response!.length + 1,
itemBuilder: (context, index) {
if (index == loadingState.response.replies.length) {
if (index == loadingState.response!.length) {
_htmlRenderCtr.onLoadMore();
return Container(
alignment: Alignment.center,
@@ -785,7 +785,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
child: Text(
_htmlRenderCtr.isEnd.not
? '加载中...'
: loadingState.response.replies.isEmpty
: loadingState.response!.isEmpty
? '还没有评论'
: '没有更多了',
style: TextStyle(
@@ -796,19 +796,20 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
);
} else {
return ReplyItemGrpc(
replyItem: loadingState.response.replies[index],
replyItem: loadingState.response![index],
replyLevel: '1',
replyReply: (replyItem, id) =>
replyReply(context, replyItem, id),
onReply: () {
_htmlRenderCtr.onReply(
context,
replyItem: loadingState.response.replies[index],
replyItem: loadingState.response![index],
index: index,
);
},
onDelete: _htmlRenderCtr.onMDelete,
upMid: loadingState.response.subjectControl.upMid,
onDelete: (subIndex) =>
_htmlRenderCtr.onRemove(index, subIndex),
upMid: _htmlRenderCtr.upMid,
callback: _getImageCallback,
onCheckReply: (item) =>
_htmlRenderCtr.onCheckReply(context, item),

View File

@@ -9,7 +9,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/user.dart';
class LaterController extends MultiSelectController {
class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
RxInt count = (-1).obs;
dynamic mid;
@@ -22,25 +22,24 @@ class LaterController extends MultiSelectController {
}
@override
bool customHandleResponse(Success response) {
count.value = response.response['count'];
if (response.response['list'].isEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response['list'].insertAll(
0,
List<HotVideoItemModel>.from((loadingState.value as Success).response),
);
}
if (response.response['list'].length >= count.value) {
isEnd = true;
}
loadingState.value = LoadingState.success(response.response['list']);
return true;
List<HotVideoItemModel>? getDataList(response) {
return response['list'];
}
Future toViewDel(BuildContext context, {int? aid}) async {
@override
void checkIsEnd(int length) {
if (length >= count.value) {
isEnd = true;
}
}
@override
bool customHandleResponse(bool isRefresh, Success response) {
count.value = response.response['count'];
return false;
}
Future toViewDel(BuildContext context, {index, aid}) async {
await showDialog(
context: context,
builder: (context) {
@@ -50,7 +49,7 @@ class LaterController extends MultiSelectController {
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
actions: [
TextButton(
onPressed: () => Get.back(),
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -62,10 +61,11 @@ class LaterController extends MultiSelectController {
await UserHttp.toViewDel(aids: aid != null ? [aid] : null);
if (res['status']) {
if (aid != null) {
List list = (loadingState.value as Success).response;
list.removeWhere((e) => e.aid == aid);
List<HotVideoItemModel> list =
(loadingState.value as Success).response;
list.removeAt(index);
count.value -= 1;
loadingState.value = LoadingState.success(list);
loadingState.refresh();
} else {
onReload();
}
@@ -90,7 +90,7 @@ class LaterController extends MultiSelectController {
onConfirm: () async {
var res = await UserHttp.toViewClear();
if (res['status']) {
loadingState.value = LoadingState.success([]);
loadingState.value = LoadingState.success(null);
}
SmartDialog.showToast(res['msg']);
},
@@ -98,7 +98,7 @@ class LaterController extends MultiSelectController {
}
@override
Future<LoadingState> customGetData() => UserHttp.seeYouLater();
Future<LoadingState<Map>> customGetData() => UserHttp.seeYouLater();
onDelChecked(BuildContext context) {
showDialog(
@@ -137,7 +137,8 @@ class LaterController extends MultiSelectController {
List aids = result.map((item) => item.aid).toList();
dynamic res = await UserHttp.toViewDel(aids: aids);
if (res['status']) {
Set remainList = ((loadingState.value as Success).response as List)
Set<HotVideoItemModel> remainList =
((loadingState.value as Success).response as List<HotVideoItemModel>)
.toSet()
.difference(result.toSet());
count.value -= aids.length;

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/pages/history/view.dart' show AppBarWidget;
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
@@ -93,7 +94,7 @@ class _LaterPageState extends State<LaterPage> {
style: TextButton.styleFrom(
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
onPressed: () => Utils.onCopyOrMove<HotVideoItemModel>(
context: context,
isCopy: true,
ctr: _laterController,
@@ -110,7 +111,7 @@ class _LaterPageState extends State<LaterPage> {
style: TextButton.styleFrom(
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
onPressed: () => Utils.onCopyOrMove<HotVideoItemModel>(
context: context,
isCopy: false,
ctr: _laterController,
@@ -171,7 +172,7 @@ class _LaterPageState extends State<LaterPage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<HotVideoItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -186,7 +187,7 @@ class _LaterPageState extends State<LaterPage> {
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
@@ -195,7 +196,7 @@ class _LaterPageState extends State<LaterPage> {
),
delegate: SliverChildBuilderDelegate(
(context, index) {
var videoItem = loadingState.response[index];
var videoItem = loadingState.response![index];
return Stack(
children: [
VideoCardH(
@@ -209,7 +210,7 @@ class _LaterPageState extends State<LaterPage> {
'oid': videoItem.aid,
'heroTag': Utils.makeHeroTag(videoItem.bvid),
'sourceType': 'watchLater',
'count': loadingState.response.length,
'count': loadingState.response!.length,
'favTitle': '稍后再看',
'mediaId': _laterController.mid,
'desc': false,
@@ -293,6 +294,7 @@ class _LaterPageState extends State<LaterPage> {
onPressed: () {
_laterController.toViewDel(
context,
index: index,
aid: videoItem.aid,
);
},
@@ -305,7 +307,7 @@ class _LaterPageState extends State<LaterPage> {
],
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
)
: HttpError(

View File

@@ -1,12 +1,14 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/live.dart';
import 'package:PiliPlus/models/live/follow.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/models/live/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
class LiveController extends CommonController {
class LiveController
extends CommonListController<List<LiveItemModel>?, LiveItemModel> {
@override
void onInit() {
super.onInit();
@@ -17,7 +19,8 @@ class LiveController extends CommonController {
}
@override
Future<LoadingState> customGetData() => LiveHttp.liveList(pn: currentPage);
Future<LoadingState<List<LiveItemModel>?>> customGetData() =>
LiveHttp.liveList(pn: currentPage);
@override
Future onRefresh() {
@@ -41,20 +44,34 @@ class LiveController extends CommonController {
}
dynamic res = await LiveHttp.liveFollowing(pn: followPage, ps: 20);
if (res['status']) {
followPage++;
liveCount.value = res['data'].liveCount;
List list = res['data']
.list
.where((LiveFollowingItemModel item) =>
LiveFollowingModel data = res['data'];
liveCount.value = data.liveCount ?? 0;
List<LiveFollowingItemModel>? dataList = data.list
?.where((LiveFollowingItemModel item) =>
item.liveStatus == 1 && item.recordLiveTime == 0)
.toList();
if (isRefresh.not && followListState.value is Success) {
list.insertAll(0, (followListState.value as Success).response);
if (dataList.isNullOrEmpty) {
followEnd = true;
if (isRefresh) {
followListState.value = LoadingState.success(dataList);
}
followEnd = list.length >= liveCount.value ||
list.isEmpty ||
(res['data'].list as List?).isNullOrEmpty;
followListState.value = LoadingState.success(list);
return;
}
if (isRefresh) {
if (dataList!.length >= liveCount.value) {
followEnd = true;
}
followListState.value = LoadingState.success(dataList);
} else if (loadingState.value is Success) {
List<LiveFollowingItemModel> list =
(loadingState.value as Success).response;
list.addAll(dataList!);
if (list.length >= liveCount.value) {
followEnd = true;
}
loadingState.refresh();
}
followPage++;
} else {
followListState.value = LoadingState.error(res['msg']);
}

View File

@@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/live/item.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/live/controller.dart';
import 'package:PiliPlus/pages/live/widgets/live_item.dart';
@@ -66,17 +67,7 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
top: StyleString.cardSpace,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(
() => controller.loadingState.value is Loading ||
controller.loadingState.value is Success
? contentGrid(controller.loadingState.value)
: HttpError(
errMsg: controller.loadingState.value is Error
? (controller.loadingState.value as Error).errMsg
: '没有相关数据',
callback: controller.onReload,
),
),
sliver: Obx(() => _buildBody(controller.loadingState.value)),
),
],
),
@@ -84,33 +75,49 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
);
}
Widget contentGrid(LoadingState loadingState) {
return SliverGrid(
Widget _buildBody(LoadingState<List<LiveItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距
mainAxisSpacing: StyleString.cardSpace,
// 列间距
crossAxisSpacing: StyleString.cardSpace,
// 最大宽度
maxCrossAxisExtent: Grid.smallCardWidth,
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: MediaQuery.textScalerOf(context).scale(90),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (loadingState is Success &&
index == loadingState.response.length - 1) {
(context, index) {
return const VideoCardVSkeleton();
},
childCount: 10,
),
),
Success() => loadingState.response?.isNotEmpty == true
? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace,
maxCrossAxisExtent: Grid.smallCardWidth,
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: MediaQuery.textScalerOf(context).scale(90),
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response!.length - 1) {
controller.onLoadMore();
}
return loadingState is Success
? LiveCardV(
liveItem: loadingState.response[index],
)
: const VideoCardVSkeleton();
return LiveCardV(liveItem: loadingState.response![index]);
},
childCount: loadingState is Success ? loadingState.response.length : 10,
childCount: loadingState.response!.length,
),
);
)
: scrollErrorWidget(callback: controller.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
callback: controller.onReload,
),
_ => throw UnimplementedError(),
};
}
Widget _buildFollowList() {

View File

@@ -1,11 +1,13 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/utils/storage.dart';
class MediaController extends CommonController {
class MediaController
extends CommonDataController<FavFolderData, FavFolderData> {
List list = [
// {
// 'icon': Icons.file_download_outlined,
@@ -52,14 +54,14 @@ class MediaController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
count.value = response.response.count;
bool customHandleResponse(bool isRefresh, Success<FavFolderData> response) {
count.value = response.response.count ?? -1;
loadingState.value = response;
return true;
}
@override
Future<LoadingState> customGetData() {
Future<LoadingState<FavFolderData>> customGetData() {
mid ??= Accounts.main.mid;
return UserHttp.userfavFolder(
pn: 1,

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_article/item.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -38,10 +39,10 @@ class _MemberArticleState extends State<MemberArticle>
return Obx(() => _buildBody(_controller.loadingState.value));
}
_buildBody(LoadingState loadingState) {
_buildBody(LoadingState<List<Item>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator(
onRefresh: () async {
await _controller.onRefresh();
@@ -56,14 +57,14 @@ class _MemberArticleState extends State<MemberArticle>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_controller.onLoadMore();
}
return MemberArticleItem(
item: loadingState.response[index],
item: loadingState.response![index],
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
],

View File

@@ -2,10 +2,9 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space_article/item.dart';
import 'package:PiliPlus/models/space_article/data.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
class MemberArticleCtr extends CommonController {
class MemberArticleCtr extends CommonListController<Data, Item> {
MemberArticleCtr({
required this.mid,
});
@@ -21,24 +20,24 @@ class MemberArticleCtr extends CommonController {
}
@override
bool customHandleResponse(Success response) {
Data data = response.response;
if (data.item.isNullOrEmpty) {
isEnd = true;
}
count = data.count ?? -1;
if (currentPage != 1 && loadingState.value is Success) {
data.item ??= <Item>[];
data.item!.insertAll(0, (loadingState.value as Success).response);
}
if ((data.item?.length ?? -1) >= count) {
isEnd = true;
}
loadingState.value = LoadingState.success(data.item);
return true;
List<Item>? getDataList(Data response) {
return response.item;
}
@override
Future<LoadingState> customGetData() =>
void checkIsEnd(int length) {
if (length >= count) {
isEnd = true;
}
}
@override
bool customHandleResponse(bool isRefresh, Success<Data> response) {
count = response.response.count ?? -1;
return false;
}
@override
Future<LoadingState<Data>> customGetData() =>
MemberHttp.spaceArticle(mid: mid, page: currentPage);
}

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -41,10 +42,10 @@ class _MemberBangumiState extends State<MemberBangumi>
return Obx(() => _buildBody(_controller.loadingState.value));
}
_buildBody(LoadingState loadingState) {
_buildBody(LoadingState<List<Item>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator(
onRefresh: () async {
await _controller.onRefresh();
@@ -70,14 +71,14 @@ class _MemberBangumiState extends State<MemberBangumi>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_controller.onLoadMore();
}
return BangumiCardVMemberHome(
bangumiItem: loadingState.response[index],
bangumiItem: loadingState.response![index],
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
),

View File

@@ -2,15 +2,14 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/models/space/data.dart' as space;
class MemberBangumiCtr extends CommonController {
class MemberBangumiCtr extends CommonListController<Data, Item> {
MemberBangumiCtr({
required this.mid,
required this.heroTag,
@@ -37,24 +36,19 @@ class MemberBangumiCtr extends CommonController {
}
@override
bool customHandleResponse(Success response) {
Data data = response.response;
if (data.item.isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
data.item ??= <Item>[];
data.item!.insertAll(0, (loadingState.value as Success).response);
}
if (isEnd.not && count != null && data.item!.length >= count!) {
isEnd = true;
}
loadingState.value = LoadingState.success(data.item);
return true;
List<Item>? getDataList(Data response) {
return response.item;
}
@override
Future<LoadingState> customGetData() => MemberHttp.spaceArchive(
void checkIsEnd(int length) {
if (count != null && length >= count!) {
isEnd = true;
}
}
@override
Future<LoadingState<Data>> customGetData() => MemberHttp.spaceArchive(
type: ContributeType.bangumi,
mid: mid,
pn: currentPage,

View File

@@ -4,12 +4,12 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space_fav/datum.dart';
import 'package:PiliPlus/models/space_fav/list.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class MemberFavoriteCtr extends CommonController {
class MemberFavoriteCtr extends CommonDataController {
MemberFavoriteCtr({
required this.mid,
});
@@ -39,7 +39,7 @@ class MemberFavoriteCtr extends CommonController {
}
@override
bool customHandleResponse(Success response) {
bool customHandleResponse(bool isRefresh, Success response) {
try {
List<Datum> res = response.response;
first.value = res.first;

View File

@@ -1,10 +1,11 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
class SeasonSeriesController extends CommonController {
class SeasonSeriesController extends CommonListController {
SeasonSeriesController(this.mid);
final int mid;
int? count;
@override
void onInit() {
@@ -13,16 +14,22 @@ class SeasonSeriesController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
Map data = response.response;
List list = ((data['seasons_list'] as List?) ?? []) +
((data['series_list'] as List?) ?? []);
if (currentPage != 0 && loadingState.value is Success) {
list.insertAll(0, (loadingState.value as Success).response);
List? getDataList(response) {
return ((response['seasons_list'] as List?) ?? []) +
((response['series_list'] as List?) ?? []);
}
isEnd = list.length >= ((data['page']['total'] as int?) ?? 0);
loadingState.value = LoadingState.success(list);
return true;
@override
void checkIsEnd(int length) {
if (count != null && length >= count!) {
isEnd = true;
}
}
@override
bool customHandleResponse(bool isRefresh, Success response) {
count = response.response['page']?['total'];
return false;
}
@override

View File

@@ -39,10 +39,10 @@ class _SeasonSeriesPageState extends State<SeasonSeriesPage>
return Obx(() => _buildBody(_controller.loadingState.value));
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? CustomScrollView(
slivers: [
SliverPadding(
@@ -58,13 +58,13 @@ class _SeasonSeriesPageState extends State<SeasonSeriesPage>
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_controller.onLoadMore();
}
dynamic item = loadingState.response![index];
return SeasonSeriesCard(
item: loadingState.response[index],
item: item,
onTap: () {
dynamic item = loadingState.response[index];
bool isSeason = item['meta']['season_id'] != null;
dynamic id = isSeason
? item['meta']['season_id']
@@ -89,7 +89,7 @@ class _SeasonSeriesPageState extends State<SeasonSeriesPage>
},
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
),

View File

@@ -60,10 +60,10 @@ class _MemberVideoState extends State<MemberVideo>
return Obx(() => _buildBody(_controller.loadingState.value));
}
_buildBody(LoadingState loadingState) {
_buildBody(LoadingState<List<Item>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? Stack(
clipBehavior: Clip.none,
children: [
@@ -186,17 +186,17 @@ class _MemberVideoState extends State<MemberVideo>
delegate: SliverChildBuilderDelegate(
(context, index) {
if (widget.type != ContributeType.season &&
index == loadingState.response.length - 1) {
index == loadingState.response!.length - 1) {
_controller.onLoadMore();
}
final Item item = loadingState.response[index];
final Item item = loadingState.response![index];
return VideoCardHMemberVideo(
key: ValueKey('${item.param}'),
videoItem: item,
fromViewAid: _controller.fromViewAid,
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
),

View File

@@ -4,7 +4,7 @@ import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/episodic_button.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPlus/utils/extension.dart';
@@ -13,7 +13,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class MemberVideoCtr extends CommonController {
class MemberVideoCtr extends CommonListController<Data, Item> {
MemberVideoCtr({
required this.type,
required this.mid,
@@ -70,7 +70,7 @@ class MemberVideoCtr extends CommonController {
}
@override
bool customHandleResponse(Success response) {
bool customHandleResponse(bool isRefresh, Success<Data> response) {
Data data = response.response;
episodicButton.value = data.episodicButton ?? EpisodicButton();
episodicButton.refresh();
@@ -105,7 +105,7 @@ class MemberVideoCtr extends CommonController {
}
@override
Future<LoadingState> customGetData() => MemberHttp.spaceArchive(
Future<LoadingState<Data>> customGetData() => MemberHttp.spaceArchive(
type: type,
mid: mid,
aid: type == ContributeType.video

View File

@@ -2,7 +2,7 @@ import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
@@ -10,7 +10,7 @@ import 'package:get/get.dart';
import '../../../../../models/space/item.dart';
class MemberContributeCtr extends CommonController
class MemberContributeCtr extends CommonDataController
with GetTickerProviderStateMixin {
MemberContributeCtr({
required this.heroTag,

View File

@@ -1,67 +0,0 @@
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel_grpc.dart';
import 'package:PiliPlus/pages/member/new/content/member_dynamic/member_dynamic_ctr.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@Deprecated('Use MemberDynamicsPage instead')
class MemberDynamic extends StatefulWidget {
const MemberDynamic({
super.key,
required this.mid,
});
final int mid;
@override
State<MemberDynamic> createState() => _MemberDynamicState();
}
class _MemberDynamicState extends State<MemberDynamic>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
late final _controller = Get.put(MemberDynamicCtr(mid: widget.mid));
@override
Widget build(BuildContext context) {
super.build(context);
return Obx(() => _buildBody(_controller.loadingState.value));
}
_buildBody(LoadingState loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
? refreshIndicator(
onRefresh: () async {
await _controller.onRefresh();
},
child: ListView.separated(
itemCount: loadingState.response.length,
itemBuilder: (context, index) {
if (index == loadingState.response.length - 1) {
_controller.onLoadMore();
}
return DynamicPanelGrpc(
item: loadingState.response[index],
);
},
separatorBuilder: (context, index) =>
const SizedBox(height: 10),
),
)
: scrollErrorWidget(
callback: _controller.onReload,
),
Error() => scrollErrorWidget(
errMsg: loadingState.errMsg,
callback: _controller.onReload,
),
LoadingState() => throw UnimplementedError(),
};
}
}

View File

@@ -1,28 +0,0 @@
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
class MemberDynamicCtr extends CommonController {
MemberDynamicCtr({
required this.mid,
});
int mid;
@override
bool customHandleResponse(Success response) {
DynSpaceRsp res = response.response;
isEnd = !res.hasMore;
if (currentPage != 1 && loadingState.value is Success) {
res.list.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(res.list);
return true;
}
@override
Future<LoadingState> customGetData() => MemberHttp.spaceDynamic(
mid: mid,
page: currentPage,
);
}

View File

@@ -6,7 +6,7 @@ import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -20,7 +20,7 @@ extension MemberTabTypeExt on MemberTabType {
String get title => ['默认', '首页', '动态', '投稿', '收藏', '番剧'][index];
}
class MemberControllerNew extends CommonController
class MemberControllerNew extends CommonDataController<Data, dynamic>
with GetTickerProviderStateMixin {
MemberControllerNew({required this.mid});
int mid;
@@ -58,7 +58,7 @@ class MemberControllerNew extends CommonController
];
@override
bool customHandleResponse(Success response) {
bool customHandleResponse(bool isRefresh, Success<Data> response) {
Data data = response.response;
username = data.card?.name ?? '';
isFollow.value = data.card?.relation?.isFollow == 1;
@@ -138,7 +138,7 @@ class MemberControllerNew extends CommonController
}
@override
Future<LoadingState> customGetData() => MemberHttp.space(
Future<LoadingState<Data>> customGetData() => MemberHttp.space(
mid: mid,
fromViewAid: fromViewAid,
);

View File

@@ -24,8 +24,7 @@ class MemberPageNew extends StatefulWidget {
State<MemberPageNew> createState() => _MemberPageNewState();
}
class _MemberPageNewState extends State<MemberPageNew>
with TickerProviderStateMixin {
class _MemberPageNewState extends State<MemberPageNew> {
late final int _mid;
late final String _heroTag;
late final MemberControllerNew _userController;

View File

@@ -1,8 +1,10 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
class MemberCoinController extends CommonController {
class MemberCoinController extends CommonListController<
List<MemberCoinsDataModel>?, MemberCoinsDataModel> {
final dynamic mid;
MemberCoinController({this.mid});
@@ -13,6 +15,6 @@ class MemberCoinController extends CommonController {
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<List<MemberCoinsDataModel>?>> customGetData() =>
MemberHttp.getRecentCoinVideo(mid: mid);
}

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/member_coin/controller.dart';
import 'package:PiliPlus/pages/member_coin/widgets/item.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -41,10 +42,10 @@ class _MemberCoinPageState extends State<MemberCoinPage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<MemberCoinsDataModel>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? GridView.builder(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
@@ -59,9 +60,9 @@ class _MemberCoinPageState extends State<MemberCoinPage> {
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: MediaQuery.textScalerOf(context).scale(75),
),
itemCount: loadingState.response.length,
itemCount: loadingState.response!.length,
itemBuilder: (context, index) {
return MemberCoinsItem(coinItem: loadingState.response[index]);
return MemberCoinsItem(coinItem: loadingState.response![index]);
},
)
: scrollErrorWidget(callback: _ctr.onReload),

View File

@@ -1,12 +1,13 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class MemberDynamicsController extends CommonController {
class MemberDynamicsController
extends CommonListController<DynamicsDataModel, DynamicItemModel> {
MemberDynamicsController(this.mid);
int mid;
String offset = '';
@@ -32,22 +33,24 @@ class MemberDynamicsController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
DynamicsDataModel data = response.response;
offset = data.offset?.isNotEmpty == true ? data.offset! : '-1';
if (data.hasMore == false || data.items.isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
data.items ??= <DynamicItemModel>[];
data.items?.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(data.items);
return true;
List<DynamicItemModel>? getDataList(DynamicsDataModel response) {
return response.items;
}
@override
Future<LoadingState> customGetData() => MemberHttp.memberDynamic(
bool customHandleResponse(
bool isRefresh, Success<DynamicsDataModel> response) {
DynamicsDataModel data = response.response;
offset = data.offset?.isNotEmpty == true ? data.offset! : '-1';
if (data.hasMore == false) {
isEnd = true;
}
return false;
}
@override
Future<LoadingState<DynamicsDataModel>> customGetData() =>
MemberHttp.memberDynamic(
offset: offset,
mid: mid,
);
@@ -55,9 +58,9 @@ class MemberDynamicsController extends CommonController {
Future onRemove(dynamicId) async {
var res = await MsgHttp.removeDynamic(dynamicId);
if (res['status']) {
List list = (loadingState.value as Success).response;
List<DynamicItemModel> list = (loadingState.value as Success).response;
list.removeWhere((item) => item.idStr == dynamicId);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -1,5 +1,7 @@
import 'package:PiliPlus/common/skeleton/dynamic_card.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/pages/member_dynamics/index.dart';
@@ -56,26 +58,57 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
onRefresh: () async {
await _memberDynamicController.onRefresh();
},
child: Obx(
() => _memberDynamicController.loadingState.value is Loading
? Center(
child: CircularProgressIndicator(),
)
: CustomScrollView(
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
_buildContent(_memberDynamicController.loadingState.value),
Obx(
() => _buildContent(_memberDynamicController.loadingState.value),
)
],
),
),
);
_buildContent(LoadingState loadingState) {
return switch (loadingState) {
Loading() => HttpError(
callback: _memberDynamicController.onReload,
Widget skeleton() {
if (!dynamicsWaterfallFlow) {
return SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return const DynamicCardSkeleton();
},
childCount: 10,
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
),
),
const SliverFillRemaining()
],
);
}
return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
crossAxisSpacing: StyleString.cardSpace / 2,
mainAxisSpacing: StyleString.cardSpace / 2,
maxCrossAxisExtent: Grid.smallCardWidth * 2,
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: 50,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return const DynamicCardSkeleton();
},
childCount: 10,
),
);
}
Widget _buildContent(LoadingState<List<DynamicItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => skeleton(),
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
@@ -83,24 +116,12 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
sliver: dynamicsWaterfallFlow
? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
//cacheExtent: 0.0,
crossAxisSpacing: StyleString.safeSpace,
// mainAxisSpacing: StyleString.safeSpace,
/// follow max child trailing layout offset and layout with full cross axis extend
/// last child as loadmore item/no more item in [GridView] and [WaterfallFlow]
/// with full cross axis extend
// LastChildLayoutType.fullCrossAxisExtend,
/// as foot at trailing and layout with full cross axis extend
/// show no more item at trailing when children are not full of viewport
/// if children is full of viewport, it's the same as fullCrossAxisExtend
// LastChildLayoutType.foot,
lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_memberDynamicController.onLoadMore();
}
return index == loadingState.response.length
return index == loadingState.response!.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
@@ -121,15 +142,16 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index ==
loadingState.response!.length - 1) {
_memberDynamicController.onLoadMore();
}
return DynamicPanel(
item: loadingState.response[index],
item: loadingState.response![index],
onRemove: _memberDynamicController.onRemove,
);
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
),

View File

@@ -1,8 +1,10 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
class MemberLikeController extends CommonController {
class MemberLikeController extends CommonListController<
List<MemberCoinsDataModel>?, MemberCoinsDataModel> {
final dynamic mid;
MemberLikeController({this.mid});
@@ -13,6 +15,6 @@ class MemberLikeController extends CommonController {
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<List<MemberCoinsDataModel>?>> customGetData() =>
MemberHttp.getRecentLikeVideo(mid: mid);
}

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/member_coin/widgets/item.dart';
import 'package:PiliPlus/pages/member_like/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -41,10 +42,10 @@ class _MemberLikePageState extends State<MemberLikePage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<MemberCoinsDataModel>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? GridView.builder(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
@@ -59,9 +60,9 @@ class _MemberLikePageState extends State<MemberLikePage> {
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: MediaQuery.textScalerOf(context).scale(75),
),
itemCount: loadingState.response.length,
itemCount: loadingState.response!.length,
itemBuilder: (context, index) {
return MemberCoinsItem(coinItem: loadingState.response[index]);
return MemberCoinsItem(coinItem: loadingState.response![index]);
},
)
: scrollErrorWidget(callback: _ctr.onReload),

View File

@@ -91,22 +91,31 @@ class MemberSearchController extends GetxController
);
if (res['status']) {
DynamicsDataModel data = res['data'];
if (data.hasMore == false || data.items.isNullOrEmpty) {
isEndDynamic = true;
}
if (isRefresh) {
List<DynamicItemModel>? items = data.items;
dynamicCount.value = data.total ?? 0;
}
offset = data.offset ?? '';
if (isRefresh.not && dynamicState.value is Success) {
data.items ??= <DynamicItemModel>[];
data.items!.insertAll(0, (dynamicState.value as Success).response);
}
if (!isEndDynamic && (data.items?.length ?? 0) >= dynamicCount.value) {
if (data.hasMore == false || items.isNullOrEmpty) {
isEndDynamic = true;
dynamicState.value = LoadingState.success(items);
return;
}
if (isRefresh) {
if (items!.length >= dynamicCount.value) {
isEndDynamic = true;
}
dynamicState.value = LoadingState.success(items);
} else if (dynamicState.value is Success) {
List<DynamicItemModel> currentList =
(dynamicState.value as Success).response;
currentList.addAll(items!);
if (currentList.length >= dynamicCount.value) {
isEndDynamic = true;
}
dynamicState.refresh();
}
dynamicPn++;
dynamicState.value = LoadingState.success(data.items);
} else if (isRefresh) {
dynamicState.value = LoadingState.error(res['msg']);
}
@@ -124,24 +133,30 @@ class MemberSearchController extends GetxController
);
if (res['status']) {
MemberArchiveDataModel data = res['data'];
if (isRefresh) {
List<VListItemModel>? vlist = data.list?.vlist;
archiveCount.value = data.page?['count'] ?? 0;
if (vlist.isNullOrEmpty) {
isEndArchive = true;
archiveState.value = LoadingState.success(vlist);
return;
}
if (data.list == null || data.list!.vlist.isNullOrEmpty) {
if (isRefresh) {
if (vlist!.length >= archiveCount.value) {
isEndArchive = true;
}
if (isRefresh.not && archiveState.value is Success) {
data.list ??= ArchiveListModel();
data.list!.vlist ??= <VListItemModel>[];
data.list!.vlist!
.insertAll(0, (archiveState.value as Success).response);
archiveState.value = LoadingState.success(vlist);
} else if (dynamicState.value is Success) {
List<VListItemModel> currentList =
(dynamicState.value as Success).response;
currentList.addAll(vlist!);
if (currentList.length >= archiveCount.value) {
isEndDynamic = true;
}
if (!isEndArchive &&
(data.list?.vlist?.length ?? 0) >= archiveCount.value) {
isEndArchive = true;
archiveState.refresh();
}
archivePn++;
archiveState.value = LoadingState.success(data.list?.vlist);
} else if (isRefresh) {
archiveState.value = LoadingState.error(res['msg']);
}

View File

@@ -1,11 +1,10 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/models/msg/msgfeed_at_me.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class AtMeController extends CommonController {
class AtMeController extends CommonListController<MsgFeedAtMe, AtMeItems> {
int cursor = -1;
int cursorTime = -1;
@@ -16,19 +15,19 @@ class AtMeController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
List<AtMeItems>? getDataList(MsgFeedAtMe response) {
return response.items;
}
@override
bool customHandleResponse(bool isRefresh, Success<MsgFeedAtMe> response) {
MsgFeedAtMe data = response.response;
if (data.cursor?.isEnd == true || data.items.isNullOrEmpty) {
if (data.cursor?.isEnd == true) {
isEnd = true;
}
cursor = data.cursor?.id ?? -1;
cursorTime = data.cursor?.time ?? -1;
if (currentPage != 1 && loadingState.value is Success) {
data.items ??= <AtMeItems>[];
data.items!.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(data.items);
return true;
return false;
}
@override
@@ -39,16 +38,16 @@ class AtMeController extends CommonController {
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<MsgFeedAtMe>> customGetData() =>
MsgHttp.msgFeedAtMe(cursor: cursor, cursorTime: cursorTime);
Future onRemove(dynamic id, int index) async {
try {
var res = await MsgHttp.delMsgfeed(2, id);
if (res['status']) {
List list = (loadingState.value as Success).response;
List<AtMeItems> list = (loadingState.value as Success).response;
list.removeAt(index);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/msg/msgfeed_at_me.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -35,20 +36,20 @@ class _AtMePageState extends State<AtMePage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<AtMeItems>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? ListView.separated(
itemCount: loadingState.response.length,
itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_atMeController.onLoadMore();
}
final item = loadingState.response[index];
final item = loadingState.response![index];
return ListTile(
onTap: () {
String? nativeUri = item.item?.nativeUri;
@@ -103,10 +104,9 @@ class _AtMePageState extends State<AtMePage> {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if ((item.item?.sourceContent as String?)?.isNotEmpty ==
true) ...[
if (item.item?.sourceContent?.isNotEmpty == true) ...[
const SizedBox(height: 4),
Text(item.item?.sourceContent,
Text(item.item!.sourceContent!,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)

View File

@@ -1,12 +1,12 @@
import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/models/msg/msgfeed_like_me.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class LikeMeController extends CommonController {
class LikeMeController extends CommonDataController<MsgFeedLikeMe, dynamic> {
int cursor = -1;
int cursorTime = -1;
@@ -17,7 +17,7 @@ class LikeMeController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
bool customHandleResponse(bool isRefresh, Success<MsgFeedLikeMe> response) {
MsgFeedLikeMe data = response.response;
if (data.total?.cursor?.isEnd == true ||
data.total?.items.isNullOrEmpty == true) {
@@ -46,7 +46,7 @@ class LikeMeController extends CommonController {
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<MsgFeedLikeMe>> customGetData() =>
MsgHttp.msgFeedLikeMe(cursor: cursor, cursorTime: cursorTime);
Future onRemove(dynamic id, int index, bool isLatest) async {

View File

@@ -1,11 +1,11 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class ReplyMeController extends CommonController {
class ReplyMeController
extends CommonListController<MsgFeedReplyMe, ReplyMeItems> {
int cursor = -1;
int cursorTime = -1;
@@ -16,19 +16,19 @@ class ReplyMeController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
MsgFeedReplyMe data = response.response;
if (data.cursor?.isEnd == true || data.items.isNullOrEmpty) {
List<ReplyMeItems>? getDataList(MsgFeedReplyMe response) {
return response.items;
}
@override
bool customHandleResponse(bool isRefresh, Success<MsgFeedReplyMe> response) {
final data = response.response;
if (data.cursor?.isEnd == true) {
isEnd = true;
}
cursor = data.cursor?.id ?? -1;
cursorTime = data.cursor?.time ?? -1;
if (currentPage != 1 && loadingState.value is Success) {
data.items ??= <ReplyMeItems>[];
data.items!.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(data.items);
return true;
return false;
}
@override
@@ -39,16 +39,16 @@ class ReplyMeController extends CommonController {
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<MsgFeedReplyMe>> customGetData() =>
MsgHttp.msgFeedReplyMe(cursor: cursor, cursorTime: cursorTime);
Future onRemove(dynamic id, int index) async {
try {
var res = await MsgHttp.delMsgfeed(1, id);
if (res['status']) {
List list = (loadingState.value as Success).response;
List<ReplyMeItems> list = (loadingState.value as Success).response;
list.removeAt(index);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -34,21 +34,21 @@ class _ReplyMePageState extends State<ReplyMePage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<ReplyMeItems>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? ListView.separated(
itemCount: loadingState.response.length,
itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_replyMeController.onLoadMore();
}
ReplyMeItems item = loadingState.response[index];
ReplyMeItems item = loadingState.response![index];
return ListTile(
onTap: () {
String? nativeUri = item.item?.nativeUri;
@@ -121,12 +121,8 @@ class _ReplyMePageState extends State<ReplyMePage> {
Text(item.item?.sourceContent ?? "",
style: Theme.of(context).textTheme.bodyMedium),
const SizedBox(height: 4),
if (loadingState
.response[index].item?.targetReplyContent !=
null &&
loadingState
.response[index].item?.targetReplyContent !=
"")
if (item.item?.targetReplyContent != null &&
item.item?.targetReplyContent != "")
Text("| ${item.item?.targetReplyContent}",
maxLines: 1,
overflow: TextOverflow.ellipsis,

View File

@@ -1,10 +1,12 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:PiliPlus/http/msg.dart';
class SysMsgController extends CommonController {
class SysMsgController
extends CommonListController<List<SystemNotifyList>?, SystemNotifyList> {
final pageSize = 20;
int cursor = -1;
@@ -41,9 +43,9 @@ class SysMsgController extends CommonController {
try {
var res = await MsgHttp.delSysMsg(id);
if (res['status']) {
List list = (loadingState.value as Success).response;
List<SystemNotifyList> list = (loadingState.value as Success).response;
list.removeAt(index);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);
@@ -52,6 +54,6 @@ class SysMsgController extends CommonController {
}
@override
Future<LoadingState> customGetData() =>
Future<LoadingState<List<SystemNotifyList>?>> customGetData() =>
MsgHttp.msgFeedNotify(cursor: cursor, pageSize: pageSize);
}

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
@@ -39,21 +40,21 @@ class _SysMsgPageState extends State<SysMsgPage> {
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<SystemNotifyList>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? ListView.separated(
itemCount: loadingState.response.length,
itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
_sysMsgController.onLoadMore();
}
final item = loadingState.response[index];
final item = loadingState.response![index];
String? content = item.content;
if (content != null) {
try {

View File

@@ -10,7 +10,7 @@ class RankPage extends StatefulWidget {
}
class _RankPageState extends State<RankPage>
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
final RankController _rankController = Get.put(RankController());
@override

Some files were not shown because too many files have changed in this diff Show More