Compare commits

...

14 Commits

22 changed files with 300 additions and 209 deletions

View File

@ -22,20 +22,27 @@ class HttpError extends StatelessWidget {
"assets/images/error.svg",
height: 200,
),
const SizedBox(height: 20),
const SizedBox(height: 30),
Text(
errMsg ?? '请求异常',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 30),
OutlinedButton.icon(
const SizedBox(height: 20),
FilledButton.tonal(
onPressed: () {
fn!();
},
icon: const Icon(Icons.arrow_forward_outlined, size: 20),
label: Text(btnText ?? '点击重试'),
)
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.primary.withAlpha(20);
}),
),
child: Text(
btnText ?? '点击重试',
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
),
],
),
),

View File

@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
class StatDanMu extends StatelessWidget {
final String? theme;
final int? danmu;
final dynamic danmu;
final String? size;
const StatDanMu({Key? key, this.theme, this.danmu, this.size})

View File

@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
class StatView extends StatelessWidget {
final String? theme;
final int? view;
final dynamic view;
final String? size;
const StatView({Key? key, this.theme, this.view, this.size})

View File

@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../models/model_rec_video_item.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
import '../../http/dynamics.dart';
import '../../http/search.dart';
import '../../http/user.dart';
@ -322,19 +325,31 @@ class VideoStat extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RichText(
maxLines: 1,
text: TextSpan(
style: TextStyle(
fontSize: MediaQuery.textScalerOf(context)
.scale(Theme.of(context).textTheme.labelSmall!.fontSize!),
color: Theme.of(context).colorScheme.outline,
return Row(
children: [
StatView(
theme: 'gray',
view: videoItem.stat.view,
),
children: [
TextSpan(text: '${Utils.numFormat(videoItem.stat.view)}观看'),
TextSpan(text: '${Utils.numFormat(videoItem.stat.danmu)}弹幕'),
],
),
const SizedBox(width: 8),
StatDanMu(
theme: 'gray',
danmu: videoItem.stat.danmu,
),
if (videoItem is RecVideoItemModel) ...<Widget>[
const Spacer(),
RichText(
maxLines: 1,
text: TextSpan(
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
),
const SizedBox(width: 4),
]
],
);
}
}

View File

@ -130,7 +130,7 @@ class VideoHttp {
}
return {'status': true, 'data': list};
} else {
return {'status': false, 'data': []};
return {'status': false, 'data': [], 'msg': res.data['message']};
}
} catch (err) {
return {'status': false, 'data': [], 'msg': err};

View File

@ -9,7 +9,6 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rcmd/view.dart';
import 'controller.dart';
import 'widgets/bangumu_card_v.dart';
@ -199,7 +198,10 @@ class _BangumiPageState extends State<BangumiPage>
} else {
return HttpError(
errMsg: data['msg'],
fn: () => {},
fn: () {
_futureBuilderFuture =
_bangumidController.queryBangumiListFeed();
},
);
}
} else {
@ -208,7 +210,6 @@ class _BangumiPageState extends State<BangumiPage>
},
),
),
const LoadingMore()
],
),
);

View File

