feat: 首页番剧&样式
This commit is contained in:
@ -1,3 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/bangumi.dart';
|
||||
import 'package:pilipala/models/bangumi/list.dart';
|
||||
|
||||
class BangumiController extends GetxController {}
|
||||
class BangumiController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
RxList<BangumiListItemModel> bangumiList = [BangumiListItemModel()].obs;
|
||||
int _currentPage = 1;
|
||||
bool isLoadingMore = true;
|
||||
|
||||
Future queryBangumiListFeed({type = 'init'}) async {
|
||||
var result = await BangumiHttp.bangumiList(page: _currentPage);
|
||||
if (result['status']) {
|
||||
if (type == 'init') {
|
||||
bangumiList.value = result['data'].list;
|
||||
} else {
|
||||
bangumiList.addAll(result['data'].list);
|
||||
}
|
||||
_currentPage += 1;
|
||||
} else {}
|
||||
isLoadingMore = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
queryBangumiListFeed(type: 'onLoad');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,15 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/view.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
import 'widgets/bangumu_card_v.dart';
|
||||
|
||||
class BangumiPage extends StatefulWidget {
|
||||
const BangumiPage({super.key});
|
||||
@ -7,12 +18,106 @@ class BangumiPage extends StatefulWidget {
|
||||
State<BangumiPage> createState() => _BangumiPageState();
|
||||
}
|
||||
|
||||
class _BangumiPageState extends State<BangumiPage> {
|
||||
class _BangumiPageState extends State<BangumiPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final BangumiController _bangumidController = Get.put(BangumiController());
|
||||
late Future? _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ScrollController scrollController = _bangumidController.scrollController;
|
||||
StreamController<bool> mainStream =
|
||||
Get.find<MainController>().bottomBarStream;
|
||||
_futureBuilderFuture = _bangumidController.queryBangumiListFeed();
|
||||
scrollController.addListener(
|
||||
() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
if (!_bangumidController.isLoadingMore) {
|
||||
await _bangumidController.onLoad();
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
_bangumidController.isLoadingMore = true;
|
||||
}
|
||||
}
|
||||
|
||||
final ScrollDirection direction =
|
||||
scrollController.position.userScrollDirection;
|
||||
if (direction == ScrollDirection.forward) {
|
||||
mainStream.add(true);
|
||||
} else if (direction == ScrollDirection.reverse) {
|
||||
mainStream.add(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('还在开发中'),
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(StyleString.imgRadius),
|
||||
),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
return Future.delayed(const Duration(seconds: 2));
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _bangumidController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.zero,
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(_bangumidController,
|
||||
_bangumidController.bangumiList));
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return contentGrid(_bangumidController, []);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget contentGrid(ctr, bangumiList) {
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace - 2,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 列数
|
||||
crossAxisCount: 3,
|
||||
mainAxisExtent: Get.size.width / 3 / 0.65 + 45,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return bangumiList!.isNotEmpty
|
||||
? BangumiCardV(bangumiItem: bangumiList[index])
|
||||
: const SizedBox();
|
||||
},
|
||||
childCount: bangumiList!.isNotEmpty ? bangumiList!.length : 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
199
lib/pages/bangumi/widgets/bangumu_card_v.dart
Normal file
199
lib/pages/bangumi/widgets/bangumu_card_v.dart
Normal file
@ -0,0 +1,199 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/bangumi/info.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
|
||||
// 视频卡片 - 垂直布局
|
||||
class BangumiCardV extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
final bangumiItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
|
||||
const BangumiCardV({
|
||||
Key? key,
|
||||
required this.bangumiItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(bangumiItem.mediaId);
|
||||
return Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: StyleString.mdRadius,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
child: GestureDetector(
|
||||
// onLongPress: () {
|
||||
// if (longPress != null) {
|
||||
// longPress!();
|
||||
// }
|
||||
// },
|
||||
// onLongPressEnd: (details) {
|
||||
// if (longPressEnd != null) {
|
||||
// longPressEnd!();
|
||||
// }
|
||||
// },
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
int seasonId = bangumiItem.seasonId;
|
||||
SmartDialog.showLoading(msg: '获取中...');
|
||||
var res = await SearchHttp.bangumiInfo(seasonId: seasonId);
|
||||
SmartDialog.dismiss().then((value) {
|
||||
if (res['status']) {
|
||||
EpisodeItem episode = res['data'].episodes.first;
|
||||
String bvid = episode.bvid!;
|
||||
int cid = episode.cid!;
|
||||
String pic = episode.cover!;
|
||||
String heroTag = Utils.makeHeroTag(cid);
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid&seasonId=$seasonId',
|
||||
arguments: {
|
||||
'pic': pic,
|
||||
'heroTag': heroTag,
|
||||
'videoType': SearchType.media_bangumi,
|
||||
'bangumiItem': res['data'],
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: StyleString.imgRadius,
|
||||
topRight: StyleString.imgRadius,
|
||||
bottomLeft: StyleString.imgRadius,
|
||||
bottomRight: StyleString.imgRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.65,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: bangumiItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
if (bangumiItem.badge != null)
|
||||
pBadge(bangumiItem.badge, context, 6, 6, null, null),
|
||||
pBadge(bangumiItem.order, context, null, null, 6, 6,
|
||||
type: 'gray'),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
BangumiContent(bangumiItem: bangumiItem)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BangumiContent extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
final bangumiItem;
|
||||
const BangumiContent({Key? key, required this.bangumiItem}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
// 多列
|
||||
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
|
||||
// 单列
|
||||
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
bangumiItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LayoutBuilder(builder:
|
||||
(BuildContext context, BoxConstraints constraints) {
|
||||
return SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
child: Text(
|
||||
bangumiItem.indexShow,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
// SizedBox(
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// child: IconButton(
|
||||
// tooltip: '稍后再看',
|
||||
// style: ButtonStyle(
|
||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// ),
|
||||
// onPressed: () async {
|
||||
// var res =
|
||||
// await UserHttp.toViewLater(bvid: videoItem.bvid);
|
||||
// SmartDialog.showToast(res['msg']);
|
||||
// },
|
||||
// icon: Icon(
|
||||
// Icons.more_vert_outlined,
|
||||
// color: Theme.of(context).colorScheme.outline,
|
||||
// size: 14,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// StatView(
|
||||
// theme: 'black',
|
||||
// view: videoItem.stat.view,
|
||||
// ),
|
||||
// const SizedBox(width: 6),
|
||||
// StatDanMu(
|
||||
// theme: 'black',
|
||||
// danmu: videoItem.stat.danmaku,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user