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

View File

@@ -4,7 +4,8 @@ import '../models/user/black.dart';
import 'index.dart'; import 'index.dart';
class BlackHttp { 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: { var res = await Request().get(Api.blackLst, queryParameters: {
'pn': pn, 'pn': pn,
'ps': ps ?? 50, 'ps': ps ?? 50,

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ import 'api.dart';
import 'init.dart'; import 'init.dart';
class MsgHttp { class MsgHttp {
static Future<LoadingState> msgFeedReplyMe( static Future<LoadingState<MsgFeedReplyMe>> msgFeedReplyMe(
{int cursor = -1, int cursorTime = -1}) async { {int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedReply, queryParameters: { var res = await Request().get(Api.msgFeedReply, queryParameters: {
'id': cursor == -1 ? null : cursor, '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 { {int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedAt, queryParameters: { var res = await Request().get(Api.msgFeedAt, queryParameters: {
'id': cursor == -1 ? null : cursor, '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 { {int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedLike, queryParameters: { var res = await Request().get(Api.msgFeedLike, queryParameters: {
'id': cursor == -1 ? null : cursor, '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 { {int cursor = -1, int pageSize = 20}) async {
var res = await Request().get(Api.msgSysNotify, queryParameters: { var res = await Request().get(Api.msgSysNotify, queryParameters: {
'cursor': cursor == -1 ? null : cursor, '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, int type = 1,
required int oid, required int oid,
required CursorReq cursor, 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: { var res = await Request().get(Api.myEmote, queryParameters: {
'business': business ?? 'reply', 'business': business ?? 'reply',
'web_location': '333.1245', '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( final dynamic res = await Request().get(
Api.bangumiInfo, Api.bangumiInfo,
queryParameters: { queryParameters: {

View File

@@ -46,7 +46,7 @@ class UserHttp {
} }
// 收藏夹 // 收藏夹
static Future<LoadingState> userfavFolder({ static Future<LoadingState<FavFolderData>> userfavFolder({
required int pn, required int pn,
required int ps, required int ps,
required dynamic mid, required dynamic mid,
@@ -170,7 +170,7 @@ class UserHttp {
} }
} }
static Future<LoadingState> userFavFolderDetail( static Future<LoadingState<FavDetailData>> userFavFolderDetail(
{required int mediaId, {required int mediaId,
required int pn, required int pn,
required int ps, 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); var res = await Request().get(Api.seeYouLater);
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
if (res.data['data']['count'] == 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, required String type,
int? max, int? max,
int? viewAt, int? viewAt,
@@ -426,7 +426,7 @@ class UserHttp {
} }
// 我的订阅 // 我的订阅
static Future<LoadingState> userSubFolder({ static Future<LoadingState<List<SubFolderItemData>?>> userSubFolder({
required int mid, required int mid,
required int pn, required int pn,
required int ps, 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/app/card/v1/card.pb.dart' as card;
import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/member/article.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.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 { {required int pn, required int ps}) async {
var res = await Request().get( var res = await Request().get(
Api.hotList, 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 = var res =
await Request().get(Api.relatedList, queryParameters: {'bvid': bvid}); await Request().get(Api.relatedList, queryParameters: {'bvid': bvid});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
final items = final items = (res.data['data'] as List?)
(res.data['data'] as List).map((i) => HotVideoItemModel.fromJson(i)); ?.map((i) => HotVideoItemModel.fromJson(i));
final list = RecommendFilter.applyFilterToRelatedVideos final list = RecommendFilter.applyFilterToRelatedVideos
? items.where((i) => !RecommendFilter.filterAll(i)).toList() ? items?.where((i) => !RecommendFilter.filterAll(i)).toList()
: items.toList(); : items?.toList();
return LoadingState.success(list); return LoadingState.success(list);
} else { } else {
return LoadingState.error(res.data['message']); 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 rankApi = "${Api.getRankApi}?rid=$rid&type=all";
var res = await Request().get(rankApi); var res = await Request().get(rankApi);
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
@@ -1094,7 +1097,7 @@ class VideoHttp {
} }
} }
static Future<LoadingState> noteList({ static Future<LoadingState<List<FavArticleModel>?>> noteList({
required int page, required int page,
}) async { }) async {
var res = await Request().get( var res = await Request().get(
@@ -1106,13 +1109,16 @@ class VideoHttp {
}, },
); );
if (res.data['code'] == 0) { 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 { } else {
return LoadingState.error(res.data['message']); return LoadingState.error(res.data['message']);
} }
} }
static Future<LoadingState> userNoteList({ static Future<LoadingState<List<FavArticleModel>?>> userNoteList({
required int page, required int page,
}) async { }) async {
var res = await Request().get( var res = await Request().get(
@@ -1124,7 +1130,10 @@ class VideoHttp {
}, },
); );
if (res.data['code'] == 0) { 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 { } else {
return LoadingState.error(res.data['message']); 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 { class BangumiListDataModel {
BangumiListDataModel({ BangumiListDataModel({
this.hasNext, this.hasNext,
@@ -24,7 +27,7 @@ class BangumiListDataModel {
} }
} }
class BangumiListItemModel { class BangumiListItemModel with MultiSelectData {
BangumiListItemModel({ BangumiListItemModel({
this.badge, this.badge,
this.badgeType, this.badgeType,
@@ -66,8 +69,6 @@ class BangumiListItemModel {
Map? newEp; Map? newEp;
String? progress; String? progress;
bool? checked;
BangumiListItemModel.fromJson(Map<String, dynamic> json) { BangumiListItemModel.fromJson(Map<String, dynamic> json) {
badge = json['badge'] == '' ? null : json['badge']; badge = json['badge'] == '' ? null : json['badge'];
badgeType = json['badge_type']; badgeType = json['badge_type'];

View File

@@ -23,8 +23,7 @@ class LiveFollowingModel {
count = json['count']; count = json['count'];
list = (json['list'] as List?) list = (json['list'] as List?)
?.map((item) => LiveFollowingItemModel.fromJson(item)) ?.map((item) => LiveFollowingItemModel.fromJson(item))
.toList() ?? .toList();
<LiveFollowingItemModel>[];
liveCount = json['live_count']; liveCount = json['live_count'];
neverLivedCount = json['never_lived_count']; neverLivedCount = json['never_lived_count'];
neverLivedFaces = json['never_lived_faces']; 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({ MemberTagItemModel({
this.count, this.count,
this.name, this.name,
this.tagid, this.tagid,
this.tip, this.tip,
this.checked,
}); });
int? count; int? count;
String? name; String? name;
int? tagid; int? tagid;
String? tip; String? tip;
bool? checked;
MemberTagItemModel.fromJson(Map<String, dynamic> json) { MemberTagItemModel.fromJson(Map<String, dynamic> json) {
count = json['count']; count = json['count'];
name = json['name']; name = json['name'];
tagid = json['tagid']; tagid = json['tagid'];
tip = json['tip']; 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_owner.dart';
import 'model_rec_video_item.dart'; import 'model_rec_video_item.dart';
import 'model_video.dart'; import 'model_video.dart';
// 稍后再看, 排行榜等网页返回也使用该类 // 稍后再看, 排行榜等网页返回也使用该类
class HotVideoItemModel extends BaseRecVideoItemModel { class HotVideoItemModel extends BaseRecVideoItemModel with MultiSelectData {
int? videos; int? videos;
int? tid; int? tid;
String? tname; String? tname;
@@ -16,8 +18,6 @@ class HotVideoItemModel extends BaseRecVideoItemModel {
String? pgcLabel; String? pgcLabel;
String? redirectUrl; String? redirectUrl;
bool? checked; // 手动设置的
num? progress; num? progress;
HotVideoItemModel.fromJson(Map<String, dynamic> json) { 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_owner.dart';
import '../model_video.dart'; import '../model_video.dart';
import 'fav_folder.dart'; import 'fav_folder.dart';
@@ -18,7 +21,7 @@ class FavDetailData {
} }
} }
class FavDetailItemData extends BaseVideoItemModel { class FavDetailItemData extends BaseVideoItemModel with MultiSelectData {
int? id; int? id;
int? type; int? type;
int? page; int? page;
@@ -31,7 +34,6 @@ class FavDetailItemData extends BaseVideoItemModel {
int? favTime; int? favTime;
Map? ogv; Map? ogv;
String? epId; String? epId;
bool? checked;
FavDetailItemData.fromJson(Map<String, dynamic> json) { FavDetailItemData.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];

View File

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

View File

@@ -1,14 +1,15 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/list.dart'; import 'package:PiliPlus/models/bangumi/list.dart';
import 'package:PiliPlus/models/common/tab_type.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:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/http/bangumi.dart'; import 'package:PiliPlus/http/bangumi.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
class BangumiController extends CommonController { class BangumiController extends CommonListController<
List<BangumiListItemModel>?, BangumiListItemModel> {
BangumiController({required this.tabType}); BangumiController({required this.tabType});
final TabType tabType; final TabType tabType;
@@ -75,7 +76,8 @@ class BangumiController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => BangumiHttp.bangumiList( Future<LoadingState<List<BangumiListItemModel>?>> customGetData() =>
BangumiHttp.bangumiList(
page: currentPage, page: currentPage,
indexType: tabType == TabType.cinema ? 102 : null, // TODO: sort 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/init.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.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/dynamics/repost_dyn_panel.dart';
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart'; import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
import 'package:PiliPlus/pages/video/detail/introduction/pay_coins_page.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/parser.dart' as html_parser;
import 'package:html/dom.dart' as dom; import 'package:html/dom.dart' as dom;
class BangumiIntroController extends CommonController { class BangumiIntroController
extends CommonDataController<BangumiInfoModel, BangumiInfoModel> {
// 视频bvid // 视频bvid
String bvid = Get.parameters['bvid'] ?? ''; String bvid = Get.parameters['bvid'] ?? '';
var seasonId = Get.parameters['seasonId'] != null var seasonId = Get.parameters['seasonId'] != null
@@ -125,14 +126,15 @@ class BangumiIntroController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { bool customHandleResponse(
epId ??= response.response.episodes!.first.id; bool isRefresh, Success<BangumiInfoModel> response) {
epId ??= response.response.episodes?.firstOrNull?.id;
loadingState.value = response; loadingState.value = response;
return true; return true;
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<BangumiInfoModel>> customGetData() =>
SearchHttp.bangumiInfoNew(seasonId: seasonId, epId: epId); 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 { Future actionLikeVideo() async {
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value); var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
if (result['status']) { if (result['status']) {
SmartDialog.showToast(!hasLike.value ? result['data']['toast'] : '取消赞'); SmartDialog.showToast(!hasLike.value ? result['data']['toast'] : '取消赞');
hasLike.value = !hasLike.value; BangumiInfoModel bangumiDetail = (loadingState.value as Success).response;
dynamic bangumiDetail = (loadingState.value as Success).response;
bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] =
bangumiDetail.stat!['likes'] + (!hasLike.value ? 1 : -1); bangumiDetail.stat!['likes'] + (!hasLike.value ? 1 : -1);
loadingState.value = LoadingState.success(bangumiDetail); hasLike.value = !hasLike.value;
hasLike.refresh();
} else { } else {
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);
} }
@@ -194,14 +171,13 @@ class BangumiIntroController extends CommonController {
); );
if (res['status']) { if (res['status']) {
SmartDialog.showToast('投币成功'); SmartDialog.showToast('投币成功');
hasCoin.value = true; BangumiInfoModel bangumiDetail = (loadingState.value as Success).response;
dynamic bangumiDetail = (loadingState.value as Success).response;
bangumiDetail.stat!['coins'] = bangumiDetail.stat!['coins'] + coin; bangumiDetail.stat!['coins'] = bangumiDetail.stat!['coins'] + coin;
if (selectLike && hasLike.value.not) { if (selectLike && hasLike.value.not) {
hasLike.value = true; hasLike.value = true;
bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] + 1; bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] + 1;
} }
loadingState.value = LoadingState.success(bangumiDetail); hasCoin.value = true;
} else { } else {
SmartDialog.showToast(res['msg']); 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 // (取消)收藏 bangumi
@@ -582,8 +504,11 @@ class BangumiIntroController extends CommonController {
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']); Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat; PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
if ((loadingState.value as Success).response.episodes != null) { if ((loadingState.value as Success<BangumiInfoModel>).response.episodes !=
episodes = (loadingState.value as Success).response.episodes!; null) {
episodes = (loadingState.value as Success<BangumiInfoModel>)
.response
.episodes!;
} else { } else {
if (playRepeat == PlayRepeat.autoPlayRelated) { if (playRepeat == PlayRepeat.autoPlayRelated) {
return playRelated(); return playRelated();

View File

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

View File

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

View File

@@ -207,10 +207,10 @@ class _PgcIndexPageState extends State<PgcIndexPage>
], ],
); );
Widget _buildList(LoadingState loadingState) { Widget _buildList(LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => HttpError(errMsg: '加载中'), Loading() => HttpError(errMsg: '加载中'),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverGrid( ? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
@@ -221,13 +221,13 @@ class _PgcIndexPageState extends State<PgcIndexPage>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_ctr.onLoadMore(); _ctr.onLoadMore();
} }
return BangumiCardVPgcIndex( return BangumiCardVPgcIndex(
bangumiItem: loadingState.response[index]); bangumiItem: loadingState.response![index]);
}, },
childCount: loadingState.response.length, childCount: loadingState.response!.length,
), ),
) )
: HttpError(callback: _ctr.onReload), : 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/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/models/common/tab_type.dart';
import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart'; import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart';
import 'package:PiliPlus/pages/common/common_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) { return switch (loadingState) {
Loading() => const SliverToBoxAdapter(), Loading() => const SliverToBoxAdapter(),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverGrid( ? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距 // 行间距
@@ -247,13 +248,13 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
controller.onLoadMore(); controller.onLoadMore();
} }
return BangumiCardV( return BangumiCardV(
bangumiItem: loadingState.response[index]); bangumiItem: loadingState.response![index]);
}, },
childCount: loadingState.response.length, childCount: loadingState.response!.length,
), ),
) )
: HttpError( : 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/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/user/black.dart'; import 'package:PiliPlus/models/user/black.dart';
import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/pages/blacklist/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.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/storage.dart';
import 'package:PiliPlus/utils/utils.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) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.builder( ? ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
controller: _blackListController.scrollController, controller: _blackListController.scrollController,
itemCount: loadingState.response.length, itemCount: loadingState.response!.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_blackListController.onLoadMore(); _blackListController.onLoadMore();
} }
final item = loadingState.response![index];
return ListTile( return ListTile(
onTap: () { onTap: () {
Get.toNamed( Get.toNamed('/member?mid=${item.mid}');
'/member?mid=${loadingState.response[index].mid}');
}, },
leading: NetworkImgLayer( leading: NetworkImgLayer(
width: 45, width: 45,
height: 45, height: 45,
type: 'avatar', type: 'avatar',
src: loadingState.response[index].face, src: item.face,
), ),
title: Text( title: Text(
loadingState.response[index].uname!, item.uname!,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
subtitle: Text( subtitle: Text(
Utils.dateFormat(loadingState.response[index].mtime), Utils.dateFormat(item.mtime),
maxLines: 1, maxLines: 1,
style: style:
TextStyle(color: Theme.of(context).colorScheme.outline), TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -87,10 +83,11 @@ class _BlackListPageState extends State<BlackListPage> {
), ),
dense: true, dense: true,
trailing: TextButton( trailing: TextButton(
onPressed: () => _blackListController.removeBlack( onPressed: () => _blackListController.onRemove(
context, context,
loadingState.response[index].uname, index,
loadingState.response[index].mid, item.uname,
item.mid,
), ),
child: const Text('移除'), 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 { with ScrollOrRefreshMixin {
@override @override
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
@@ -35,13 +35,13 @@ abstract class CommonController extends GetxController
late int currentPage = 1; late int currentPage = 1;
bool isLoading = false; bool isLoading = false;
late bool isEnd = 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) {} void handleListResponse(List dataList) {}
bool customHandleResponse(Success response) { bool customHandleResponse(bool isRefresh, Success<R> response) {
return false; return false;
} }
@@ -49,27 +49,36 @@ abstract class CommonController extends GetxController
return false; return false;
} }
List<T>? getDataList(R response) {
return response as List<T>?;
}
void checkIsEnd(int length) {}
Future queryData([bool isRefresh = true]) async { Future queryData([bool isRefresh = true]) async {
if (isLoading || (isRefresh.not && isEnd)) return; if (isLoading || (isRefresh.not && isEnd)) return;
isLoading = true; isLoading = true;
LoadingState response = await customGetData(); LoadingState<R> response = await customGetData();
if (response is Success) { if (response is Success<R>) {
if (!customHandleResponse(response)) { if (!customHandleResponse(isRefresh, response)) {
List? dataList = response.response; List<T>? dataList = getDataList(response.response);
if (dataList.isNullOrEmpty) { if (dataList.isNullOrEmpty) {
isEnd = true; isEnd = true;
if (isRefresh) { if (isRefresh) {
loadingState.value = response; loadingState.value = LoadingState<List<T>?>.success(dataList);
} }
isLoading = false;
return; return;
} }
handleListResponse(dataList!); handleListResponse(dataList!);
if (isRefresh) { if (isRefresh) {
loadingState.value = LoadingState.success(dataList); checkIsEnd(dataList.length);
loadingState.value = LoadingState<List<T>?>.success(dataList);
} else if (loadingState.value is Success) { } else if (loadingState.value is Success) {
List currentList = (loadingState.value as Success).response ?? []; List<T> list = (loadingState.value as Success).response;
currentList.addAll(dataList); list.addAll(dataList);
loadingState.value = LoadingState.success(currentList); checkIsEnd(list.length);
loadingState.refresh();
} }
} }
currentPage++; 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/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: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 RxBool enableMultiSelect = false.obs;
late final RxInt checkedCount = 0.obs; late final RxInt checkedCount = 0.obs;
late final allSelected = false.obs;
onSelect(int index) { void onSelect(int index, [bool disableSelect = true]) {
List list = (loadingState.value as Success).response; List<T> list = (loadingState.value as Success).response;
list[index].checked = !(list[index]?.checked ?? false); list[index].checked = !(list[index].checked ?? false);
checkedCount.value = list.where((item) => item.checked == true).length; checkedCount.value = list.where((item) => item.checked == true).length;
loadingState.value = LoadingState.success(list); loadingState.refresh();
if (disableSelect) {
if (checkedCount.value == 0) { if (checkedCount.value == 0) {
enableMultiSelect.value = false; 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) { if (loadingState.value is Success) {
List list = (loadingState.value as Success).response; List<T>? list = (loadingState.value as Success).response;
if (list.isNotEmpty) { if (list?.isNotEmpty == true) {
loadingState.value = LoadingState.success( for (T item in list!) {
list.map((item) => item..checked = checked).toList()); item.checked = checked;
}
loadingState.refresh();
checkedCount.value = checked ? list.length : 0; checkedCount.value = checked ? list.length : 0;
} }
} }
if (checked.not) { if (disableSelect && !checked) {
enableMultiSelect.value = false; 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/http/reply.dart';
import 'package:PiliPlus/models/common/reply_type.dart'; import 'package:PiliPlus/models/common/reply_type.dart';
import 'package:PiliPlus/models/video/reply/data.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/pages/video/detail/reply_new/reply_page.dart';
import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/extension.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:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/models/common/reply_sort_type.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/feed_back.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get_navigation/src/dialog/dialog_route.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 = ''; String nextOffset = '';
RxInt count = (-1).obs; RxInt count = (-1).obs;
@@ -31,6 +30,7 @@ abstract class ReplyController extends CommonController {
late final bool isLogin = Accounts.main.isLogin; late final bool isLogin = Accounts.main.isLogin;
dynamic upMid;
CursorReply? cursor; CursorReply? cursor;
late Rx<Mode> mode = Mode.MAIN_LIST_HOT.obs; late Rx<Mode> mode = Mode.MAIN_LIST_HOT.obs;
late bool hasUpTop = false; 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 @override
Future onRefresh() { Future onRefresh() {
cursor = null; cursor = null;
@@ -66,29 +89,6 @@ abstract class ReplyController extends CommonController {
return super.onRefresh(); 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() { queryBySort() {
EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () { EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () {
@@ -168,16 +168,24 @@ abstract class ReplyController extends CommonController {
if (res != null) { if (res != null) {
savedReplies[key] = null; savedReplies[key] = null;
ReplyInfo replyInfo = Utils.replyCast(res); ReplyInfo replyInfo = Utils.replyCast(res);
MainListReply response = loadingState.value is Success if (loadingState.value is Success) {
? (loadingState.value as Success).response List<ReplyInfo>? list = (loadingState.value as Success).response;
: MainListReply(); if (list == null) {
if (oid != null) { loadingState.value = LoadingState.success([replyInfo]);
response.replies.insert(hasUpTop ? 1 : 0, replyInfo);
} else { } 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; count.value += 1;
loadingState.value = LoadingState.success(response);
// check reply
if (enableCommAntifraud && context.mounted) { if (enableCommAntifraud && context.mounted) {
checkReply( checkReply(
context: context, context: context,
@@ -203,28 +211,19 @@ abstract class ReplyController extends CommonController {
); );
} }
onMDelete(rpid, frpid) { void onRemove(int index, int? subIndex) {
MainListReply response = (loadingState.value as Success).response; List<ReplyInfo> list = (loadingState.value as Success).response;
if (frpid == null) { if (subIndex == null) {
response.replies.removeWhere((item) { list.removeAt(index);
return item.id.toInt() == rpid;
});
} else { } else {
response.replies.map((item) { list[index].replies.removeAt(subIndex);
if (item.id == frpid) {
return item..replies.removeWhere((reply) => reply.id.toInt() == rpid);
} else {
return item;
}
}).toList();
} }
count.value -= 1; count.value -= 1;
loadingState.value = LoadingState.success(response); loadingState.refresh();
} }
void onCheckReply(context, item) { void onCheckReply(context, item) {
try { try {
if (item is ReplyInfo) {
checkReply( checkReply(
context: context, context: context,
oid: item.oid.toInt(), oid: item.oid.toInt(),
@@ -242,24 +241,6 @@ abstract class ReplyController extends CommonController {
// //
isManual: true, 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) { } catch (e) {
SmartDialog.showToast(e.toString()); 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, isUpTop: isUpTop,
); );
if (res['status']) { if (res['status']) {
final data = (loadingState.value as Success).response; List<ReplyInfo> list = (loadingState.value as Success).response;
if (data is MainListReply) { list[index].replyControl.isUpTop = !isUpTop;
data.replies[index].replyControl.isUpTop = !isUpTop;
if (!isUpTop && index != 0) { if (!isUpTop && index != 0) {
data.replies[0].replyControl.isUpTop = false; list[0].replyControl.isUpTop = false;
final item = data.replies.removeAt(index); final item = list.removeAt(index);
data.replies.insert(0, item); list.insert(0, item);
}
loadingState.value = LoadingState.success(data);
} }
loadingState.refresh();
SmartDialog.showToast('${isUpTop ? '取消' : ''}置顶成功'); SmartDialog.showToast('${isUpTop ? '取消' : ''}置顶成功');
} else { } else {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);

View File

@@ -10,7 +10,7 @@ import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/http/reply.dart';
import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:fixnum/fixnum.dart' as $fixnum;
class DynamicDetailController extends ReplyController { class DynamicDetailController extends ReplyController<MainListReply> {
DynamicDetailController(this.oid, this.type); DynamicDetailController(this.oid, this.type);
int oid; int oid;
int type; int type;
@@ -47,7 +47,13 @@ class DynamicDetailController extends ReplyController {
} }
@override @override
Future<LoadingState> customGetData() => ReplyHttp.replyListGrpc( List<ReplyInfo>? getDataList(MainListReply response) {
return response.replies;
}
@override
Future<LoadingState<MainListReply>> customGetData() =>
ReplyHttp.replyListGrpc(
type: type, type: type,
oid: oid, oid: oid,
cursor: CursorReq( 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) { return switch (loadingState) {
Loading() => SliverList( Loading() => SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
@@ -785,11 +785,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
childCount: 8, childCount: 8,
), ),
), ),
Success() => (loadingState.response.replies as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverList( ? SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.replies.length) { if (index == loadingState.response!.length) {
_dynamicDetailController.onLoadMore(); _dynamicDetailController.onLoadMore();
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
@@ -799,7 +799,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
child: Text( child: Text(
_dynamicDetailController.isEnd.not _dynamicDetailController.isEnd.not
? '加载中...' ? '加载中...'
: loadingState.response.replies.isEmpty : loadingState.response!.isEmpty
? '还没有评论' ? '还没有评论'
: '没有更多了', : '没有更多了',
style: TextStyle( style: TextStyle(
@@ -810,19 +810,20 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
); );
} else { } else {
return ReplyItemGrpc( return ReplyItemGrpc(
replyItem: loadingState.response.replies[index], replyItem: loadingState.response![index],
replyLevel: '1', replyLevel: '1',
replyReply: (replyItem, id) => replyReply: (replyItem, id) =>
replyReply(context, replyItem, id), replyReply(context, replyItem, id),
onReply: () { onReply: () {
_dynamicDetailController.onReply( _dynamicDetailController.onReply(
context, context,
replyItem: loadingState.response.replies[index], replyItem: loadingState.response![index],
index: index, index: index,
); );
}, },
onDelete: _dynamicDetailController.onMDelete, onDelete: (subIndex) =>
upMid: loadingState.response.subjectControl.upMid, _dynamicDetailController.onRemove(index, subIndex),
upMid: _dynamicDetailController.upMid,
callback: _getImageCallback, callback: _getImageCallback,
onCheckReply: (item) => onCheckReply: (item) =>
_dynamicDetailController.onCheckReply(context, 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( : HttpError(

View File

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

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/common/common_page.dart';
import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/utils/storage.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) { return switch (loadingState) {
Loading() => skeleton(), Loading() => skeleton(),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80, bottom: MediaQuery.paddingOf(context).bottom + 80,
@@ -153,23 +154,23 @@ class _DynamicsTabPageState
// mainAxisSpacing: StyleString.cardSpace / 2, // mainAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) { lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
controller.onLoadMore(); controller.onLoadMore();
} }
return index == loadingState.response.length return index == loadingState.response!.length
? LastChildLayoutType.foot ? LastChildLayoutType.foot
: LastChildLayoutType.none; : LastChildLayoutType.none;
}, },
children: [ children: [
if (dynamicsController.tabController.index == 4 && if (dynamicsController.tabController.index == 4 &&
dynamicsController.mid.value != -1) ...[ dynamicsController.mid.value != -1) ...[
for (var i in loadingState.response) for (var i in loadingState.response!)
DynamicPanel( DynamicPanel(
item: i, item: i,
onRemove: controller.onRemove, onRemove: controller.onRemove,
), ),
] else ...[ ] else ...[
for (var i in loadingState.response) for (var i in loadingState.response!)
if (!dynamicsController.tempBannedList if (!dynamicsController.tempBannedList
.contains(i.modules?.moduleAuthor?.mid)) .contains(i.modules?.moduleAuthor?.mid))
DynamicPanel( DynamicPanel(
@@ -187,23 +188,24 @@ class _DynamicsTabPageState
sliver: SliverList( sliver: SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index ==
loadingState.response!.length - 1) {
controller.onLoadMore(); controller.onLoadMore();
} }
final item = loadingState.response![index];
if ((dynamicsController.tabController.index == if ((dynamicsController.tabController.index ==
4 && 4 &&
dynamicsController.mid.value != -1) || dynamicsController.mid.value != -1) ||
!dynamicsController.tempBannedList.contains( !dynamicsController.tempBannedList.contains(
loadingState.response[index].modules item.modules?.moduleAuthor?.mid)) {
?.moduleAuthor?.mid)) {
return DynamicPanel( return DynamicPanel(
item: loadingState.response[index], item: item,
onRemove: controller.onRemove, onRemove: controller.onRemove,
); );
} }
return const SizedBox.shrink(); 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/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:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../http/reply.dart'; import '../../http/reply.dart';
class EmotePanelController extends CommonController class EmotePanelController
extends CommonListController<List<Packages>?, Packages>
with GetTickerProviderStateMixin { with GetTickerProviderStateMixin {
TabController? tabController; TabController? tabController;
@@ -16,15 +18,17 @@ class EmotePanelController extends CommonController
} }
@override @override
bool customHandleResponse(Success response) { bool customHandleResponse(bool isRefresh, Success<List<Packages>?> response) {
if (response.response?.isNotEmpty == true) {
tabController = tabController =
TabController(length: response.response.length, vsync: this); TabController(length: response.response!.length, vsync: this);
}
loadingState.value = response; loadingState.value = response;
return true; return true;
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<List<Packages>?>> customGetData() =>
ReplyHttp.getEmoteList(business: 'reply'); ReplyHttp.getEmoteList(business: 'reply');
@override @override

View File

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

View File

@@ -1,12 +1,12 @@
import 'package:PiliPlus/http/fan.dart'; import 'package:PiliPlus/http/fan.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/fans/result.dart'; import 'package:PiliPlus/models/fans/result.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:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
class FansController extends CommonController { class FansController
extends CommonListController<FansDataModel, FansItemModel> {
int ps = 20; int ps = 20;
int total = 0; int total = 0;
late int? mid; late int? mid;
@@ -28,22 +28,12 @@ class FansController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { List<FansItemModel>? getDataList(FansDataModel response) {
if ((currentPage == 1 && response.response.total < ps) || return response.list;
(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;
} }
@override @override
Future<LoadingState> customGetData() => FanHttp.fans( Future<LoadingState<FansDataModel>> customGetData() => FanHttp.fans(
vmid: mid, vmid: mid,
pn: currentPage, pn: currentPage,
ps: ps, ps: ps,

View File

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

View File

@@ -1,9 +1,9 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.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'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class FavArticleController extends CommonController { class FavArticleController extends CommonListController {
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@@ -19,7 +19,7 @@ class FavArticleController extends CommonController {
if (res['status']) { if (res['status']) {
List list = (loadingState.value as Success).response; List list = (loadingState.value as Success).response;
list.removeAt(index); list.removeAt(index);
loadingState.value = LoadingState.success(list); loadingState.refresh();
SmartDialog.showToast('已取消收藏'); SmartDialog.showToast('已取消收藏');
} else { } else {
SmartDialog.showToast(res['msg']); 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) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
@@ -55,7 +55,7 @@ class _FavArticlePageState extends State<FavArticlePage>
childCount: 10, childCount: 10,
), ),
), ),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: StyleString.safeSpace - 5, top: StyleString.safeSpace - 5,
@@ -69,11 +69,11 @@ class _FavArticlePageState extends State<FavArticlePage>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_favArticleController.onLoadMore(); _favArticleController.onLoadMore();
} }
return FavArticleItem( return FavArticleItem(
item: loadingState.response[index], item: loadingState.response![index],
onDelete: () { onDelete: () {
showConfirmDialog( showConfirmDialog(
context: context, context: context,
@@ -81,13 +81,13 @@ class _FavArticlePageState extends State<FavArticlePage>
onConfirm: () { onConfirm: () {
_favArticleController.onRemove( _favArticleController.onRemove(
index, 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/icon_button.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/controller.dart';
import 'package:PiliPlus/pages/fav/note/widget/item.dart'; import 'package:PiliPlus/pages/fav/note/widget/item.dart';
import 'package:PiliPlus/utils/grid.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) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
@@ -147,7 +148,7 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
childCount: 10, childCount: 10,
), ),
), ),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80), bottom: MediaQuery.paddingOf(context).bottom + 80),
@@ -159,18 +160,18 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_favNoteController.onLoadMore(); _favNoteController.onLoadMore();
} }
return FavNoteItem( return FavNoteItem(
item: loadingState.response[index], item: loadingState.response![index],
ctr: _favNoteController, ctr: _favNoteController,
onSelect: () { onSelect: () {
_favNoteController.onSelect(index); _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/loading_state.dart';
import 'package:PiliPlus/http/video.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:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.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); FavNoteController(this.isPublish);
final bool isPublish; final bool isPublish;
late final allSelected = false.obs;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@@ -18,48 +17,36 @@ class FavNoteController extends MultiSelectController {
} }
@override @override
onSelect(int index) { onSelect(int index, [bool disableSelect = true]) {
List list = (loadingState.value as Success).response; super.onSelect(index, false);
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;
}
} }
@override @override
void handleSelect([bool checked = false]) { void handleSelect([bool checked = false, bool disableSelect = true]) {
allSelected.value = checked; allSelected.value = checked;
if (loadingState.value is Success) { super.handleSelect(checked, false);
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;
}
}
} }
@override @override
Future<LoadingState> customGetData() { Future<LoadingState<List<FavArticleModel>?>> customGetData() {
return isPublish return isPublish
? VideoHttp.userNoteList(page: currentPage) ? VideoHttp.userNoteList(page: currentPage)
: VideoHttp.noteList(page: currentPage); : VideoHttp.noteList(page: currentPage);
} }
void onRemove() async { void onRemove() async {
List dataList = (loadingState.value as Success).response as List; List<FavArticleModel> dataList = (loadingState.value as Success).response;
Set removeList = dataList.where((item) => item['checked'] == true).toSet(); Set<FavArticleModel> removeList =
dataList.where((item) => item.checked == true).toSet();
final res = await VideoHttp.delNote( final res = await VideoHttp.delNote(
isPublish: isPublish, isPublish: isPublish,
noteIds: removeList noteIds: removeList
.map((item) => isPublish ? item['cvid'] : item['note_id']) .map((item) => isPublish ? item.cvid : item.noteId)
.toList(), .toList(),
); );
if (res['status']) { if (res['status']) {
List remainList = dataList.toSet().difference(removeList).toList(); List<FavArticleModel> remainList =
dataList.toSet().difference(removeList).toList();
loadingState.value = LoadingState.success(remainList); loadingState.value = LoadingState.success(remainList);
enableMultiSelect.value = false; enableMultiSelect.value = false;
SmartDialog.showToast('删除成功'); SmartDialog.showToast('删除成功');

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.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/pages/fav/note/controller.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -12,7 +13,7 @@ class FavNoteItem extends StatelessWidget {
required this.onSelect, required this.onSelect,
}); });
final dynamic item; final FavArticleModel item;
final FavNoteController ctr; final FavNoteController ctr;
final VoidCallback onSelect; final VoidCallback onSelect;
@@ -26,10 +27,12 @@ class FavNoteItem extends StatelessWidget {
onSelect(); onSelect();
return; return;
} }
if (item.webUrl?.isNotEmpty == true) {
Utils.handleWebview( Utils.handleWebview(
item['web_url'], item.webUrl!,
inApp: true, inApp: true,
); );
}
}, },
onLongPress: () { onLongPress: () {
if (!ctr.enableMultiSelect.value) { if (!ctr.enableMultiSelect.value) {
@@ -53,7 +56,7 @@ class FavNoteItem extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
item['title'], item.title ?? '',
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@@ -64,14 +67,14 @@ class FavNoteItem extends StatelessWidget {
), ),
), ),
Text( Text(
item['summary'], item.summary ?? '',
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
Text( Text(
item['message'], item.message ?? '',
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.outline, 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), const SizedBox(width: 10),
AspectRatio( AspectRatio(
aspectRatio: StyleString.aspectRatio, aspectRatio: StyleString.aspectRatio,
@@ -91,7 +94,7 @@ class FavNoteItem extends StatelessWidget {
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
src: item['arc']?['pic'], src: item.pic,
width: boxConstraints.maxWidth, width: boxConstraints.maxWidth,
height: boxConstraints.maxHeight, height: boxConstraints.maxHeight,
), ),
@@ -100,7 +103,7 @@ class FavNoteItem extends StatelessWidget {
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) => builder: (context, constraints) =>
AnimatedOpacity( AnimatedOpacity(
opacity: item['checked'] == true ? 1 : 0, opacity: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
@@ -115,7 +118,7 @@ class FavNoteItem extends StatelessWidget {
width: 34, width: 34,
height: 34, height: 34,
child: AnimatedScale( child: AnimatedScale(
scale: item['checked'] == true ? 1 : 0, scale: item.checked == true ? 1 : 0,
duration: duration:
const Duration(milliseconds: 250), const Duration(milliseconds: 250),
curve: Curves.easeInOut, 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/icon_button.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/controller.dart';
import 'package:PiliPlus/pages/fav/pgc/widget/item.dart'; import 'package:PiliPlus/pages/fav/pgc/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
@@ -126,7 +127,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
if (_favPgcController.checkedCount.value != if (_favPgcController.checkedCount.value !=
0) { 0) {
_favPgcController _favPgcController
.onUpdate(item['followStatus']); .onUpdateList(item['followStatus']);
} }
}, },
child: Padding( child: Padding(
@@ -156,7 +157,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
); );
} }
Widget _buildBody(LoadingState loadingState) { Widget _buildBody(LoadingState<List<BangumiListItemModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
@@ -171,7 +172,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
childCount: 10, childCount: 10,
), ),
), ),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80), bottom: MediaQuery.paddingOf(context).bottom + 80),
@@ -183,11 +184,12 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_favPgcController.onLoadMore(); _favPgcController.onLoadMore();
} }
final item = loadingState.response![index];
return FavPgcItem( return FavPgcItem(
item: loadingState.response[index], item: item,
ctr: _favPgcController, ctr: _favPgcController,
onSelect: () { onSelect: () {
_favPgcController.onSelect(index); _favPgcController.onSelect(index);
@@ -201,13 +203,13 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
if (followStatus == -1) { if (followStatus == -1) {
_favPgcController.bangumiDel( _favPgcController.bangumiDel(
index, index,
loadingState.response[index].seasonId, item.seasonId,
); );
} else { } else {
_favPgcController.bangumiUpdate( _favPgcController.onUpdate(
index, index,
followStatus, 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:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class FavPgcController extends MultiSelectController { class FavPgcController
extends MultiSelectController<BangumiListDataModel, BangumiListItemModel> {
final int type; final int type;
final int followStatus; final int followStatus;
FavPgcController(this.type, this.followStatus); FavPgcController(this.type, this.followStatus);
late final allSelected = false.obs;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@@ -23,34 +22,24 @@ class FavPgcController extends MultiSelectController {
} }
@override @override
onSelect(int index) { onSelect(int index, [bool disableSelect = true]) {
List<BangumiListItemModel> list = (loadingState.value as Success).response; super.onSelect(index, false);
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;
}
} }
@override @override
void handleSelect([bool checked = false]) { void handleSelect([bool checked = false, bool disableSelect = true]) {
allSelected.value = checked; allSelected.value = checked;
if (loadingState.value is Success) { super.handleSelect(checked, false);
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;
}
}
} }
@override @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, mid: Accounts.main.mid,
type: type, type: type,
followStatus: followStatus, followStatus: followStatus,
@@ -71,12 +60,12 @@ class FavPgcController extends MultiSelectController {
List<BangumiListItemModel> list = List<BangumiListItemModel> list =
(loadingState.value as Success).response; (loadingState.value as Success).response;
list.removeAt(index); list.removeAt(index);
loadingState.value = LoadingState.success(list); loadingState.refresh();
} }
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);
} }
Future onUpdate(followStatus) async { Future onUpdateList(followStatus) async {
List<BangumiListItemModel> dataList = List<BangumiListItemModel> dataList =
(loadingState.value as Success).response as List<BangumiListItemModel>; (loadingState.value as Success).response as List<BangumiListItemModel>;
Set<BangumiListItemModel> updateList = Set<BangumiListItemModel> updateList =
@@ -96,7 +85,7 @@ class FavPgcController extends MultiSelectController {
List<BangumiListItemModel> list = List<BangumiListItemModel> list =
(ctr.loadingState.value as Success).response; (ctr.loadingState.value as Success).response;
list.insertAll(0, updateList.map((item) => item..checked = null)); list.insertAll(0, updateList.map((item) => item..checked = null));
ctr.loadingState.value = LoadingState.success(list); ctr.loadingState.refresh();
ctr.allSelected.value = false; ctr.allSelected.value = false;
} }
} catch (e) { } catch (e) {
@@ -106,7 +95,7 @@ class FavPgcController extends MultiSelectController {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
} }
Future bangumiUpdate(index, followStatus, seasonId) async { Future onUpdate(index, followStatus, seasonId) async {
var result = await VideoHttp.bangumiUpdate( var result = await VideoHttp.bangumiUpdate(
seasonId: [seasonId], seasonId: [seasonId],
status: followStatus, status: followStatus,
@@ -115,14 +104,14 @@ class FavPgcController extends MultiSelectController {
List<BangumiListItemModel> list = List<BangumiListItemModel> list =
(loadingState.value as Success).response; (loadingState.value as Success).response;
final item = list.removeAt(index); final item = list.removeAt(index);
loadingState.value = LoadingState.success(list); loadingState.refresh();
try { try {
final ctr = Get.find<FavPgcController>(tag: '$type$followStatus'); final ctr = Get.find<FavPgcController>(tag: '$type$followStatus');
if (ctr.loadingState.value is Success) { if (ctr.loadingState.value is Success) {
List<BangumiListItemModel> list = List<BangumiListItemModel> list =
(ctr.loadingState.value as Success).response; (ctr.loadingState.value as Success).response;
list.insert(0, item); list.insert(0, item);
ctr.loadingState.value = LoadingState.success(list); ctr.loadingState.refresh();
ctr.allSelected.value = false; ctr.allSelected.value = false;
} }
} catch (e) { } catch (e) {

View File

@@ -1,11 +1,11 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/user/fav_folder.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/http/user.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
class FavController extends CommonController { class FavController
extends CommonListController<FavFolderData, FavFolderItemData> {
late final dynamic mid = Accounts.main.mid; late final dynamic mid = Accounts.main.mid;
@override @override
@@ -24,22 +24,20 @@ class FavController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { List<FavFolderItemData>? getDataList(FavFolderData response) {
if (response.response.hasMore == false || return response.list;
(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;
} }
@override @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, pn: currentPage,
ps: 10, ps: 10,
mid: mid, mid: mid,

View File

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

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/loading_state.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/article/view.dart';
import 'package:PiliPlus/pages/fav/note/view.dart'; import 'package:PiliPlus/pages/fav/note/view.dart';
import 'package:PiliPlus/pages/fav/pgc/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( Get.toNamed('/createFav')?.then(
(data) { (data) {
if (data != null) { if (data != null) {
List list = _favController.loadingState.value is Success List<FavFolderItemData> list =
_favController.loadingState.value is Success
? (_favController.loadingState.value as Success) ? (_favController.loadingState.value as Success)
.response .response
: []; : <FavFolderItemData>[];
list.insert(list.isNotEmpty ? 1 : 0, data); list.insert(list.isNotEmpty ? 1 : 0, data);
_favController.loadingState.value = _favController.loadingState.refresh();
LoadingState.success(list);
} }
}, },
); );
@@ -81,6 +82,7 @@ class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
'title': item.title, 'title': item.title,
'count': item.mediaCount, 'count': item.mediaCount,
'searchType': SearchType.fav, 'searchType': SearchType.fav,
'isOwner': true,
}); });
} catch (_) {} } 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_detail.dart';
import 'package:PiliPlus/models/user/fav_folder.dart'; import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.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/storage.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.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:get/get.dart';
import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/http/video.dart';
class FavDetailController extends MultiSelectController { class FavDetailController
extends MultiSelectController<FavDetailData, FavDetailItemData> {
Rx<FavFolderItemData> item = FavFolderItemData().obs; Rx<FavFolderItemData> item = FavFolderItemData().obs;
int? mediaId; int? mediaId;
late String heroTag; late String heroTag;
@@ -35,40 +35,39 @@ class FavDetailController extends MultiSelectController {
} }
@override @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; FavDetailData data = response.response;
if (currentPage == 1) { if (isRefresh) {
item.value = data.info ?? FavFolderItemData(); item.value = data.info ?? FavFolderItemData();
isOwner.value = data.info?.mid == mid; isOwner.value = data.info?.mid == mid;
} }
if (data.list.isNullOrEmpty) { return false;
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;
} }
onCancelFav(int id, int type) async { onCancelFav(int index, int id, int type) async {
var result = await VideoHttp.delFav( var result = await VideoHttp.delFav(
ids: ['$id:$type'], ids: ['$id:$type'],
delIds: mediaId.toString(), delIds: mediaId.toString(),
); );
if (result['status']) { if (result['status']) {
List dataList = (loadingState.value as Success).response; List<FavDetailItemData> dataList =
dataList.removeWhere((item) => item.id == id); (loadingState.value as Success).response;
item.value.mediaCount = item.value.mediaCount! - 1; item.value.mediaCount = item.value.mediaCount! - 1;
item.refresh(); item.refresh();
loadingState.value = LoadingState.success(dataList); dataList.removeAt(index);
loadingState.refresh();
SmartDialog.showToast('取消收藏'); SmartDialog.showToast('取消收藏');
} else { } else {
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);
@@ -76,7 +75,8 @@ class FavDetailController extends MultiSelectController {
} }
@override @override
Future<LoadingState> customGetData() => UserHttp.userFavFolderDetail( Future<LoadingState<FavDetailData>> customGetData() =>
UserHttp.userFavFolderDetail(
pn: currentPage, pn: currentPage,
ps: 20, ps: 20,
mediaId: mediaId!, mediaId: mediaId!,
@@ -110,8 +110,9 @@ class FavDetailController extends MultiSelectController {
delIds: mediaId.toString(), delIds: mediaId.toString(),
); );
if (result['status']) { if (result['status']) {
List dataList = (loadingState.value as Success).response; List<FavDetailItemData> dataList =
List remainList = (loadingState.value as Success).response;
List<FavDetailItemData> remainList =
dataList.toSet().difference(list.toSet()).toList(); dataList.toSet().difference(list.toSet()).toList();
item.value.mediaCount = item.value.mediaCount! - list.length; item.value.mediaCount = item.value.mediaCount! - list.length;
item.refresh(); item.refresh();
@@ -137,8 +138,7 @@ class FavDetailController extends MultiSelectController {
void toViewPlayAll() { void toViewPlayAll() {
if (loadingState.value is Success) { if (loadingState.value is Success) {
List<FavDetailItemData> list = List<FavDetailItemData>.from( List<FavDetailItemData> list = (loadingState.value as Success).response;
(loadingState.value as Success).response);
for (FavDetailItemData element in list) { for (FavDetailItemData element in list) {
if (element.cid == null) { if (element.cid == null) {
continue; continue;

View File

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

View File

@@ -135,7 +135,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
visualDensity: visualDensity:
VisualDensity(horizontal: -2, vertical: -2), VisualDensity(horizontal: -2, vertical: -2),
), ),
onPressed: () => Utils.onCopyOrMove( onPressed: () =>
Utils.onCopyOrMove<FavDetailItemData>(
context: context, context: context,
isCopy: true, isCopy: true,
ctr: _favDetailController, ctr: _favDetailController,
@@ -155,7 +156,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
visualDensity: visualDensity:
VisualDensity(horizontal: -2, vertical: -2), VisualDensity(horizontal: -2, vertical: -2),
), ),
onPressed: () => Utils.onCopyOrMove( onPressed: () =>
Utils.onCopyOrMove<FavDetailItemData>(
context: context, context: context,
isCopy: false, isCopy: false,
ctr: _favDetailController, ctr: _favDetailController,
@@ -188,15 +190,18 @@ class _FavDetailPageState extends State<FavDetailPage> {
: [ : [
IconButton( IconButton(
tooltip: '搜索', tooltip: '搜索',
onPressed: () => onPressed: () => Get.toNamed(
Get.toNamed('/favSearch', arguments: { '/favSearch',
arguments: {
'type': 0, 'type': 0,
'mediaId': int.parse(mediaId), 'mediaId': int.parse(mediaId),
'title': _favDetailController.item.value.title, 'title': _favDetailController.item.value.title,
'count': 'count':
_favDetailController.item.value.mediaCount, _favDetailController.item.value.mediaCount,
'searchType': SearchType.fav, 'searchType': SearchType.fav,
}), 'isOwner': _favDetailController.isOwner.value,
},
),
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
), ),
// IconButton( // IconButton(
@@ -416,7 +421,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
); );
} }
Widget _buildBody(LoadingState loadingState) { Widget _buildBody(LoadingState<List<FavDetailItemData>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -431,7 +436,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
childCount: 10, childCount: 10,
), ),
), ),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 85, bottom: MediaQuery.of(context).padding.bottom + 85,
@@ -444,7 +449,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length) { if (index == loadingState.response!.length) {
_favDetailController.onLoadMore(); _favDetailController.onLoadMore();
return Container( return Container(
height: 60, height: 60,
@@ -458,25 +463,26 @@ class _FavDetailPageState extends State<FavDetailPage> {
), ),
); );
} }
FavDetailItemData element = loadingState.response[index]; FavDetailItemData item = loadingState.response![index];
return Stack( return Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: FavVideoCardH( child: FavVideoCardH(
videoItem: element, videoItem: item,
callFn: () => _favDetailController.onCancelFav( callFn: () => _favDetailController.onCancelFav(
element.id!, index,
element.type!, item.id!,
item.type!,
), ),
onViewFav: () { onViewFav: () {
Utils.toViewPage( Utils.toViewPage(
'bvid=${element.bvid}&cid=${element.cid}', 'bvid=${item.bvid}&cid=${item.cid}',
arguments: { arguments: {
'videoItem': element, 'videoItem': item,
'heroTag': Utils.makeHeroTag(element.bvid), 'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': 'fav', 'sourceType': 'fav',
'mediaId': _favDetailController.item.value.id, 'mediaId': _favDetailController.item.value.id,
'oid': element.id, 'oid': item.id,
'favTitle': 'favTitle':
_favDetailController.item.value.title, _favDetailController.item.value.title,
'count': _favDetailController 'count': _favDetailController
@@ -513,10 +519,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) => builder: (context, constraints) =>
AnimatedOpacity( AnimatedOpacity(
opacity: opacity: item.checked == true ? 1 : 0,
loadingState.response[index].checked == true
? 1
: 0,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
@@ -531,11 +534,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
width: 34, width: 34,
height: 34, height: 34,
child: AnimatedScale( child: AnimatedScale(
scale: loadingState scale: item.checked == true ? 1 : 0,
.response[index].checked ==
true
? 1
: 0,
duration: duration:
const Duration(milliseconds: 250), const Duration(milliseconds: 250),
curve: Curves.easeInOut, 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/loading_state.dart';
import 'package:PiliPlus/http/member.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:PiliPlus/pages/fav_search/view.dart' show SearchType;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -9,7 +9,7 @@ import 'package:PiliPlus/http/user.dart';
import '../../http/video.dart'; import '../../http/video.dart';
class FavSearchController extends CommonController { class FavSearchController extends CommonListController {
final controller = TextEditingController(); final controller = TextEditingController();
final searchFocusNode = FocusNode(); final searchFocusNode = FocusNode();
@@ -17,6 +17,7 @@ class FavSearchController extends CommonController {
int? mediaId; int? mediaId;
int? mid; int? mid;
late SearchType searchType; late SearchType searchType;
final bool? isOwner = Get.arguments['isOwner'];
@override @override
void onInit() { void onInit() {
@@ -45,23 +46,19 @@ class FavSearchController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { List? getDataList(response) {
late List currentList = loadingState.value is Success return response.list;
? (loadingState.value as Success).response }
: [];
List? dataList = currentPage == 1 @override
? response.response.list bool customHandleResponse(bool isRefresh, Success response) {
: response.response.list != null
? currentList + response.response.list
: currentList;
isEnd = searchType == SearchType.fav isEnd = searchType == SearchType.fav
? response.response.hasMore == false ? response.response.hasMore == false
: response.response.list == null || response.response.list.isEmpty; : response.response.list == null || response.response.list.isEmpty;
loadingState.value = LoadingState.success(dataList); return false;
return true;
} }
onCancelFav(int id, int type) async { onCancelFav(int index, int id, int type) async {
var result = await VideoHttp.favVideo( var result = await VideoHttp.favVideo(
aid: id, aid: id,
addIds: '', addIds: '',
@@ -70,8 +67,8 @@ class FavSearchController extends CommonController {
); );
if (result['status']) { if (result['status']) {
List dataList = (loadingState.value as Success).response; List dataList = (loadingState.value as Success).response;
dataList.removeWhere((item) => item.id == id); dataList.removeAt(index);
loadingState.value = LoadingState.success(dataList); loadingState.refresh();
SmartDialog.showToast('取消收藏'); SmartDialog.showToast('取消收藏');
} }
} }
@@ -104,7 +101,7 @@ class FavSearchController extends CommonController {
super.onClose(); super.onClose();
} }
Future delHistory(kid, business) async { Future onDelHistory(index, kid, business) async {
String resKid = 'archive_$kid'; String resKid = 'archive_$kid';
if (business == 'live') { if (business == 'live') {
resKid = 'live_$kid'; resKid = 'live_$kid';
@@ -115,8 +112,8 @@ class FavSearchController extends CommonController {
var res = await UserHttp.delHistory([resKid]); var res = await UserHttp.delHistory([resKid]);
if (res['status']) { if (res['status']) {
List historyList = (loadingState.value as Success).response; List historyList = (loadingState.value as Success).response;
historyList.removeWhere((e) => e.kid == kid); historyList.removeAt(index);
loadingState.value = LoadingState.success(historyList); loadingState.refresh();
SmartDialog.showToast(res['msg']); 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) { return switch (loadingState) {
Loading() => errorWidget(), Loading() => errorWidget(),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? switch (_favSearchCtr.searchType) { ? switch (_favSearchCtr.searchType) {
SearchType.fav => CustomScrollView( SearchType.fav || SearchType.history => CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: _favSearchCtr.scrollController, controller: _favSearchCtr.scrollController,
slivers: [ slivers: [
@@ -82,30 +82,34 @@ class _FavSearchPageState extends State<FavSearchPage> {
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore(); _favSearchCtr.onLoadMore();
} }
final element = loadingState.response[index]; final item = loadingState.response![index];
return FavVideoCardH( return _favSearchCtr.searchType == SearchType.fav
videoItem: element, ? FavVideoCardH(
videoItem: item,
isOwner: _favSearchCtr.isOwner ?? false,
searchType: _favSearchCtr.type, searchType: _favSearchCtr.type,
callFn: _favSearchCtr.type != 1 callFn: _favSearchCtr.type != 1
? () { ? () {
_favSearchCtr.onCancelFav( _favSearchCtr.onCancelFav(
element.id!, index,
element.type, item.id!,
item.type,
); );
} }
: null, : null,
onViewFav: () { onViewFav: () {
Utils.toViewPage( Utils.toViewPage(
'bvid=${element.bvid}&cid=${element.cid}', 'bvid=${item.bvid}&cid=${item.cid}',
arguments: { arguments: {
'videoItem': element, 'videoItem': item,
'heroTag': Utils.makeHeroTag(element.bvid), 'heroTag':
Utils.makeHeroTag(item.bvid),
'sourceType': 'fav', 'sourceType': 'fav',
'mediaId': Get.arguments['mediaId'], 'mediaId': Get.arguments['mediaId'],
'oid': element.id, 'oid': item.id,
'favTitle': Get.arguments['title'], 'favTitle': Get.arguments['title'],
'count': Get.arguments['count'], 'count': Get.arguments['count'],
'desc': true, '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, bottom: MediaQuery.of(context).padding.bottom + 80,
), ),
controller: _favSearchCtr.scrollController, controller: _favSearchCtr.scrollController,
itemCount: loadingState.response.length, itemCount: loadingState.response!.length,
itemBuilder: ((context, index) { itemBuilder: ((context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_favSearchCtr.onLoadMore(); _favSearchCtr.onLoadMore();
} }
return FollowItem( 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( : errorWidget(
callback: _favSearchCtr.onReload, callback: _favSearchCtr.onReload,

View File

@@ -37,12 +37,16 @@ class _FollowPageState extends State<FollowPage> {
), ),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => Get.toNamed('/favSearch', arguments: { onPressed: () => Get.toNamed(
'/favSearch',
arguments: {
'mid': int.parse(mid), 'mid': int.parse(mid),
'searchType': SearchType.follow, 'searchType': SearchType.follow,
}), },
),
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
tooltip: '搜索'), tooltip: '搜索',
),
PopupMenuButton( PopupMenuButton(
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[ 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/models/user/history.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
class HistoryController extends MultiSelectController class HistoryController extends MultiSelectController<HistoryData, HisListItem>
with GetTickerProviderStateMixin { with GetTickerProviderStateMixin {
HistoryController(this.type); HistoryController(this.type);
@@ -37,25 +37,27 @@ class HistoryController extends MultiSelectController
} }
@override @override
onSelect(int index) { onSelect(int index, [bool disableSelect = true]) {
List list = (loadingState.value as Success).response; List<HisListItem> list = (loadingState.value as Success).response;
list[index].checked = !(list[index]?.checked ?? false); list[index].checked = !(list[index].checked ?? false);
baseCtr.checkedCount.value = baseCtr.checkedCount.value =
list.where((item) => item.checked == true).length; list.where((item) => item.checked == true).length;
loadingState.value = LoadingState.success(list); loadingState.refresh();
if (baseCtr.checkedCount.value == 0) { if (baseCtr.checkedCount.value == 0) {
baseCtr.enableMultiSelect.value = false; baseCtr.enableMultiSelect.value = false;
} }
} }
@override @override
void handleSelect([bool checked = false]) { void handleSelect([bool checked = false, bool disableSelect = true]) {
if (loadingState.value is Success) { if (loadingState.value is Success) {
List list = (loadingState.value as Success).response; List<HisListItem>? list = (loadingState.value as Success).response;
if (list.isNotEmpty) { if (list?.isNotEmpty == true) {
loadingState.value = LoadingState.success( for (HisListItem item in list!) {
list.map((item) => item..checked = checked).toList()); item.checked = checked;
}
baseCtr.checkedCount.value = checked ? list.length : 0; baseCtr.checkedCount.value = checked ? list.length : 0;
loadingState.refresh();
} }
} }
if (checked.not) { if (checked.not) {
@@ -64,26 +66,28 @@ class HistoryController extends MultiSelectController
} }
@override @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; HistoryData data = response.response;
isEnd = data.list.isNullOrEmpty; isEnd = data.list.isNullOrEmpty;
max = data.list?.lastOrNull?.history.oid; max = data.list?.lastOrNull?.history.oid;
viewAt = data.list?.lastOrNull?.viewAt; 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!; tabs.value = data.tab!;
tabController = tabController = TabController(
TabController(length: data.tab!.length + 1, vsync: this); 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),
); );
} }
loadingState.value = LoadingState.success(data.list); }
return true;
return false;
} }
// 观看历史暂停状态 // 观看历史暂停状态
@@ -125,7 +129,8 @@ class HistoryController extends MultiSelectController
}).toList(); }).toList();
dynamic response = await UserHttp.delHistory(kidList); dynamic response = await UserHttp.delHistory(kidList);
if (response['status']) { if (response['status']) {
List remainList = ((loadingState.value as Success).response as List) List<HisListItem> remainList =
((loadingState.value as Success).response as List<HisListItem>)
.toSet() .toSet()
.difference(result.toSet()) .difference(result.toSet())
.toList(); .toList();
@@ -179,7 +184,7 @@ class HistoryController extends MultiSelectController
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<HistoryData>> customGetData() =>
UserHttp.historyList(type: type ?? 'all', max: max, viewAt: viewAt); UserHttp.historyList(type: type ?? 'all', max: max, viewAt: viewAt);
@override @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/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/loading_state.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/fav_search/view.dart' show SearchType;
import 'package:PiliPlus/pages/history/base_controller.dart'; import 'package:PiliPlus/pages/history/base_controller.dart';
import 'package:PiliPlus/utils/extension.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) { return switch (loadingState) {
Loading() => SliverGrid( Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -276,7 +277,7 @@ class _HistoryPageState extends State<HistoryPage>
childCount: 10, childCount: 10,
), ),
), ),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: StyleString.safeSpace - 5, top: StyleString.safeSpace - 5,
@@ -290,18 +291,18 @@ class _HistoryPageState extends State<HistoryPage>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_historyController.onLoadMore(); _historyController.onLoadMore();
} }
return HistoryItem( return HistoryItem(
videoItem: loadingState.response[index], videoItem: loadingState.response![index],
ctr: _historyController.baseCtr, ctr: _historyController.baseCtr,
onChoose: () => _historyController.onSelect(index), onChoose: () => _historyController.onSelect(index),
onDelete: (kid, business) => onDelete: (kid, business) =>
_historyController.delHistory(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 HisListItem videoItem;
final dynamic ctr; final dynamic ctr;
final Function? onChoose; final Function? onChoose;
final Function? onDelete; final Function(dynamic kid, dynamic business) onDelete;
const HistoryItem({ const HistoryItem({
super.key, super.key,
required this.videoItem, required this.videoItem,
this.ctr, this.ctr,
this.onChoose, this.onChoose,
this.onDelete, required this.onDelete,
}); });
@override @override
@@ -380,10 +380,8 @@ class HistoryItem extends StatelessWidget {
), ),
), ),
PopupMenuItem<String>( PopupMenuItem<String>(
onTap: () => onDelete != null onTap: () =>
? onDelete!(videoItem.kid, videoItem.history.business) onDelete(videoItem.kid, videoItem.history.business),
: ctr.delHistory(
videoItem.kid, videoItem.history.business),
height: 35, height: 35,
child: const Row( child: const Row(
children: [ children: [

View File

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

View File

@@ -1,10 +1,12 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/http/video.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:PiliPlus/utils/storage.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class HotController extends CommonController { class HotController
extends CommonListController<List<HotVideoItemModel>, HotVideoItemModel> {
// int idx = 0; // int idx = 0;
late RxBool showHotRcmd = GStorage.showHotRcmd.obs; late RxBool showHotRcmd = GStorage.showHotRcmd.obs;
@@ -22,7 +24,8 @@ class HotController extends CommonController {
// } // }
@override @override
Future<LoadingState> customGetData() => VideoHttp.hotVideoList( Future<LoadingState<List<HotVideoItemModel>>> customGetData() =>
VideoHttp.hotVideoList(
pn: currentPage, pn: currentPage,
ps: 20, 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/common/widgets/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/tab_type.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/common/common_page.dart';
import 'package:PiliPlus/pages/rank/view.dart'; import 'package:PiliPlus/pages/rank/view.dart';
import 'package:flutter/material.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) { return switch (loadingState) {
Loading() => _buildSkeleton(), Loading() => _buildSkeleton(),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverGrid( ? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2, mainAxisSpacing: 2,
@@ -172,15 +173,15 @@ class _HotPageState extends CommonPageState<HotPage, HotController>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
controller.onLoadMore(); controller.onLoadMore();
} }
return VideoCardH( return VideoCardH(
videoItem: loadingState.response[index], videoItem: loadingState.response![index],
showPubdate: true, showPubdate: true,
); );
}, },
childCount: loadingState.response.length, childCount: loadingState.response!.length,
), ),
) )
: HttpError( : HttpError(

View File

@@ -13,7 +13,7 @@ import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/http/reply.dart';
import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:fixnum/fixnum.dart' as $fixnum;
class HtmlRenderController extends ReplyController { class HtmlRenderController extends ReplyController<MainListReply> {
late String id; late String id;
late String dynamicType; late String dynamicType;
late int type; late int type;
@@ -91,7 +91,12 @@ class HtmlRenderController extends ReplyController {
} }
@override @override
Future<LoadingState> customGetData() { List<ReplyInfo>? getDataList(MainListReply response) {
return response.replies;
}
@override
Future<LoadingState<MainListReply>> customGetData() {
return ReplyHttp.replyListGrpc( return ReplyHttp.replyListGrpc(
type: type, type: type,
oid: oid.value, 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) { return switch (loadingState) {
Loading() => SliverList.builder( Loading() => SliverList.builder(
itemCount: 5, itemCount: 5,
@@ -771,11 +771,11 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
return const VideoReplySkeleton(); return const VideoReplySkeleton();
}, },
), ),
Success() => (loadingState.response.replies as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverList.builder( ? SliverList.builder(
itemCount: loadingState.response.replies.length + 1, itemCount: loadingState.response!.length + 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == loadingState.response.replies.length) { if (index == loadingState.response!.length) {
_htmlRenderCtr.onLoadMore(); _htmlRenderCtr.onLoadMore();
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
@@ -785,7 +785,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
child: Text( child: Text(
_htmlRenderCtr.isEnd.not _htmlRenderCtr.isEnd.not
? '加载中...' ? '加载中...'
: loadingState.response.replies.isEmpty : loadingState.response!.isEmpty
? '还没有评论' ? '还没有评论'
: '没有更多了', : '没有更多了',
style: TextStyle( style: TextStyle(
@@ -796,19 +796,20 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
); );
} else { } else {
return ReplyItemGrpc( return ReplyItemGrpc(
replyItem: loadingState.response.replies[index], replyItem: loadingState.response![index],
replyLevel: '1', replyLevel: '1',
replyReply: (replyItem, id) => replyReply: (replyItem, id) =>
replyReply(context, replyItem, id), replyReply(context, replyItem, id),
onReply: () { onReply: () {
_htmlRenderCtr.onReply( _htmlRenderCtr.onReply(
context, context,
replyItem: loadingState.response.replies[index], replyItem: loadingState.response![index],
index: index, index: index,
); );
}, },
onDelete: _htmlRenderCtr.onMDelete, onDelete: (subIndex) =>
upMid: loadingState.response.subjectControl.upMid, _htmlRenderCtr.onRemove(index, subIndex),
upMid: _htmlRenderCtr.upMid,
callback: _getImageCallback, callback: _getImageCallback,
onCheckReply: (item) => onCheckReply: (item) =>
_htmlRenderCtr.onCheckReply(context, 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:get/get.dart';
import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/user.dart';
class LaterController extends MultiSelectController { class LaterController extends MultiSelectController<Map, HotVideoItemModel> {
RxInt count = (-1).obs; RxInt count = (-1).obs;
dynamic mid; dynamic mid;
@@ -22,25 +22,24 @@ class LaterController extends MultiSelectController {
} }
@override @override
bool customHandleResponse(Success response) { List<HotVideoItemModel>? getDataList(response) {
count.value = response.response['count']; return response['list'];
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;
} }
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( await showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
@@ -50,7 +49,7 @@ class LaterController extends MultiSelectController {
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'), aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: Get.back,
child: Text( child: Text(
'取消', '取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -62,10 +61,11 @@ class LaterController extends MultiSelectController {
await UserHttp.toViewDel(aids: aid != null ? [aid] : null); await UserHttp.toViewDel(aids: aid != null ? [aid] : null);
if (res['status']) { if (res['status']) {
if (aid != null) { if (aid != null) {
List list = (loadingState.value as Success).response; List<HotVideoItemModel> list =
list.removeWhere((e) => e.aid == aid); (loadingState.value as Success).response;
list.removeAt(index);
count.value -= 1; count.value -= 1;
loadingState.value = LoadingState.success(list); loadingState.refresh();
} else { } else {
onReload(); onReload();
} }
@@ -90,7 +90,7 @@ class LaterController extends MultiSelectController {
onConfirm: () async { onConfirm: () async {
var res = await UserHttp.toViewClear(); var res = await UserHttp.toViewClear();
if (res['status']) { if (res['status']) {
loadingState.value = LoadingState.success([]); loadingState.value = LoadingState.success(null);
} }
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
}, },
@@ -98,7 +98,7 @@ class LaterController extends MultiSelectController {
} }
@override @override
Future<LoadingState> customGetData() => UserHttp.seeYouLater(); Future<LoadingState<Map>> customGetData() => UserHttp.seeYouLater();
onDelChecked(BuildContext context) { onDelChecked(BuildContext context) {
showDialog( showDialog(
@@ -137,7 +137,8 @@ class LaterController extends MultiSelectController {
List aids = result.map((item) => item.aid).toList(); List aids = result.map((item) => item.aid).toList();
dynamic res = await UserHttp.toViewDel(aids: aids); dynamic res = await UserHttp.toViewDel(aids: aids);
if (res['status']) { if (res['status']) {
Set remainList = ((loadingState.value as Success).response as List) Set<HotVideoItemModel> remainList =
((loadingState.value as Success).response as List<HotVideoItemModel>)
.toSet() .toSet()
.difference(result.toSet()); .difference(result.toSet());
count.value -= aids.length; count.value -= aids.length;

View File

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

View File

@@ -1,12 +1,14 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/http/live.dart';
import 'package:PiliPlus/models/live/follow.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/extension.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
class LiveController extends CommonController { class LiveController
extends CommonListController<List<LiveItemModel>?, LiveItemModel> {
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@@ -17,7 +19,8 @@ class LiveController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => LiveHttp.liveList(pn: currentPage); Future<LoadingState<List<LiveItemModel>?>> customGetData() =>
LiveHttp.liveList(pn: currentPage);
@override @override
Future onRefresh() { Future onRefresh() {
@@ -41,20 +44,34 @@ class LiveController extends CommonController {
} }
dynamic res = await LiveHttp.liveFollowing(pn: followPage, ps: 20); dynamic res = await LiveHttp.liveFollowing(pn: followPage, ps: 20);
if (res['status']) { if (res['status']) {
followPage++; LiveFollowingModel data = res['data'];
liveCount.value = res['data'].liveCount; liveCount.value = data.liveCount ?? 0;
List list = res['data'] List<LiveFollowingItemModel>? dataList = data.list
.list ?.where((LiveFollowingItemModel item) =>
.where((LiveFollowingItemModel item) =>
item.liveStatus == 1 && item.recordLiveTime == 0) item.liveStatus == 1 && item.recordLiveTime == 0)
.toList(); .toList();
if (isRefresh.not && followListState.value is Success) { if (dataList.isNullOrEmpty) {
list.insertAll(0, (followListState.value as Success).response); followEnd = true;
if (isRefresh) {
followListState.value = LoadingState.success(dataList);
} }
followEnd = list.length >= liveCount.value || return;
list.isEmpty || }
(res['data'].list as List?).isNullOrEmpty; if (isRefresh) {
followListState.value = LoadingState.success(list); 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 { } else {
followListState.value = LoadingState.error(res['msg']); 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/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPlus/http/loading_state.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/common/common_page.dart';
import 'package:PiliPlus/pages/live/controller.dart'; import 'package:PiliPlus/pages/live/controller.dart';
import 'package:PiliPlus/pages/live/widgets/live_item.dart'; import 'package:PiliPlus/pages/live/widgets/live_item.dart';
@@ -66,17 +67,7 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
top: StyleString.cardSpace, top: StyleString.cardSpace,
bottom: MediaQuery.paddingOf(context).bottom + 80, bottom: MediaQuery.paddingOf(context).bottom + 80,
), ),
sliver: Obx( sliver: Obx(() => _buildBody(controller.loadingState.value)),
() => 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,
),
),
), ),
], ],
), ),
@@ -84,33 +75,49 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
); );
} }
Widget contentGrid(LoadingState loadingState) { Widget _buildBody(LoadingState<List<LiveItemModel>?> loadingState) {
return SliverGrid( return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio( gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
// 列间距
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace,
// 最大宽度
maxCrossAxisExtent: Grid.smallCardWidth, maxCrossAxisExtent: Grid.smallCardWidth,
childAspectRatio: StyleString.aspectRatio, childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), mainAxisExtent: MediaQuery.textScalerOf(context).scale(90),
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (context, index) {
if (loadingState is Success && return const VideoCardVSkeleton();
index == loadingState.response.length - 1) { },
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(); controller.onLoadMore();
} }
return loadingState is Success return LiveCardV(liveItem: loadingState.response![index]);
? LiveCardV(
liveItem: loadingState.response[index],
)
: const VideoCardVSkeleton();
}, },
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() { Widget _buildFollowList() {

View File

@@ -1,11 +1,13 @@
import 'package:PiliPlus/http/loading_state.dart'; 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:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
class MediaController extends CommonController { class MediaController
extends CommonDataController<FavFolderData, FavFolderData> {
List list = [ List list = [
// { // {
// 'icon': Icons.file_download_outlined, // 'icon': Icons.file_download_outlined,
@@ -52,14 +54,14 @@ class MediaController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { bool customHandleResponse(bool isRefresh, Success<FavFolderData> response) {
count.value = response.response.count; count.value = response.response.count ?? -1;
loadingState.value = response; loadingState.value = response;
return true; return true;
} }
@override @override
Future<LoadingState> customGetData() { Future<LoadingState<FavFolderData>> customGetData() {
mid ??= Accounts.main.mid; mid ??= Accounts.main.mid;
return UserHttp.userfavFolder( return UserHttp.userfavFolder(
pn: 1, 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/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/member_article_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
@@ -38,10 +39,10 @@ class _MemberArticleState extends State<MemberArticle>
return Obx(() => _buildBody(_controller.loadingState.value)); return Obx(() => _buildBody(_controller.loadingState.value));
} }
_buildBody(LoadingState loadingState) { _buildBody(LoadingState<List<Item>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator( ? refreshIndicator(
onRefresh: () async { onRefresh: () async {
await _controller.onRefresh(); await _controller.onRefresh();
@@ -56,14 +57,14 @@ class _MemberArticleState extends State<MemberArticle>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_controller.onLoadMore(); _controller.onLoadMore();
} }
return MemberArticleItem( 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/http/member.dart';
import 'package:PiliPlus/models/space_article/item.dart'; import 'package:PiliPlus/models/space_article/item.dart';
import 'package:PiliPlus/models/space_article/data.dart'; import 'package:PiliPlus/models/space_article/data.dart';
import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
class MemberArticleCtr extends CommonController { class MemberArticleCtr extends CommonListController<Data, Item> {
MemberArticleCtr({ MemberArticleCtr({
required this.mid, required this.mid,
}); });
@@ -21,24 +20,24 @@ class MemberArticleCtr extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { List<Item>? getDataList(Data response) {
Data data = response.response; return response.item;
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;
} }
@override @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); 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/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/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/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart';
import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/grid.dart';
@@ -41,10 +42,10 @@ class _MemberBangumiState extends State<MemberBangumi>
return Obx(() => _buildBody(_controller.loadingState.value)); return Obx(() => _buildBody(_controller.loadingState.value));
} }
_buildBody(LoadingState loadingState) { _buildBody(LoadingState<List<Item>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? refreshIndicator( ? refreshIndicator(
onRefresh: () async { onRefresh: () async {
await _controller.onRefresh(); await _controller.onRefresh();
@@ -70,14 +71,14 @@ class _MemberBangumiState extends State<MemberBangumi>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_controller.onLoadMore(); _controller.onLoadMore();
} }
return BangumiCardVMemberHome( 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/http/member.dart';
import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/item.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' import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/models/space/data.dart' as space; import 'package:PiliPlus/models/space/data.dart' as space;
class MemberBangumiCtr extends CommonController { class MemberBangumiCtr extends CommonListController<Data, Item> {
MemberBangumiCtr({ MemberBangumiCtr({
required this.mid, required this.mid,
required this.heroTag, required this.heroTag,
@@ -37,24 +36,19 @@ class MemberBangumiCtr extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { List<Item>? getDataList(Data response) {
Data data = response.response; return response.item;
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;
} }
@override @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, type: ContributeType.bangumi,
mid: mid, mid: mid,
pn: currentPage, pn: currentPage,

View File

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

View File

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

View File

@@ -39,10 +39,10 @@ class _SeasonSeriesPageState extends State<SeasonSeriesPage>
return Obx(() => _buildBody(_controller.loadingState.value)); return Obx(() => _buildBody(_controller.loadingState.value));
} }
Widget _buildBody(LoadingState loadingState) { Widget _buildBody(LoadingState<List<dynamic>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? CustomScrollView( ? CustomScrollView(
slivers: [ slivers: [
SliverPadding( SliverPadding(
@@ -58,13 +58,13 @@ class _SeasonSeriesPageState extends State<SeasonSeriesPage>
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_controller.onLoadMore(); _controller.onLoadMore();
} }
dynamic item = loadingState.response![index];
return SeasonSeriesCard( return SeasonSeriesCard(
item: loadingState.response[index], item: item,
onTap: () { onTap: () {
dynamic item = loadingState.response[index];
bool isSeason = item['meta']['season_id'] != null; bool isSeason = item['meta']['season_id'] != null;
dynamic id = isSeason dynamic id = isSeason
? item['meta']['season_id'] ? 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)); return Obx(() => _buildBody(_controller.loadingState.value));
} }
_buildBody(LoadingState loadingState) { _buildBody(LoadingState<List<Item>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? Stack( ? Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
@@ -186,17 +186,17 @@ class _MemberVideoState extends State<MemberVideo>
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (widget.type != ContributeType.season && if (widget.type != ContributeType.season &&
index == loadingState.response.length - 1) { index == loadingState.response!.length - 1) {
_controller.onLoadMore(); _controller.onLoadMore();
} }
final Item item = loadingState.response[index]; final Item item = loadingState.response![index];
return VideoCardHMemberVideo( return VideoCardHMemberVideo(
key: ValueKey('${item.param}'), key: ValueKey('${item.param}'),
videoItem: item, videoItem: item,
fromViewAid: _controller.fromViewAid, 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/data.dart';
import 'package:PiliPlus/models/space_archive/episodic_button.dart'; import 'package:PiliPlus/models/space_archive/episodic_button.dart';
import 'package:PiliPlus/models/space_archive/item.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' import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPlus/utils/extension.dart'; 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:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class MemberVideoCtr extends CommonController { class MemberVideoCtr extends CommonListController<Data, Item> {
MemberVideoCtr({ MemberVideoCtr({
required this.type, required this.type,
required this.mid, required this.mid,
@@ -70,7 +70,7 @@ class MemberVideoCtr extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { bool customHandleResponse(bool isRefresh, Success<Data> response) {
Data data = response.response; Data data = response.response;
episodicButton.value = data.episodicButton ?? EpisodicButton(); episodicButton.value = data.episodicButton ?? EpisodicButton();
episodicButton.refresh(); episodicButton.refresh();
@@ -105,7 +105,7 @@ class MemberVideoCtr extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => MemberHttp.spaceArchive( Future<LoadingState<Data>> customGetData() => MemberHttp.spaceArchive(
type: type, type: type,
mid: mid, mid: mid,
aid: type == ContributeType.video aid: type == ContributeType.video

View File

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

View File

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

View File

@@ -1,8 +1,10 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.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; final dynamic mid;
MemberCoinController({this.mid}); MemberCoinController({this.mid});
@@ -13,6 +15,6 @@ class MemberCoinController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<List<MemberCoinsDataModel>?>> customGetData() =>
MemberHttp.getRecentCoinVideo(mid: mid); MemberHttp.getRecentCoinVideo(mid: mid);
} }

View File

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

View File

@@ -1,12 +1,13 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/msg.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/utils/extension.dart';
import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class MemberDynamicsController extends CommonController { class MemberDynamicsController
extends CommonListController<DynamicsDataModel, DynamicItemModel> {
MemberDynamicsController(this.mid); MemberDynamicsController(this.mid);
int mid; int mid;
String offset = ''; String offset = '';
@@ -32,22 +33,24 @@ class MemberDynamicsController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { List<DynamicItemModel>? getDataList(DynamicsDataModel response) {
DynamicsDataModel data = response.response; return response.items;
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;
} }
@override @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, offset: offset,
mid: mid, mid: mid,
); );
@@ -55,9 +58,9 @@ class MemberDynamicsController extends CommonController {
Future onRemove(dynamicId) async { Future onRemove(dynamicId) async {
var res = await MsgHttp.removeDynamic(dynamicId); var res = await MsgHttp.removeDynamic(dynamicId);
if (res['status']) { if (res['status']) {
List list = (loadingState.value as Success).response; List<DynamicItemModel> list = (loadingState.value as Success).response;
list.removeWhere((item) => item.idStr == dynamicId); list.removeWhere((item) => item.idStr == dynamicId);
loadingState.value = LoadingState.success(list); loadingState.refresh();
SmartDialog.showToast('删除成功'); SmartDialog.showToast('删除成功');
} else { } else {
SmartDialog.showToast(res['msg']); 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/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/pages/member_dynamics/index.dart'; import 'package:PiliPlus/pages/member_dynamics/index.dart';
@@ -56,26 +58,57 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
onRefresh: () async { onRefresh: () async {
await _memberDynamicController.onRefresh(); await _memberDynamicController.onRefresh();
}, },
child: Obx( child: CustomScrollView(
() => _memberDynamicController.loadingState.value is Loading
? Center(
child: CircularProgressIndicator(),
)
: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
_buildContent(_memberDynamicController.loadingState.value), Obx(
() => _buildContent(_memberDynamicController.loadingState.value),
)
], ],
), ),
),
); );
_buildContent(LoadingState loadingState) { Widget skeleton() {
return switch (loadingState) { if (!dynamicsWaterfallFlow) {
Loading() => HttpError( return SliverCrossAxisGroup(
callback: _memberDynamicController.onReload, 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( ? SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80, bottom: MediaQuery.paddingOf(context).bottom + 80,
@@ -83,24 +116,12 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
sliver: dynamicsWaterfallFlow sliver: dynamicsWaterfallFlow
? SliverWaterfallFlow.extent( ? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2, maxCrossAxisExtent: Grid.smallCardWidth * 2,
//cacheExtent: 0.0,
crossAxisSpacing: StyleString.safeSpace, 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) { lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_memberDynamicController.onLoadMore(); _memberDynamicController.onLoadMore();
} }
return index == loadingState.response.length return index == loadingState.response!.length
? LastChildLayoutType.foot ? LastChildLayoutType.foot
: LastChildLayoutType.none; : LastChildLayoutType.none;
}, },
@@ -121,15 +142,16 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
sliver: SliverList( sliver: SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == loadingState.response.length - 1) { if (index ==
loadingState.response!.length - 1) {
_memberDynamicController.onLoadMore(); _memberDynamicController.onLoadMore();
} }
return DynamicPanel( return DynamicPanel(
item: loadingState.response[index], item: loadingState.response![index],
onRemove: _memberDynamicController.onRemove, 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/loading_state.dart';
import 'package:PiliPlus/http/member.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; final dynamic mid;
MemberLikeController({this.mid}); MemberLikeController({this.mid});
@@ -13,6 +15,6 @@ class MemberLikeController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<List<MemberCoinsDataModel>?>> customGetData() =>
MemberHttp.getRecentLikeVideo(mid: mid); MemberHttp.getRecentLikeVideo(mid: mid);
} }

View File

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

View File

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

View File

@@ -1,11 +1,10 @@
import 'package:PiliPlus/http/loading_state.dart'; 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/http/msg.dart';
import 'package:PiliPlus/models/msg/msgfeed_at_me.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'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class AtMeController extends CommonController { class AtMeController extends CommonListController<MsgFeedAtMe, AtMeItems> {
int cursor = -1; int cursor = -1;
int cursorTime = -1; int cursorTime = -1;
@@ -16,19 +15,19 @@ class AtMeController extends CommonController {
} }
@override @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; MsgFeedAtMe data = response.response;
if (data.cursor?.isEnd == true || data.items.isNullOrEmpty) { if (data.cursor?.isEnd == true) {
isEnd = true; isEnd = true;
} }
cursor = data.cursor?.id ?? -1; cursor = data.cursor?.id ?? -1;
cursorTime = data.cursor?.time ?? -1; cursorTime = data.cursor?.time ?? -1;
if (currentPage != 1 && loadingState.value is Success) { return false;
data.items ??= <AtMeItems>[];
data.items!.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(data.items);
return true;
} }
@override @override
@@ -39,16 +38,16 @@ class AtMeController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<MsgFeedAtMe>> customGetData() =>
MsgHttp.msgFeedAtMe(cursor: cursor, cursorTime: cursorTime); MsgHttp.msgFeedAtMe(cursor: cursor, cursorTime: cursorTime);
Future onRemove(dynamic id, int index) async { Future onRemove(dynamic id, int index) async {
try { try {
var res = await MsgHttp.delMsgfeed(2, id); var res = await MsgHttp.delMsgfeed(2, id);
if (res['status']) { if (res['status']) {
List list = (loadingState.value as Success).response; List<AtMeItems> list = (loadingState.value as Success).response;
list.removeAt(index); list.removeAt(index);
loadingState.value = LoadingState.success(list); loadingState.refresh();
SmartDialog.showToast('删除成功'); SmartDialog.showToast('删除成功');
} else { } else {
SmartDialog.showToast(res['msg']); 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/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/app_scheme.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.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) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.separated( ? ListView.separated(
itemCount: loadingState.response.length, itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80), bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) { itemBuilder: (context, int index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_atMeController.onLoadMore(); _atMeController.onLoadMore();
} }
final item = loadingState.response[index]; final item = loadingState.response![index];
return ListTile( return ListTile(
onTap: () { onTap: () {
String? nativeUri = item.item?.nativeUri; String? nativeUri = item.item?.nativeUri;
@@ -103,10 +104,9 @@ class _AtMePageState extends State<AtMePage> {
subtitle: Column( subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if ((item.item?.sourceContent as String?)?.isNotEmpty == if (item.item?.sourceContent?.isNotEmpty == true) ...[
true) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
Text(item.item?.sourceContent, Text(item.item!.sourceContent!,
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context) style: Theme.of(context)

View File

@@ -1,12 +1,12 @@
import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/http/loading_state.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/http/msg.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/models/msg/msgfeed_like_me.dart'; import 'package:PiliPlus/models/msg/msgfeed_like_me.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class LikeMeController extends CommonController { class LikeMeController extends CommonDataController<MsgFeedLikeMe, dynamic> {
int cursor = -1; int cursor = -1;
int cursorTime = -1; int cursorTime = -1;
@@ -17,7 +17,7 @@ class LikeMeController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { bool customHandleResponse(bool isRefresh, Success<MsgFeedLikeMe> response) {
MsgFeedLikeMe data = response.response; MsgFeedLikeMe data = response.response;
if (data.total?.cursor?.isEnd == true || if (data.total?.cursor?.isEnd == true ||
data.total?.items.isNullOrEmpty == true) { data.total?.items.isNullOrEmpty == true) {
@@ -46,7 +46,7 @@ class LikeMeController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<MsgFeedLikeMe>> customGetData() =>
MsgHttp.msgFeedLikeMe(cursor: cursor, cursorTime: cursorTime); MsgHttp.msgFeedLikeMe(cursor: cursor, cursorTime: cursorTime);
Future onRemove(dynamic id, int index, bool isLatest) async { 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/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/http/msg.dart';
import 'package:PiliPlus/models/msg/msgfeed_reply_me.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'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class ReplyMeController extends CommonController { class ReplyMeController
extends CommonListController<MsgFeedReplyMe, ReplyMeItems> {
int cursor = -1; int cursor = -1;
int cursorTime = -1; int cursorTime = -1;
@@ -16,19 +16,19 @@ class ReplyMeController extends CommonController {
} }
@override @override
bool customHandleResponse(Success response) { List<ReplyMeItems>? getDataList(MsgFeedReplyMe response) {
MsgFeedReplyMe data = response.response; return response.items;
if (data.cursor?.isEnd == true || data.items.isNullOrEmpty) { }
@override
bool customHandleResponse(bool isRefresh, Success<MsgFeedReplyMe> response) {
final data = response.response;
if (data.cursor?.isEnd == true) {
isEnd = true; isEnd = true;
} }
cursor = data.cursor?.id ?? -1; cursor = data.cursor?.id ?? -1;
cursorTime = data.cursor?.time ?? -1; cursorTime = data.cursor?.time ?? -1;
if (currentPage != 1 && loadingState.value is Success) { return false;
data.items ??= <ReplyMeItems>[];
data.items!.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(data.items);
return true;
} }
@override @override
@@ -39,16 +39,16 @@ class ReplyMeController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<MsgFeedReplyMe>> customGetData() =>
MsgHttp.msgFeedReplyMe(cursor: cursor, cursorTime: cursorTime); MsgHttp.msgFeedReplyMe(cursor: cursor, cursorTime: cursorTime);
Future onRemove(dynamic id, int index) async { Future onRemove(dynamic id, int index) async {
try { try {
var res = await MsgHttp.delMsgfeed(1, id); var res = await MsgHttp.delMsgfeed(1, id);
if (res['status']) { if (res['status']) {
List list = (loadingState.value as Success).response; List<ReplyMeItems> list = (loadingState.value as Success).response;
list.removeAt(index); list.removeAt(index);
loadingState.value = LoadingState.success(list); loadingState.refresh();
SmartDialog.showToast('删除成功'); SmartDialog.showToast('删除成功');
} else { } else {
SmartDialog.showToast(res['msg']); 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) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.separated( ? ListView.separated(
itemCount: loadingState.response.length, itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80), bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) { itemBuilder: (context, int index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_replyMeController.onLoadMore(); _replyMeController.onLoadMore();
} }
ReplyMeItems item = loadingState.response[index]; ReplyMeItems item = loadingState.response![index];
return ListTile( return ListTile(
onTap: () { onTap: () {
String? nativeUri = item.item?.nativeUri; String? nativeUri = item.item?.nativeUri;
@@ -121,12 +121,8 @@ class _ReplyMePageState extends State<ReplyMePage> {
Text(item.item?.sourceContent ?? "", Text(item.item?.sourceContent ?? "",
style: Theme.of(context).textTheme.bodyMedium), style: Theme.of(context).textTheme.bodyMedium),
const SizedBox(height: 4), const SizedBox(height: 4),
if (loadingState if (item.item?.targetReplyContent != null &&
.response[index].item?.targetReplyContent != item.item?.targetReplyContent != "")
null &&
loadingState
.response[index].item?.targetReplyContent !=
"")
Text("| ${item.item?.targetReplyContent}", Text("| ${item.item?.targetReplyContent}",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View File

@@ -1,10 +1,12 @@
import 'package:PiliPlus/http/loading_state.dart'; 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:PiliPlus/utils/extension.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/http/msg.dart';
class SysMsgController extends CommonController { class SysMsgController
extends CommonListController<List<SystemNotifyList>?, SystemNotifyList> {
final pageSize = 20; final pageSize = 20;
int cursor = -1; int cursor = -1;
@@ -41,9 +43,9 @@ class SysMsgController extends CommonController {
try { try {
var res = await MsgHttp.delSysMsg(id); var res = await MsgHttp.delSysMsg(id);
if (res['status']) { if (res['status']) {
List list = (loadingState.value as Success).response; List<SystemNotifyList> list = (loadingState.value as Success).response;
list.removeAt(index); list.removeAt(index);
loadingState.value = LoadingState.success(list); loadingState.refresh();
SmartDialog.showToast('删除成功'); SmartDialog.showToast('删除成功');
} else { } else {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
@@ -52,6 +54,6 @@ class SysMsgController extends CommonController {
} }
@override @override
Future<LoadingState> customGetData() => Future<LoadingState<List<SystemNotifyList>?>> customGetData() =>
MsgHttp.msgFeedNotify(cursor: cursor, pageSize: pageSize); 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/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.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/app_scheme.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/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) { return switch (loadingState) {
Loading() => loadingWidget, Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? ListView.separated( ? ListView.separated(
itemCount: loadingState.response.length, itemCount: loadingState.response!.length,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80), bottom: MediaQuery.paddingOf(context).bottom + 80),
itemBuilder: (context, int index) { itemBuilder: (context, int index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
_sysMsgController.onLoadMore(); _sysMsgController.onLoadMore();
} }
final item = loadingState.response[index]; final item = loadingState.response![index];
String? content = item.content; String? content = item.content;
if (content != null) { if (content != null) {
try { try {

View File

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

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