@ -192,22 +192,6 @@ class _DynamicsPageState extends State<DynamicsPage>
)
],
),
// Obx(
// () => Visibility(
// visible: _dynamicsController.userLogin.value,
// child: Positioned(
// right: 4,
// top: 0,
// bottom: 0,
// child: IconButton(
// padding: EdgeInsets.zero,
// onPressed: () =>
// {feedBack(), _dynamicsController.resetSearch()},
// icon: const Icon(Icons.history, size: 21),
// ),
// ),
// ),
// ),
],
),
),
@ -229,7 +213,8 @@ class _DynamicsPageState extends State<DynamicsPage>
return Obx(() => UpPanel(_dynamicsController.upData.value));
} else {
return const SliverToBoxAdapter(
child: SizedBox(height: 80));
child: SizedBox(height: 80),
);
}
} else {
return const SliverToBoxAdapter(
@ -240,15 +225,6 @@ class _DynamicsPageState extends State<DynamicsPage>
}
},
),
SliverToBoxAdapter(
child: Container(
height: 6,
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.5),
),
),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {

View File

@ -36,8 +36,7 @@ class _UpPanelState extends State<UpPanel> {
}
upList.insert(
0,
UpItem(
face: 'https://files.catbox.moe/8uc48f.png', uname: '全部动态', mid: -1),
UpItem(face: '', uname: '全部动态', mid: -1),
);
userInfo = userInfoCache.get('userInfoCache');
upList.insert(
@ -56,7 +55,7 @@ class _UpPanelState extends State<UpPanel> {
floating: true,
pinned: false,
delegate: _SliverHeaderDelegate(
height: 124,
height: 126,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@ -121,6 +120,13 @@ class _UpPanelState extends State<UpPanel> {
],
),
),
Container(
height: 6,
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.5),
),
],
)),
);
@ -171,6 +177,9 @@ class _UpPanelState extends State<UpPanel> {
},
onLongPress: () {
feedBack();
if (data.mid == -1) {
return;
}
String heroTag = Utils.makeHeroTag(data.mid);
Get.toNamed('/member?mid=${data.mid}',
arguments: {'face': data.face, 'heroTag': heroTag});
@ -198,12 +207,19 @@ class _UpPanelState extends State<UpPanel> {
backgroundColor: data.type == 'live'
? Theme.of(context).colorScheme.secondaryContainer
: Theme.of(context).colorScheme.primary,
child: NetworkImgLayer(
width: 49,
height: 49,
src: data.face,
type: 'avatar',
),
child: data.face != ''
? NetworkImgLayer(
width: 50,
height: 50,
src: data.face,
type: 'avatar',
)
: const CircleAvatar(
radius: 25,
backgroundImage: AssetImage(
'assets/images/noface.jpeg',
),
),
),
Padding(
padding: const EdgeInsets.only(top: 4),
@ -271,13 +287,11 @@ class UpPanelSkeleton extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 49,
height: 49,
width: 50,
height: 50,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(
Radius.circular(24),
),
borderRadius: BorderRadius.circular(50),
),
),
Container(

View File

@ -15,9 +15,14 @@ import '../../../common/widgets/badge.dart';
class FavVideoCardH extends StatelessWidget {
final dynamic videoItem;
final Function? callFn;
final int? searchType;
const FavVideoCardH({Key? key, required this.videoItem, this.callFn})
: super(key: key);
const FavVideoCardH({
Key? key,
required this.videoItem,
this.callFn,
this.searchType,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -107,7 +112,11 @@ class FavVideoCardH extends StatelessWidget {
},
),
),
VideoContent(videoItem: videoItem, callFn: callFn)
VideoContent(
videoItem: videoItem,
callFn: callFn,
searchType: searchType,
)
],
),
);
@ -123,7 +132,13 @@ class FavVideoCardH extends StatelessWidget {
class VideoContent extends StatelessWidget {
final dynamic videoItem;
final Function? callFn;
const VideoContent({super.key, required this.videoItem, this.callFn});
final int? searchType;
const VideoContent({
super.key,
required this.videoItem,
this.callFn,
this.searchType,
});
@override
Widget build(BuildContext context) {
@ -189,48 +204,51 @@ class VideoContent extends StatelessWidget {
),
],
),
Positioned(
right: 0,
bottom: -4,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('要取消收藏吗?'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(
color:
Theme.of(context).colorScheme.outline),
)),
TextButton(
onPressed: () async {
await callFn!();
Get.back();
},
child: const Text('确定取消'),
)
],
);
},
);
},
icon: Icon(
Icons.clear_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
),
),
searchType != 1
? Positioned(
right: 0,
bottom: -4,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('要取消收藏吗?'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline),
)),
TextButton(
onPressed: () async {
await callFn!();
Get.back();
},
child: const Text('确定取消'),
)
],
);
},
);
},
icon: Icon(
Icons.clear_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
),
)
: const SizedBox(),
],
),
),

