mod: 最热合并到首页
This commit is contained in:
Binary file not shown.
BIN
assets/fonts/HarmonyOS_Sans_SC_Regular.ttf
Executable file
BIN
assets/fonts/HarmonyOS_Sans_SC_Regular.ttf
Executable file
Binary file not shown.
BIN
assets/fonts/Jura-Bold.ttf
Normal file
BIN
assets/fonts/Jura-Bold.ttf
Normal file
Binary file not shown.
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/controller.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class LiveCard extends StatelessWidget {
|
||||
@ -96,7 +96,7 @@ class LiveContent extends StatelessWidget {
|
||||
liveItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
maxLines: Get.find<HomeController>().crossAxisCount,
|
||||
maxLines: Get.find<RcmdController>().crossAxisCount,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/pages/rcmd/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pilipala/pages/home/controller.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
@ -123,7 +124,7 @@ class VideoContent extends StatelessWidget {
|
||||
// fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
fontSize: 13,
|
||||
),
|
||||
maxLines: Get.find<HomeController>().crossAxisCount,
|
||||
maxLines: Get.find<RcmdController>().crossAxisCount,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(
|
||||
|
@ -1,77 +1,42 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/pages/hot/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/index.dart';
|
||||
|
||||
class HomeController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
int count = 12;
|
||||
int _currentPage = 1;
|
||||
int crossAxisCount = 2;
|
||||
RxList<RecVideoItemModel> videoList = [RecVideoItemModel()].obs;
|
||||
bool isLoadingMore = false;
|
||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
bool flag = false;
|
||||
OverlayEntry? popupDialog;
|
||||
Box recVideo = GStrorage.recVideo;
|
||||
List tabs = [
|
||||
{'label': '直播', 'type': 'live'},
|
||||
{'label': '推荐', 'type': 'rcm'},
|
||||
{'label': '热门', 'type': 'hot'},
|
||||
];
|
||||
int initialIndex = 1;
|
||||
late TabController tabController;
|
||||
List ctrList = [
|
||||
'',
|
||||
Get.find<RcmdController>,
|
||||
Get.find<HotController>,
|
||||
];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (recVideo.get('cacheList') != null &&
|
||||
recVideo.get('cacheList').isNotEmpty) {
|
||||
List<RecVideoItemModel> list = [];
|
||||
for (var i in recVideo.get('cacheList')) {
|
||||
list.add(i);
|
||||
}
|
||||
videoList.value = list;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取推荐
|
||||
Future queryRcmdFeed(type) async {
|
||||
var res = await VideoHttp.rcmdVideoList(
|
||||
ps: count,
|
||||
freshIdx: _currentPage,
|
||||
tabController = TabController(
|
||||
initialIndex: initialIndex,
|
||||
length: tabs.length,
|
||||
vsync: this,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
if (videoList.length > 1) {
|
||||
videoList.addAll(res['data']);
|
||||
} else {
|
||||
videoList.value = res['data'];
|
||||
}
|
||||
} else if (type == 'onRefresh') {
|
||||
videoList.insertAll(0, res['data']);
|
||||
} else if (type == 'onLoad') {
|
||||
videoList.addAll(res['data']);
|
||||
}
|
||||
recVideo.put('cacheList', res['data']);
|
||||
_currentPage += 1;
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future onRefresh() async {
|
||||
queryRcmdFeed('onRefresh');
|
||||
void onRefresh() {
|
||||
int index = tabController.index;
|
||||
var ctr = ctrList[index];
|
||||
ctr().onRefresh();
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
queryRcmdFeed('onLoad');
|
||||
}
|
||||
|
||||
// 返回顶部并刷新
|
||||
void animateToTop() async {
|
||||
if (scrollController.offset >=
|
||||
MediaQuery.of(Get.context!).size.height * 5) {
|
||||
scrollController.jumpTo(0);
|
||||
} else {
|
||||
await scrollController.animateTo(0,
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
void animateToTop() {
|
||||
int index = tabController.index;
|
||||
var ctr = ctrList[index];
|
||||
ctr().animateToTop();
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,9 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_v.dart';
|
||||
import 'package:pilipala/pages/hot/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/index.dart';
|
||||
import './controller.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/pages/home/widgets/app_bar.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
@ -17,10 +13,10 @@ class HomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
Future? _futureBuilderFuture;
|
||||
List videoList = [];
|
||||
// late TabController? _tabController;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@ -28,181 +24,93 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _homeController.queryRcmdFeed('init');
|
||||
_homeController.videoList.listen((value) {
|
||||
videoList = value;
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
_homeController.scrollController.addListener(
|
||||
() {
|
||||
if (_homeController.scrollController.position.pixels >=
|
||||
_homeController.scrollController.position.maxScrollExtent - 200) {
|
||||
if (!_homeController.isLoadingMore) {
|
||||
_homeController.isLoadingMore = true;
|
||||
_homeController.onLoad();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
// _tabController = TabController(
|
||||
// initialIndex: _homeController.initialIndex,
|
||||
// length: _homeController.tabs.length,
|
||||
// vsync: this,
|
||||
// );
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
// body: NestedScrollView(
|
||||
// headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
// const HomeAppBar()
|
||||
// ],
|
||||
body: RefreshIndicator(
|
||||
displacement: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||
onRefresh: () async {
|
||||
return await _homeController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _homeController.scrollController,
|
||||
slivers: [
|
||||
const HomeAppBar(),
|
||||
// SliverPersistentHeader(
|
||||
// delegate: MySliverPersistentHeaderDelegate(),
|
||||
// pinned: true,
|
||||
// ),
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding: _homeController.crossAxisCount == 1
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.fromLTRB(
|
||||
StyleString.cardSpace, 0, StyleString.cardSpace, 8),
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(
|
||||
_homeController, _homeController.videoList));
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 缓存数据
|
||||
if (_homeController.videoList.length > 1) {
|
||||
return contentGrid(
|
||||
_homeController, _homeController.videoList);
|
||||
}
|
||||
// 骨架屏
|
||||
else {
|
||||
return contentGrid(_homeController, []);
|
||||
}
|
||||
}
|
||||
},
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
child: Stack(
|
||||
children: [
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
'PLPL',
|
||||
style: TextStyle(
|
||||
height: 2.8,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1,
|
||||
fontFamily: 'Jura-Bold',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
],
|
||||
),
|
||||
),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
|
||||
OverlayEntry _createPopupDialog(videoItem) {
|
||||
return OverlayEntry(
|
||||
builder: (context) => AnimatedDialog(
|
||||
child: OverlayPop(videoItem: videoItem),
|
||||
));
|
||||
}
|
||||
|
||||
Widget contentGrid(ctr, videoList) {
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 列数
|
||||
crossAxisCount: ctr.crossAxisCount,
|
||||
mainAxisExtent: MediaQuery.of(context).size.width /
|
||||
ctr.crossAxisCount /
|
||||
StyleString.aspectRatio +
|
||||
70,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return videoList!.isNotEmpty
|
||||
?
|
||||
// VideoCardV(videoItem: videoList![index])
|
||||
VideoCardV(
|
||||
videoItem: videoList[index],
|
||||
longPress: () {
|
||||
_homeController.popupDialog =
|
||||
_createPopupDialog(videoList[index]);
|
||||
Overlay.of(context).insert(_homeController.popupDialog!);
|
||||
},
|
||||
longPressEnd: () {
|
||||
_homeController.popupDialog?.remove();
|
||||
},
|
||||
)
|
||||
: const VideoCardVSkeleton();
|
||||
},
|
||||
childCount: videoList!.isNotEmpty ? videoList!.length : 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
)),
|
||||
child: const Text(
|
||||
'我是一个SliverPersistentHeader',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => 55.0;
|
||||
|
||||
@override
|
||||
double get minExtent => 55.0;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) =>
|
||||
true; // 如果内容需要更新,设置为true
|
||||
}
|
||||
|
||||
// loading more
|
||||
class LoadingMore extends StatelessWidget {
|
||||
const LoadingMore({super.key});
|
||||
|
||||
@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: Center(
|
||||
child: Text(
|
||||
'加载中...',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline, fontSize: 13),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
|
||||
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _homeController.tabController,
|
||||
tabs: [
|
||||
for (var i in _homeController.tabs) Tab(text: i['label']),
|
||||
],
|
||||
isScrollable: true,
|
||||
indicatorWeight: 0,
|
||||
indicatorPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
|
||||
indicator: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
labelColor:
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
labelStyle: const TextStyle(fontSize: 13),
|
||||
dividerColor: Colors.transparent,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.outline,
|
||||
onTap: (value) => {_homeController.initialIndex = value},
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Hero(
|
||||
tag: 'searchTag',
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
Get.toNamed('/search');
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.search, size: 21),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _homeController.tabController,
|
||||
children: const [
|
||||
SizedBox(),
|
||||
RcmdPage(),
|
||||
HotPage(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'package:pilipala/pages/hot/controller.dart';
|
||||
import 'package:pilipala/pages/home/widgets/app_bar.dart';
|
||||
|
||||
class HotPage extends StatefulWidget {
|
||||
const HotPage({Key? key}) : super(key: key);
|
||||
@ -52,7 +51,6 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
child: CustomScrollView(
|
||||
controller: _hotController.scrollController,
|
||||
slivers: [
|
||||
const HomeAppBar(),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
|
@ -13,7 +13,7 @@ import 'package:pilipala/utils/storage.dart';
|
||||
class MainController extends GetxController {
|
||||
List<Widget> pages = <Widget>[
|
||||
const HomePage(),
|
||||
const HotPage(),
|
||||
// const HotPage(),
|
||||
const DynamicsPage(),
|
||||
const MediaPage(),
|
||||
];
|
||||
@ -25,13 +25,13 @@ class MainController extends GetxController {
|
||||
),
|
||||
'label': "推荐",
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.eco,
|
||||
size: 20,
|
||||
),
|
||||
'label': "热门",
|
||||
},
|
||||
// {
|
||||
// 'icon': const Icon(
|
||||
// Icons.eco,
|
||||
// size: 20,
|
||||
// ),
|
||||
// 'label': "热门",
|
||||
// },
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.bolt,
|
||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/pages/dynamics/index.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/hot/index.dart';
|
||||
import './controller.dart';
|
||||
|
||||
class MainApp extends StatefulWidget {
|
||||
@ -15,7 +14,6 @@ class MainApp extends StatefulWidget {
|
||||
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
final MainController _mainController = Get.put(MainController());
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
final HotController _hotController = Get.put(HotController());
|
||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
||||
|
||||
PageController? _pageController;
|
||||
@ -70,22 +68,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
_homeController.flag = false;
|
||||
}
|
||||
|
||||
if (currentPage is HotPage) {
|
||||
if (_hotController.flag) {
|
||||
// 单击返回顶部 双击并刷新
|
||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
||||
_hotController.onRefresh();
|
||||
} else {
|
||||
await Future.delayed(const Duration(microseconds: 300));
|
||||
_hotController.animateToTop();
|
||||
}
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
_hotController.flag = true;
|
||||
} else {
|
||||
_hotController.flag = false;
|
||||
}
|
||||
|
||||
if (currentPage is DynamicsPage) {
|
||||
if (_dynamicController.flag) {
|
||||
// 单击返回顶部 双击并刷新
|
||||
@ -131,7 +113,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: selectedIndex,
|
||||
type: BottomNavigationBarType.shifting,
|
||||
// type: BottomNavigationBarType.shifting,
|
||||
selectedItemColor: Theme.of(context).colorScheme.primary,
|
||||
unselectedItemColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
selectedFontSize: 12.4,
|
||||
|
77
lib/pages/rcmd/controller.dart
Normal file
77
lib/pages/rcmd/controller.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class RcmdController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
int count = 12;
|
||||
int _currentPage = 1;
|
||||
int crossAxisCount = 2;
|
||||
RxList<RecVideoItemModel> videoList = [RecVideoItemModel()].obs;
|
||||
bool isLoadingMore = false;
|
||||
bool flag = false;
|
||||
OverlayEntry? popupDialog;
|
||||
Box recVideo = GStrorage.recVideo;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (recVideo.get('cacheList') != null &&
|
||||
recVideo.get('cacheList').isNotEmpty) {
|
||||
List<RecVideoItemModel> list = [];
|
||||
for (var i in recVideo.get('cacheList')) {
|
||||
list.add(i);
|
||||
}
|
||||
videoList.value = list;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取推荐
|
||||
Future queryRcmdFeed(type) async {
|
||||
var res = await VideoHttp.rcmdVideoList(
|
||||
ps: count,
|
||||
freshIdx: _currentPage,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
if (videoList.length > 1) {
|
||||
videoList.addAll(res['data']);
|
||||
} else {
|
||||
videoList.value = res['data'];
|
||||
}
|
||||
} else if (type == 'onRefresh') {
|
||||
videoList.insertAll(0, res['data']);
|
||||
} else if (type == 'onLoad') {
|
||||
videoList.addAll(res['data']);
|
||||
}
|
||||
recVideo.put('cacheList', res['data']);
|
||||
_currentPage += 1;
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future onRefresh() async {
|
||||
queryRcmdFeed('onRefresh');
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
queryRcmdFeed('onLoad');
|
||||
}
|
||||
|
||||
// 返回顶部并刷新
|
||||
void animateToTop() async {
|
||||
if (scrollController.offset >=
|
||||
MediaQuery.of(Get.context!).size.height * 5) {
|
||||
scrollController.jumpTo(0);
|
||||
} else {
|
||||
await scrollController.animateTo(0,
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
}
|
4
lib/pages/rcmd/index.dart
Normal file
4
lib/pages/rcmd/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library recm_panel;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
153
lib/pages/rcmd/view.dart
Normal file
153
lib/pages/rcmd/view.dart
Normal file
@ -0,0 +1,153 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_v.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class RcmdPage extends StatefulWidget {
|
||||
const RcmdPage({super.key});
|
||||
|
||||
@override
|
||||
State<RcmdPage> createState() => _RcmdPageState();
|
||||
}
|
||||
|
||||
class _RcmdPageState extends State<RcmdPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final RcmdController _rcmdController = Get.put(RcmdController());
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_rcmdController.scrollController.addListener(
|
||||
() {
|
||||
if (_rcmdController.scrollController.position.pixels >=
|
||||
_rcmdController.scrollController.position.maxScrollExtent - 200) {
|
||||
if (!_rcmdController.isLoadingMore) {
|
||||
_rcmdController.isLoadingMore = true;
|
||||
_rcmdController.onLoad();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
return await _rcmdController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _rcmdController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding: _rcmdController.crossAxisCount == 1
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.fromLTRB(
|
||||
StyleString.cardSpace, 0, StyleString.cardSpace, 8),
|
||||
sliver: FutureBuilder(
|
||||
future: _rcmdController.queryRcmdFeed('init'),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(
|
||||
_rcmdController, _rcmdController.videoList));
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 缓存数据
|
||||
if (_rcmdController.videoList.length > 1) {
|
||||
return contentGrid(
|
||||
_rcmdController, _rcmdController.videoList);
|
||||
}
|
||||
// 骨架屏
|
||||
else {
|
||||
return contentGrid(_rcmdController, []);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OverlayEntry _createPopupDialog(videoItem) {
|
||||
return OverlayEntry(
|
||||
builder: (context) => AnimatedDialog(
|
||||
child: OverlayPop(videoItem: videoItem),
|
||||
));
|
||||
}
|
||||
|
||||
Widget contentGrid(ctr, videoList) {
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 列数
|
||||
crossAxisCount: ctr.crossAxisCount,
|
||||
mainAxisExtent:
|
||||
Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 70,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return videoList!.isNotEmpty
|
||||
?
|
||||
// VideoCardV(videoItem: videoList![index])
|
||||
VideoCardV(
|
||||
videoItem: videoList[index],
|
||||
longPress: () {
|
||||
_rcmdController.popupDialog =
|
||||
_createPopupDialog(videoList[index]);
|
||||
Overlay.of(context).insert(_rcmdController.popupDialog!);
|
||||
},
|
||||
longPressEnd: () {
|
||||
_rcmdController.popupDialog?.remove();
|
||||
},
|
||||
)
|
||||
: const VideoCardVSkeleton();
|
||||
},
|
||||
childCount: videoList!.isNotEmpty ? videoList!.length : 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingMore extends StatelessWidget {
|
||||
const LoadingMore({super.key});
|
||||
|
||||
@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: Center(
|
||||
child: Text(
|
||||
'加载中...',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import 'package:pilipala/http/init.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/mine/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/controller.dart';
|
||||
import 'package:pilipala/utils/cookie.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
|
||||
@ -71,7 +72,7 @@ class WebviewController extends GetxController {
|
||||
userInfoCache.put('userInfoCache', result['data']);
|
||||
Get.find<MineController>().userInfo.value = result['data'];
|
||||
Get.find<MineController>().onInit();
|
||||
Get.find<HomeController>().queryRcmdFeed('onRefresh');
|
||||
Get.find<RcmdController>().queryRcmdFeed('onRefresh');
|
||||
Get.back();
|
||||
}
|
||||
} catch (e) {
|
||||
|
40
pubspec.lock
40
pubspec.lock
@ -321,6 +321,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.2"
|
||||
extended_list:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_list
|
||||
sha256: "7eb3c21cd8b7710b0092cb0606807cd24614e5832e65f370e5dee226d317de44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
extended_list_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_list_library
|
||||
sha256: cb424a04464e89bd6737f9ae025029bd8e913c7bf37101ad10c2defe0238d842
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
extended_nested_scroll_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -598,6 +614,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
loading_more_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: loading_more_list
|
||||
sha256: aa680edc81cf024c394dccfa7ba1db701a5efb23ec1b8c657308428ce9da11d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
loading_more_list_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: loading_more_list_library
|
||||
sha256: "31348925a98748ffe04f661e4b47df37103fabad39442064fcf59148a5fee2dd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1331,6 +1363,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
waterfall_flow:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: waterfall_flow
|
||||
sha256: "84486ac34f51977176990ada50b44d83be24726814fa03c22c1e10e3791cc3f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -85,6 +85,7 @@ dependencies:
|
||||
ref: feature-custom
|
||||
path: package
|
||||
custom_sliding_segmented_control: ^1.7.5
|
||||
loading_more_list: ^5.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -144,9 +145,12 @@ flutter:
|
||||
- family: fansCard
|
||||
fonts:
|
||||
- asset: assets/fonts/fansCard.ttf
|
||||
- family: ArchivoNarrow
|
||||
- family: Jura-Bold
|
||||
fonts:
|
||||
- asset: assets/fonts/ArchivoNarrow-BoldItalic.ttf
|
||||
- asset: assets/fonts/Jura-Bold.ttf
|
||||
- family: HarmonyOS
|
||||
fonts:
|
||||
- asset: assets/fonts/HarmonyOS_Sans_SC_Regular.ttf
|
||||
|
||||
|
||||
# For details regarding fonts from package dependencies,
|
||||
|
Reference in New Issue
Block a user