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:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.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';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class LiveCard extends StatelessWidget {
|
class LiveCard extends StatelessWidget {
|
||||||
@ -96,7 +96,7 @@ class LiveContent extends StatelessWidget {
|
|||||||
liveItem.title,
|
liveItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||||
maxLines: Get.find<HomeController>().crossAxisCount,
|
maxLines: Get.find<RcmdController>().crossAxisCount,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/view.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/utils/utils.dart';
|
||||||
import 'package:pilipala/pages/home/controller.dart';
|
import 'package:pilipala/pages/home/controller.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.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: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
maxLines: Get.find<HomeController>().crossAxisCount,
|
maxLines: Get.find<RcmdController>().crossAxisCount,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@ -1,77 +1,42 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:pilipala/pages/hot/index.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/pages/rcmd/index.dart';
|
||||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
|
||||||
import 'package:pilipala/utils/storage.dart';
|
|
||||||
|
|
||||||
class HomeController extends GetxController {
|
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
int count = 12;
|
|
||||||
int _currentPage = 1;
|
|
||||||
int crossAxisCount = 2;
|
|
||||||
RxList<RecVideoItemModel> videoList = [RecVideoItemModel()].obs;
|
|
||||||
bool isLoadingMore = false;
|
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
OverlayEntry? popupDialog;
|
List tabs = [
|
||||||
Box recVideo = GStrorage.recVideo;
|
{'label': '直播', 'type': 'live'},
|
||||||
|
{'label': '推荐', 'type': 'rcm'},
|
||||||
|
{'label': '热门', 'type': 'hot'},
|
||||||
|
];
|
||||||
|
int initialIndex = 1;
|
||||||
|
late TabController tabController;
|
||||||
|
List ctrList = [
|
||||||
|
'',
|
||||||
|
Get.find<RcmdController>,
|
||||||
|
Get.find<HotController>,
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
if (recVideo.get('cacheList') != null &&
|
tabController = TabController(
|
||||||
recVideo.get('cacheList').isNotEmpty) {
|
initialIndex: initialIndex,
|
||||||
List<RecVideoItemModel> list = [];
|
length: tabs.length,
|
||||||
for (var i in recVideo.get('cacheList')) {
|
vsync: this,
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下拉刷新
|
void onRefresh() {
|
||||||
Future onRefresh() async {
|
int index = tabController.index;
|
||||||
queryRcmdFeed('onRefresh');
|
var ctr = ctrList[index];
|
||||||
|
ctr().onRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上拉加载
|
void animateToTop() {
|
||||||
Future onLoad() async {
|
int index = tabController.index;
|
||||||
queryRcmdFeed('onLoad');
|
var ctr = ctrList[index];
|
||||||
}
|
ctr().animateToTop();
|
||||||
|
|
||||||
// 返回顶部并刷新
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
import 'package:pilipala/pages/hot/index.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
import 'package:pilipala/pages/rcmd/index.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 './controller.dart';
|
import './controller.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'package:pilipala/pages/home/widgets/app_bar.dart';
|
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
@ -17,10 +13,10 @@ class HomePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage>
|
class _HomePageState extends State<HomePage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final HomeController _homeController = Get.put(HomeController());
|
||||||
Future? _futureBuilderFuture;
|
|
||||||
List videoList = [];
|
List videoList = [];
|
||||||
|
// late TabController? _tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -28,181 +24,93 @@ class _HomePageState extends State<HomePage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _homeController.queryRcmdFeed('init');
|
// _tabController = TabController(
|
||||||
_homeController.videoList.listen((value) {
|
// initialIndex: _homeController.initialIndex,
|
||||||
videoList = value;
|
// length: _homeController.tabs.length,
|
||||||
setState(() {});
|
// vsync: this,
|
||||||
});
|
// );
|
||||||
|
|
||||||
_homeController.scrollController.addListener(
|
|
||||||
() {
|
|
||||||
if (_homeController.scrollController.position.pixels >=
|
|
||||||
_homeController.scrollController.position.maxScrollExtent - 200) {
|
|
||||||
if (!_homeController.isLoadingMore) {
|
|
||||||
_homeController.isLoadingMore = true;
|
|
||||||
_homeController.onLoad();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
// body: NestedScrollView(
|
appBar: AppBar(
|
||||||
// headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
titleSpacing: 0,
|
||||||
// const HomeAppBar()
|
title: Padding(
|
||||||
// ],
|
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||||
body: RefreshIndicator(
|
child: Stack(
|
||||||
displacement: kToolbarHeight + MediaQuery.of(context).padding.top,
|
children: [
|
||||||
onRefresh: () async {
|
const Align(
|
||||||
return await _homeController.onRefresh();
|
alignment: Alignment.centerLeft,
|
||||||
},
|
child: Padding(
|
||||||
child: CustomScrollView(
|
padding: EdgeInsets.only(left: 8),
|
||||||
controller: _homeController.scrollController,
|
child: Text(
|
||||||
slivers: [
|
'PLPL',
|
||||||
const HomeAppBar(),
|
style: TextStyle(
|
||||||
// SliverPersistentHeader(
|
height: 2.8,
|
||||||
// delegate: MySliverPersistentHeaderDelegate(),
|
fontSize: 17,
|
||||||
// pinned: true,
|
fontWeight: FontWeight.bold,
|
||||||
// ),
|
letterSpacing: 1,
|
||||||
SliverPadding(
|
fontFamily: 'Jura-Bold',
|
||||||
// 单列布局 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, []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
Align(
|
||||||
const LoadingMore()
|
alignment: Alignment.center,
|
||||||
],
|
child: Theme(
|
||||||
),
|
data: ThemeData(
|
||||||
),
|
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
|
||||||
// ),
|
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
|
||||||
);
|
),
|
||||||
}
|
child: TabBar(
|
||||||
|
controller: _homeController.tabController,
|
||||||
OverlayEntry _createPopupDialog(videoItem) {
|
tabs: [
|
||||||
return OverlayEntry(
|
for (var i in _homeController.tabs) Tab(text: i['label']),
|
||||||
builder: (context) => AnimatedDialog(
|
],
|
||||||
child: OverlayPop(videoItem: videoItem),
|
isScrollable: true,
|
||||||
));
|
indicatorWeight: 0,
|
||||||
}
|
indicatorPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
|
||||||
Widget contentGrid(ctr, videoList) {
|
indicator: BoxDecoration(
|
||||||
return SliverGrid(
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
// 行间距
|
),
|
||||||
mainAxisSpacing: StyleString.cardSpace,
|
indicatorSize: TabBarIndicatorSize.tab,
|
||||||
// 列间距
|
labelColor:
|
||||||
crossAxisSpacing: StyleString.cardSpace,
|
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
// 列数
|
labelStyle: const TextStyle(fontSize: 13),
|
||||||
crossAxisCount: ctr.crossAxisCount,
|
dividerColor: Colors.transparent,
|
||||||
mainAxisExtent: MediaQuery.of(context).size.width /
|
unselectedLabelColor: Theme.of(context).colorScheme.outline,
|
||||||
ctr.crossAxisCount /
|
onTap: (value) => {_homeController.initialIndex = value},
|
||||||
StyleString.aspectRatio +
|
),
|
||||||
70,
|
),
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
Align(
|
||||||
(BuildContext context, int index) {
|
alignment: Alignment.centerRight,
|
||||||
return videoList!.isNotEmpty
|
child: Hero(
|
||||||
?
|
tag: 'searchTag',
|
||||||
// VideoCardV(videoItem: videoList![index])
|
child: IconButton(
|
||||||
VideoCardV(
|
onPressed: () {
|
||||||
videoItem: videoList[index],
|
Get.toNamed('/search');
|
||||||
longPress: () {
|
},
|
||||||
_homeController.popupDialog =
|
icon: const Icon(CupertinoIcons.search, size: 21),
|
||||||
_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),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
import 'package:pilipala/pages/hot/controller.dart';
|
import 'package:pilipala/pages/hot/controller.dart';
|
||||||
import 'package:pilipala/pages/home/widgets/app_bar.dart';
|
|
||||||
|
|
||||||
class HotPage extends StatefulWidget {
|
class HotPage extends StatefulWidget {
|
||||||
const HotPage({Key? key}) : super(key: key);
|
const HotPage({Key? key}) : super(key: key);
|
||||||
@ -52,7 +51,6 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: _hotController.scrollController,
|
controller: _hotController.scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
const HomeAppBar(),
|
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
class MainController extends GetxController {
|
class MainController extends GetxController {
|
||||||
List<Widget> pages = <Widget>[
|
List<Widget> pages = <Widget>[
|
||||||
const HomePage(),
|
const HomePage(),
|
||||||
const HotPage(),
|
// const HotPage(),
|
||||||
const DynamicsPage(),
|
const DynamicsPage(),
|
||||||
const MediaPage(),
|
const MediaPage(),
|
||||||
];
|
];
|
||||||
@ -25,13 +25,13 @@ class MainController extends GetxController {
|
|||||||
),
|
),
|
||||||
'label': "推荐",
|
'label': "推荐",
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
'icon': const Icon(
|
// 'icon': const Icon(
|
||||||
Icons.eco,
|
// Icons.eco,
|
||||||
size: 20,
|
// size: 20,
|
||||||
),
|
// ),
|
||||||
'label': "热门",
|
// 'label': "热门",
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
'icon': const Icon(
|
'icon': const Icon(
|
||||||
Icons.bolt,
|
Icons.bolt,
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/home/index.dart';
|
||||||
import 'package:pilipala/pages/hot/index.dart';
|
|
||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
|
|
||||||
class MainApp extends StatefulWidget {
|
class MainApp extends StatefulWidget {
|
||||||
@ -15,7 +14,6 @@ class MainApp extends StatefulWidget {
|
|||||||
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||||
final MainController _mainController = Get.put(MainController());
|
final MainController _mainController = Get.put(MainController());
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final HomeController _homeController = Get.put(HomeController());
|
||||||
final HotController _hotController = Get.put(HotController());
|
|
||||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
||||||
|
|
||||||
PageController? _pageController;
|
PageController? _pageController;
|
||||||
@ -70,22 +68,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
_homeController.flag = false;
|
_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 (currentPage is DynamicsPage) {
|
||||||
if (_dynamicController.flag) {
|
if (_dynamicController.flag) {
|
||||||
// 单击返回顶部 双击并刷新
|
// 单击返回顶部 双击并刷新
|
||||||
@ -131,7 +113,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
currentIndex: selectedIndex,
|
currentIndex: selectedIndex,
|
||||||
type: BottomNavigationBarType.shifting,
|
// type: BottomNavigationBarType.shifting,
|
||||||
selectedItemColor: Theme.of(context).colorScheme.primary,
|
selectedItemColor: Theme.of(context).colorScheme.primary,
|
||||||
unselectedItemColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
unselectedItemColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
selectedFontSize: 12.4,
|
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/http/user.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/home/index.dart';
|
||||||
import 'package:pilipala/pages/mine/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/cookie.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
|
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
|
||||||
@ -71,7 +72,7 @@ class WebviewController extends GetxController {
|
|||||||
userInfoCache.put('userInfoCache', result['data']);
|
userInfoCache.put('userInfoCache', result['data']);
|
||||||
Get.find<MineController>().userInfo.value = result['data'];
|
Get.find<MineController>().userInfo.value = result['data'];
|
||||||
Get.find<MineController>().onInit();
|
Get.find<MineController>().onInit();
|
||||||
Get.find<HomeController>().queryRcmdFeed('onRefresh');
|
Get.find<RcmdController>().queryRcmdFeed('onRefresh');
|
||||||
Get.back();
|
Get.back();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
40
pubspec.lock
40
pubspec.lock
@ -321,6 +321,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.2"
|
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:
|
extended_nested_scroll_view:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -598,6 +614,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
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:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1331,6 +1363,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
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:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -85,6 +85,7 @@ dependencies:
|
|||||||
ref: feature-custom
|
ref: feature-custom
|
||||||
path: package
|
path: package
|
||||||
custom_sliding_segmented_control: ^1.7.5
|
custom_sliding_segmented_control: ^1.7.5
|
||||||
|
loading_more_list: ^5.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -144,9 +145,12 @@ flutter:
|
|||||||
- family: fansCard
|
- family: fansCard
|
||||||
fonts:
|
fonts:
|
||||||
- asset: assets/fonts/fansCard.ttf
|
- asset: assets/fonts/fansCard.ttf
|
||||||
- family: ArchivoNarrow
|
- family: Jura-Bold
|
||||||
fonts:
|
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,
|
# For details regarding fonts from package dependencies,
|
||||||
|
|||||||
Reference in New Issue
Block a user