View File

@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/user/fav_detail.dart';
import '../../http/video.dart';
class FavSearchController extends GetxController {
final ScrollController scrollController = ScrollController();
Rx<TextEditingController> controller = TextEditingController().obs;
@ -72,4 +75,21 @@ class FavSearchController extends GetxController {
if (!hasMore) return;
searchFav(type: 'onLoad');
}
onCancelFav(int id) async {
var result = await VideoHttp.favVideo(
aid: id, addIds: '', delIds: mediaId.toString());
if (result['status']) {
if (result['data']['prompt']) {
List dataList = favList;
for (var i in dataList) {
if (i.id == id) {
dataList.remove(i);
break;
}
}
SmartDialog.showToast('取消收藏');
}
}
}
}

View File

@ -8,9 +8,7 @@ import 'package:pilipala/pages/fav_detail/widget/fav_video_card.dart';
import 'controller.dart';
class FavSearchPage extends StatefulWidget {
final int? sourceType;
final int? mediaId;
const FavSearchPage({super.key, this.sourceType, this.mediaId});
const FavSearchPage({super.key});
@override
State<FavSearchPage> createState() => _FavSearchPageState();
@ -19,11 +17,12 @@ class FavSearchPage extends StatefulWidget {
class _FavSearchPageState extends State<FavSearchPage> {
final FavSearchController _favSearchCtr = Get.put(FavSearchController());
late ScrollController scrollController;
late int searchType;
@override
void initState() {
super.initState();
searchType = int.parse(Get.parameters['searchType']!);
scrollController = _favSearchCtr.scrollController;
scrollController.addListener(
() {
@ -100,7 +99,11 @@ class _FavSearchPageState extends State<FavSearchPage> {
} else {
return FavVideoCardH(
videoItem: _favSearchCtr.favList[index],
callFn: () => null,
searchType: searchType,
callFn: () => searchType != 1
? _favSearchCtr
.onCancelFav(_favSearchCtr.favList[index].id!)
: {},
);
}
},

View File

@ -89,8 +89,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
if (data['status']) {
return Obx(
() => SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
delegate: SliverChildBuilderDelegate((context, index) {
return VideoCardH(
videoItem: _hotController.videoList[index],
showPubdate: true,
@ -110,7 +109,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
} else {
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
fn: () {
setState(() {
_futureBuilderFuture =
_hotController.queryHotFeed('init');
});
},
);
}
} else {

View File

@ -10,8 +10,7 @@ class LiveController extends GetxController {
int count = 12;
int _currentPage = 1;
RxInt crossAxisCount = 2.obs;
RxList<LiveItemModel> liveList = [LiveItemModel()].obs;
bool isLoadingMore = false;
RxList<LiveItemModel> liveList = <LiveItemModel>[].obs;
bool flag = false;
OverlayEntry? popupDialog;
Box setting = GStrorage.setting;
@ -39,7 +38,6 @@ class LiveController extends GetxController {
}
_currentPage += 1;
}
isLoadingMore = false;
return res;
}

View File

@ -11,7 +11,6 @@ import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rcmd/index.dart';
import 'controller.dart';
import 'widgets/live_item.dart';
@ -45,8 +44,8 @@ class _LivePageState extends State<LivePage>
() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle('liveList', const Duration(seconds: 1), () {
_liveController.isLoadingMore = true;
EasyThrottle.throttle('liveList', const Duration(milliseconds: 200),
() {
_liveController.onLoad();
});
}
@ -108,24 +107,20 @@ class _LivePageState extends State<LivePage>
} else {
return HttpError(
errMsg: data['msg'],
fn: () => {},
fn: () {
setState(() {
_futureBuilderFuture =
_liveController.queryLiveList('init');
});
},
);
}
} else {
// 缓存数据
if (_liveController.liveList.length > 1) {
return contentGrid(
_liveController, _liveController.liveList);
}
// 骨架屏
else {
return contentGrid(_liveController, []);
}
return contentGrid(_liveController, []);
}
},
),
),
LoadingMore(ctr: _liveController)
],
),
),

View File

@ -44,7 +44,7 @@ class _RcmdPageState extends State<RcmdPage>
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle(
'my-throttler', const Duration(milliseconds: 500), () {
'my-throttler', const Duration(milliseconds: 200), () {
_rcmdController.isLoadingMore = true;
_rcmdController.onLoad();
});
@ -113,6 +113,7 @@ class _RcmdPageState extends State<RcmdPage>
errMsg: data['msg'],
fn: () {
setState(() {
_rcmdController.isLoadingMore = true;
_futureBuilderFuture =
_rcmdController.queryRcmdFeed('init');
});
@ -125,7 +126,6 @@ class _RcmdPageState extends State<RcmdPage>
},
),
),
LoadingMore(ctr: _rcmdController),
],
),
),
@ -188,33 +188,3 @@ class _RcmdPageState extends State<RcmdPage>
);
}
}
class LoadingMore extends StatelessWidget {
final dynamic ctr;
const LoadingMore({super.key, this.ctr});
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).padding.bottom + 80,
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: GestureDetector(
onTap: () {
if (ctr != null) {
ctr!.isLoadingMore = true;
ctr!.onLoad();
}
},
child: Center(
child: Text(
'点击加载更多 👇',
style: TextStyle(
color: Theme.of(context).colorScheme.outline, fontSize: 13),
),
),
),
),
);
}
}

View File

@ -187,9 +187,13 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
),
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
return CustomScrollView(
slivers: [
HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
)
],
);
}
} else {

View File

@ -105,7 +105,11 @@ class _SearchPanelState extends State<SearchPanel>
slivers: [
HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
fn: () {
setState(() {
_searchPanelController.onSearch();
});
},
),
],
);
@ -116,7 +120,11 @@ class _SearchPanelState extends State<SearchPanel>
slivers: [
HttpError(
errMsg: '没有相关数据',
fn: () => setState(() {}),
fn: () {
setState(() {
_searchPanelController.onSearch();
});
},
),
],
);

View File

@ -35,7 +35,7 @@ class SearchVideoPanel extends StatelessWidget {
padding: index == 0
? const EdgeInsets.only(top: 2)
: EdgeInsets.zero,
child: VideoCardH(videoItem: i),
child: VideoCardH(videoItem: i, showPubdate: true),
);
},
),
@ -70,7 +70,7 @@ class SearchVideoPanel extends StatelessWidget {
controller.selectedType.value = i['type'];
ctr!.order.value =
i['type'].toString().split('.').last;
SmartDialog.showLoading(msg: 'loooad');
SmartDialog.showLoading(msg: 'loading');
await ctr!.onRefresh();
SmartDialog.dismiss();
},
@ -202,7 +202,7 @@ class VideoPanelController extends GetxController {
Get.find<SearchPanelController>(
tag: 'video${searchPanelCtr.keyword!}');
ctr.duration.value = i['value'];
SmartDialog.showLoading(msg: 'loooad');
SmartDialog.showLoading(msg: 'loading');
await ctr.onRefresh();
SmartDialog.dismiss();
},

View File

@ -18,6 +18,7 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:share_plus/share_plus.dart';
import '../related/index.dart';
import 'widgets/group_panel.dart';
class VideoIntroController extends GetxController {
@ -478,11 +479,15 @@ class VideoIntroController extends GetxController {
// 重新获取视频资源
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
final ReleatedController releatedCtr =
Get.find<ReleatedController>(tag: heroTag);
videoDetailCtr.bvid = bvid;
videoDetailCtr.oid.value = aid;
videoDetailCtr.cid.value = cid;
videoDetailCtr.danmakuCid.value = cid;
videoDetailCtr.queryVideoUrl();
releatedCtr.bvid = bvid;
releatedCtr.queryRelatedVideo();
// 重新请求评论
try {
/// 未渲染回复组件时可能异常

View File

@ -1,14 +1,22 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/video.dart';
import '../../../../models/model_hot_video_item.dart';
class ReleatedController extends GetxController {
// 视频aid
String bvid = Get.parameters['bvid'] ?? "";
// 推荐视频列表
List relatedVideoList = [];
RxList relatedVideoList = <HotVideoItemModel>[].obs;
OverlayEntry? popupDialog;
Future<dynamic> queryRelatedVideo() => VideoHttp.relatedVideoList(bvid: bvid);
Future<dynamic> queryRelatedVideo() async {
return VideoHttp.relatedVideoList(bvid: bvid).then((value) {
if (value['status']) {
relatedVideoList.value = value['data'];
}
return value;
});
}
}

View File

@ -7,48 +7,73 @@ import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import './controller.dart';
class RelatedVideoPanel extends StatelessWidget {
final ReleatedController _releatedController =
Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
RelatedVideoPanel({super.key});
class RelatedVideoPanel extends StatefulWidget {
const RelatedVideoPanel({super.key});
@override
State<RelatedVideoPanel> createState() => _RelatedVideoPanelState();
}
class _RelatedVideoPanelState extends State<RelatedVideoPanel>
with AutomaticKeepAliveClientMixin {
late ReleatedController _releatedController;
late Future _futureBuilder;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_releatedController =
Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
_futureBuilder = _releatedController.queryRelatedVideo();
}
@override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder(
future: _releatedController.queryRelatedVideo(),
future: _futureBuilder,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox());
}
if (snapshot.data!['status']) {
if (snapshot.data!['status'] && snapshot.data != null) {
RxList relatedVideoList = _releatedController.relatedVideoList;
// 请求成功
return SliverList(
return Obx(
() => SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
if (index == snapshot.data['data'].length) {
return SizedBox(height: MediaQuery.of(context).padding.bottom);
} else {
return Material(
child: VideoCardH(
videoItem: snapshot.data['data'][index],
showPubdate: true,
longPress: () {
try {
_releatedController.popupDialog =
_createPopupDialog(snapshot.data['data'][index]);
Overlay.of(context)
.insert(_releatedController.popupDialog!);
} catch (err) {
return {};
}
},
longPressEnd: () {
_releatedController.popupDialog?.remove();
},
),
);
}
}, childCount: snapshot.data['data'].length + 1));
if (index == relatedVideoList.length) {
return SizedBox(
height: MediaQuery.of(context).padding.bottom);
} else {
return Material(
child: VideoCardH(
videoItem: relatedVideoList[index],
showPubdate: true,
longPress: () {
try {
_releatedController.popupDialog =
_createPopupDialog(_releatedController
.relatedVideoList[index]);
Overlay.of(context)
.insert(_releatedController.popupDialog!);
} catch (err) {
return {};
}
},
longPressEnd: () {
_releatedController.popupDialog?.remove();
},
),
);
}
}, childCount: relatedVideoList.length + 1),
),
);
} else {
// 请求错误
return HttpError(errMsg: '出错了', fn: () {});

View File

@ -28,7 +28,7 @@ class Utils {
}
static String numFormat(dynamic number) {
if (number == null){
if (number == null) {
return '0';
}
if (number is String) {
@ -63,6 +63,26 @@ class Utils {
}
}
// 完全相对时间显示
static String formatTimestampToRelativeTime(timeStamp) {
var difference = DateTime.now()
.difference(DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000));
if (difference.inDays > 365) {
return '${difference.inDays ~/ 365}年前';
} else if (difference.inDays > 30) {
return '${difference.inDays ~/ 30}个月前';
} else if (difference.inDays > 0) {
return '${difference.inDays}天前';
} else if (difference.inHours > 0) {
return '${difference.inHours}小时前';
} else if (difference.inMinutes > 0) {
return '${difference.inMinutes}分钟前';
} else {
return '刚刚';
}
}
// 时间显示刚刚x分钟前
static String dateFormat(timeStamp, {formatType = 'list'}) {
// 当前时间