Merge branch 'design'
This commit is contained in:
79
lib/common/skeleton/media_bangumi.dart
Normal file
79
lib/common/skeleton/media_bangumi.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
|
||||||
|
import 'skeleton.dart';
|
||||||
|
|
||||||
|
class MediaBangumiSkeleton extends StatefulWidget {
|
||||||
|
const MediaBangumiSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MediaBangumiSkeleton> createState() => _MediaBangumiSkeletonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
||||||
|
return Skeleton(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 111,
|
||||||
|
height: 148,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
|
color: bgColor),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
SizedBox(
|
||||||
|
height: 148,
|
||||||
|
child: Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
width: 200,
|
||||||
|
height: 20,
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
width: 150,
|
||||||
|
height: 13,
|
||||||
|
margin: const EdgeInsets.only(bottom: 5),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
width: 150,
|
||||||
|
height: 13,
|
||||||
|
margin: const EdgeInsets.only(bottom: 5),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
width: 150,
|
||||||
|
height: 13,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
width: 90,
|
||||||
|
height: 35,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(20)),
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,103 +8,86 @@ class VideoCardHSkeleton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Skeleton(
|
return Skeleton(
|
||||||
child: Column(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.fromLTRB(
|
||||||
Padding(
|
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
|
||||||
padding: const EdgeInsets.fromLTRB(
|
child: LayoutBuilder(
|
||||||
StyleString.cardSpace, 7, StyleString.cardSpace, 7),
|
builder: (context, boxConstraints) {
|
||||||
child: LayoutBuilder(
|
double width =
|
||||||
builder: (context, boxConstraints) {
|
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
|
||||||
double width =
|
return SizedBox(
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
height: width / StyleString.aspectRatio,
|
||||||
return SizedBox(
|
child: Row(
|
||||||
height: width / StyleString.aspectRatio,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
child: Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
AspectRatio(
|
||||||
children: [
|
aspectRatio: StyleString.aspectRatio,
|
||||||
AspectRatio(
|
child: LayoutBuilder(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
builder: (context, boxConstraints) {
|
||||||
child: LayoutBuilder(
|
return Container(
|
||||||
builder: (context, boxConstraints) {
|
decoration: BoxDecoration(
|
||||||
return Container(
|
color:
|
||||||
decoration: BoxDecoration(
|
Theme.of(context).colorScheme.onInverseSurface,
|
||||||
color: Theme.of(context)
|
borderRadius:
|
||||||
.colorScheme
|
BorderRadius.circular(StyleString.imgRadius.x),
|
||||||
.onInverseSurface,
|
),
|
||||||
borderRadius: BorderRadius.circular(
|
);
|
||||||
StyleString.imgRadius.x),
|
},
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
// VideoContent(videoItem: videoItem)
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
width: 200,
|
||||||
|
height: 11,
|
||||||
|
margin: const EdgeInsets.only(bottom: 5),
|
||||||
),
|
),
|
||||||
),
|
Container(
|
||||||
// VideoContent(videoItem: videoItem)
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
Expanded(
|
width: 150,
|
||||||
child: Padding(
|
height: 13,
|
||||||
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
|
),
|
||||||
child: Column(
|
const Spacer(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
width: 100,
|
||||||
|
height: 13,
|
||||||
|
margin: const EdgeInsets.only(bottom: 5),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onInverseSurface,
|
.onInverseSurface,
|
||||||
width: 200,
|
width: 40,
|
||||||
height: 11,
|
height: 13,
|
||||||
margin: const EdgeInsets.only(bottom: 5),
|
margin: const EdgeInsets.only(right: 8),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onInverseSurface,
|
.onInverseSurface,
|
||||||
width: 150,
|
width: 40,
|
||||||
height: 13,
|
height: 13,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
Container(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface,
|
|
||||||
width: 100,
|
|
||||||
height: 13,
|
|
||||||
margin: const EdgeInsets.only(bottom: 5),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface,
|
|
||||||
width: 40,
|
|
||||||
height: 13,
|
|
||||||
margin: const EdgeInsets.only(right: 8),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface,
|
|
||||||
width: 40,
|
|
||||||
height: 13,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
)),
|
],
|
||||||
],
|
),
|
||||||
),
|
)),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
Divider(
|
),
|
||||||
height: 1,
|
|
||||||
indent: 8,
|
|
||||||
endIndent: 12,
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,63 +56,53 @@ class VideoCardH extends StatelessWidget {
|
|||||||
SmartDialog.showToast(err.toString());
|
SmartDialog.showToast(err.toString());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.fromLTRB(
|
||||||
Padding(
|
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
|
||||||
padding: const EdgeInsets.fromLTRB(
|
child: LayoutBuilder(
|
||||||
StyleString.safeSpace, 6, StyleString.safeSpace, 6),
|
builder: (context, boxConstraints) {
|
||||||
child: LayoutBuilder(
|
double width =
|
||||||
builder: (context, boxConstraints) {
|
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
|
||||||
double width =
|
return SizedBox(
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
|
height: width / StyleString.aspectRatio,
|
||||||
return SizedBox(
|
child: Row(
|
||||||
height: width / StyleString.aspectRatio,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
child: Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
AspectRatio(
|
||||||
children: [
|
aspectRatio: StyleString.aspectRatio,
|
||||||
AspectRatio(
|
child: LayoutBuilder(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
builder: (context, boxConstraints) {
|
||||||
child: LayoutBuilder(
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
builder: (context, boxConstraints) {
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
return Stack(
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
children: [
|
||||||
return Stack(
|
Hero(
|
||||||
children: [
|
tag: heroTag,
|
||||||
Hero(
|
child: NetworkImgLayer(
|
||||||
tag: heroTag,
|
src: videoItem.pic,
|
||||||
child: NetworkImgLayer(
|
width: maxWidth,
|
||||||
src: videoItem.pic,
|
height: maxHeight,
|
||||||
width: maxWidth,
|
),
|
||||||
height: maxHeight,
|
),
|
||||||
),
|
pBadge(Utils.timeFormat(videoItem.duration!),
|
||||||
),
|
context, null, 6.0, 6.0, null,
|
||||||
pBadge(Utils.timeFormat(videoItem.duration!),
|
type: 'gray'),
|
||||||
context, null, 6.0, 6.0, null,
|
if (videoItem.rcmdReason != null &&
|
||||||
type: 'gray'),
|
videoItem.rcmdReason.content != '')
|
||||||
if (videoItem.rcmdReason != null &&
|
pBadge(videoItem.rcmdReason.content, context,
|
||||||
videoItem.rcmdReason.content != '')
|
6.0, 6.0, null, null),
|
||||||
pBadge(videoItem.rcmdReason.content,
|
],
|
||||||
context, 6.0, 6.0, null, null),
|
);
|
||||||
],
|
},
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
VideoContent(videoItem: videoItem)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
VideoContent(videoItem: videoItem)
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
// Divider(
|
},
|
||||||
// height: 1,
|
),
|
||||||
// indent: 8,
|
|
||||||
// endIndent: 12,
|
|
||||||
// color: Theme.of(context).dividerColor.withOpacity(0.08),
|
|
||||||
// )
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -167,6 +167,10 @@ class Api {
|
|||||||
// 热搜
|
// 热搜
|
||||||
static const String hotSearchList =
|
static const String hotSearchList =
|
||||||
'https://s.search.bilibili.com/main/hotword';
|
'https://s.search.bilibili.com/main/hotword';
|
||||||
|
|
||||||
|
// 默认搜索词
|
||||||
|
static const String searchDefault = '/x/web-interface/wbi/search/default';
|
||||||
|
|
||||||
// 搜索关键词
|
// 搜索关键词
|
||||||
static const String serachSuggest =
|
static const String serachSuggest =
|
||||||
'https://s.search.bilibili.com/main/suggest';
|
'https://s.search.bilibili.com/main/suggest';
|
||||||
|
|||||||
3
lib/pages/bangumi/controller.dart
Normal file
3
lib/pages/bangumi/controller.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class BangumiController extends GetxController {}
|
||||||
4
lib/pages/bangumi/index.dart
Normal file
4
lib/pages/bangumi/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library bangumi_panel;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
19
lib/pages/bangumi/view.dart
Normal file
19
lib/pages/bangumi/view.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BangumiPage extends StatefulWidget {
|
||||||
|
const BangumiPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BangumiPage> createState() => _BangumiPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BangumiPageState extends State<BangumiPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Text('还在开发中'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,16 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
|
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
import 'package:pilipala/pages/mine/index.dart';
|
import 'package:pilipala/pages/mine/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -36,18 +40,27 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||||
|
ScrollController scrollController = _dynamicsController.scrollController;
|
||||||
_dynamicsController.scrollController.addListener(
|
StreamController<bool> mainStream =
|
||||||
|
Get.find<MainController>().bottomBarStream;
|
||||||
|
scrollController.addListener(
|
||||||
() async {
|
() async {
|
||||||
if (_dynamicsController.scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
_dynamicsController.scrollController.position.maxScrollExtent -
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
200) {
|
|
||||||
if (!_isLoadingMore) {
|
if (!_isLoadingMore) {
|
||||||
_isLoadingMore = true;
|
_isLoadingMore = true;
|
||||||
await _dynamicsController.queryFollowDynamic(type: 'onLoad');
|
await _dynamicsController.queryFollowDynamic(type: 'onLoad');
|
||||||
_isLoadingMore = false;
|
_isLoadingMore = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ScrollDirection direction =
|
||||||
|
scrollController.position.userScrollDirection;
|
||||||
|
if (direction == ScrollDirection.forward) {
|
||||||
|
mainStream.add(true);
|
||||||
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
|
mainStream.add(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
import 'package:pilipala/pages/bangumi/index.dart';
|
||||||
import 'package:pilipala/pages/hot/index.dart';
|
import 'package:pilipala/pages/hot/index.dart';
|
||||||
import 'package:pilipala/pages/live/index.dart';
|
import 'package:pilipala/pages/live/index.dart';
|
||||||
import 'package:pilipala/pages/rcmd/index.dart';
|
import 'package:pilipala/pages/rcmd/index.dart';
|
||||||
@ -31,6 +33,14 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
'label': '热门',
|
'label': '热门',
|
||||||
'type': 'hot'
|
'type': 'hot'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(
|
||||||
|
Icons.play_circle_outlined,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
'label': '番剧',
|
||||||
|
'type': 'bangumi'
|
||||||
|
},
|
||||||
];
|
];
|
||||||
int initialIndex = 1;
|
int initialIndex = 1;
|
||||||
late TabController tabController;
|
late TabController tabController;
|
||||||
@ -38,7 +48,9 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
Get.find<LiveController>,
|
Get.find<LiveController>,
|
||||||
Get.find<RcmdController>,
|
Get.find<RcmdController>,
|
||||||
Get.find<HotController>,
|
Get.find<HotController>,
|
||||||
|
Get.find<BangumiController>,
|
||||||
];
|
];
|
||||||
|
RxString defaultSearch = '输入关键词搜索'.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -48,6 +60,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
length: tabs.length,
|
length: tabs.length,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
searchDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRefresh() {
|
void onRefresh() {
|
||||||
@ -61,4 +74,11 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
var ctr = ctrList[index];
|
var ctr = ctrList[index];
|
||||||
ctr().animateToTop();
|
ctr().animateToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void searchDefault() async {
|
||||||
|
var res = await Request().get(Api.searchDefault);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
defaultSearch.value = res.data['data']['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
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:hive/hive.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/pages/bangumi/index.dart';
|
||||||
import 'package:pilipala/pages/hot/index.dart';
|
import 'package:pilipala/pages/hot/index.dart';
|
||||||
import 'package:pilipala/pages/live/index.dart';
|
import 'package:pilipala/pages/live/index.dart';
|
||||||
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
|
import 'package:pilipala/pages/mine/index.dart';
|
||||||
import 'package:pilipala/pages/rcmd/index.dart';
|
import 'package:pilipala/pages/rcmd/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
@ -18,7 +24,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final HomeController _homeController = Get.put(HomeController());
|
||||||
List videoList = [];
|
List videoList = [];
|
||||||
// late TabController? _tabController;
|
Stream<bool> stream = Get.find<MainController>().bottomBarStream.stream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -27,103 +33,231 @@ class _HomePageState extends State<HomePage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
extendBody: true,
|
||||||
titleSpacing: 0,
|
extendBodyBehindAppBar: true,
|
||||||
title: Padding(
|
appBar: AppBar(toolbarHeight: 0, elevation: 0),
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 0),
|
body: Column(
|
||||||
child: Stack(
|
children: [
|
||||||
children: [
|
CustomAppBar(stream: stream, ctr: _homeController),
|
||||||
const Align(
|
Container(
|
||||||
alignment: Alignment.centerLeft,
|
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4),
|
||||||
child: Padding(
|
child: Stack(
|
||||||
padding: EdgeInsets.only(left: 8),
|
children: [
|
||||||
child: Text(
|
Align(
|
||||||
'PLPL',
|
alignment: Alignment.center,
|
||||||
style: TextStyle(
|
child: Theme(
|
||||||
height: 2.8,
|
data: ThemeData(
|
||||||
fontSize: 17,
|
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
|
||||||
fontWeight: FontWeight.bold,
|
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
|
||||||
letterSpacing: 1,
|
|
||||||
fontFamily: 'Jura-Bold',
|
|
||||||
),
|
),
|
||||||
),
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.only(top: 2),
|
||||||
),
|
child: TabBar(
|
||||||
Align(
|
controller: _homeController.tabController,
|
||||||
alignment: Alignment.center,
|
tabs: [
|
||||||
child: Theme(
|
for (var i in _homeController.tabs)
|
||||||
data: ThemeData(
|
// Tab(text: i['label'])
|
||||||
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
|
Padding(
|
||||||
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
|
padding: const EdgeInsets.symmetric(
|
||||||
),
|
horizontal: 0, vertical: 11),
|
||||||
child: Padding(
|
child: Row(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
children: [
|
||||||
child: TabBar(
|
i['icon'],
|
||||||
controller: _homeController.tabController,
|
const SizedBox(width: 4),
|
||||||
tabs: [
|
Text(i['label'])
|
||||||
for (var i in _homeController.tabs)
|
],
|
||||||
// Tab(text: i['label'])
|
),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 0, vertical: 11),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
i['icon'],
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(i['label'])
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
isScrollable: true,
|
||||||
isScrollable: true,
|
indicatorWeight: 0,
|
||||||
indicatorWeight: 0,
|
indicatorPadding: const EdgeInsets.symmetric(
|
||||||
indicatorPadding: const EdgeInsets.symmetric(
|
horizontal: 4, vertical: 5),
|
||||||
horizontal: 4, vertical: 5),
|
indicator: BoxDecoration(
|
||||||
indicator: BoxDecoration(
|
color: Theme.of(context)
|
||||||
color: Theme.of(context)
|
.colorScheme
|
||||||
.colorScheme
|
.primaryContainer
|
||||||
.primaryContainer
|
.withOpacity(0.8),
|
||||||
.withOpacity(0.8),
|
borderRadius:
|
||||||
borderRadius:
|
const BorderRadius.all(Radius.circular(20)),
|
||||||
const BorderRadius.all(Radius.circular(20)),
|
),
|
||||||
|
indicatorSize: TabBarIndicatorSize.tab,
|
||||||
|
labelColor: Theme.of(context).colorScheme.primary,
|
||||||
|
labelStyle: const TextStyle(fontSize: 13),
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
unselectedLabelColor:
|
||||||
|
Theme.of(context).colorScheme.outline,
|
||||||
|
onTap: (value) =>
|
||||||
|
{feedBack(), _homeController.initialIndex = value},
|
||||||
),
|
),
|
||||||
indicatorSize: TabBarIndicatorSize.tab,
|
|
||||||
labelColor: Theme.of(context).colorScheme.primary,
|
|
||||||
labelStyle: const TextStyle(fontSize: 13),
|
|
||||||
dividerColor: Colors.transparent,
|
|
||||||
unselectedLabelColor:
|
|
||||||
Theme.of(context).colorScheme.outline,
|
|
||||||
onTap: (value) =>
|
|
||||||
{feedBack(), _homeController.initialIndex = value},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
Align(
|
),
|
||||||
alignment: Alignment.centerRight,
|
),
|
||||||
child: Hero(
|
Expanded(
|
||||||
tag: 'searchTag',
|
child: TabBarView(
|
||||||
child: IconButton(
|
controller: _homeController.tabController,
|
||||||
onPressed: () {
|
children: const [
|
||||||
feedBack();
|
LivePage(),
|
||||||
Get.toNamed('/search');
|
RcmdPage(),
|
||||||
},
|
HotPage(),
|
||||||
icon: const Icon(CupertinoIcons.search, size: 21),
|
BangumiPage(),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
body: TabBarView(
|
|
||||||
controller: _homeController.tabController,
|
|
||||||
children: const [
|
|
||||||
LivePage(),
|
|
||||||
RcmdPage(),
|
|
||||||
HotPage(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final double height;
|
||||||
|
final Stream<bool>? stream;
|
||||||
|
final ctr;
|
||||||
|
|
||||||
|
const CustomAppBar({
|
||||||
|
super.key,
|
||||||
|
this.height = kToolbarHeight,
|
||||||
|
this.stream,
|
||||||
|
this.ctr,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => Size.fromHeight(height);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Box user = GStrorage.user;
|
||||||
|
|
||||||
|
return StreamBuilder(
|
||||||
|
stream: stream,
|
||||||
|
initialData: true,
|
||||||
|
builder: (context, AsyncSnapshot snapshot) {
|
||||||
|
return ClipRect(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: snapshot.data ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
curve: Curves.linear,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
height: snapshot.data ? 94 : MediaQuery.of(context).padding.top,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 12,
|
||||||
|
right: 12,
|
||||||
|
bottom: 4,
|
||||||
|
top: MediaQuery.of(context).padding.top,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'PLPL',
|
||||||
|
style: TextStyle(
|
||||||
|
height: 2.8,
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Jura-Bold',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Hero(
|
||||||
|
tag: 'searchWrap',
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed('/search', parameters: {
|
||||||
|
'hintText': ctr.defaultSearch.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 250,
|
||||||
|
height: 45,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
padding: const EdgeInsets.only(left: 12, right: 22),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(25)),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onInverseSurface,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.search_outlined,
|
||||||
|
size: 23,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 7),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
ctr.defaultSearch.value,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
if (user.get(UserBoxKey.userLogin) ?? false) ...[
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
feedBack();
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const SizedBox(
|
||||||
|
height: 450,
|
||||||
|
child: MinePage(),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
isScrollControlled: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
type: 'avatar',
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
src: user.get(UserBoxKey.userFace),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
] else ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
feedBack();
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const SizedBox(
|
||||||
|
height: 450,
|
||||||
|
child: MinePage(),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
isScrollControlled: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(CupertinoIcons.person, size: 22),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||||
@ -6,6 +9,7 @@ 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/main/index.dart';
|
||||||
|
|
||||||
class HotPage extends StatefulWidget {
|
class HotPage extends StatefulWidget {
|
||||||
const HotPage({Key? key}) : super(key: key);
|
const HotPage({Key? key}) : super(key: key);
|
||||||
@ -26,15 +30,26 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _hotController.queryHotFeed('init');
|
_futureBuilderFuture = _hotController.queryHotFeed('init');
|
||||||
_hotController.scrollController.addListener(
|
ScrollController scrollController = _hotController.scrollController;
|
||||||
|
StreamController<bool> mainStream =
|
||||||
|
Get.find<MainController>().bottomBarStream;
|
||||||
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
if (_hotController.scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
_hotController.scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
if (!_hotController.isLoadingMore) {
|
if (!_hotController.isLoadingMore) {
|
||||||
_hotController.isLoadingMore = true;
|
_hotController.isLoadingMore = true;
|
||||||
_hotController.onLoad();
|
_hotController.onLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ScrollDirection direction =
|
||||||
|
scrollController.position.userScrollDirection;
|
||||||
|
if (direction == ScrollDirection.forward) {
|
||||||
|
mainStream.add(true);
|
||||||
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
|
mainStream.add(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.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/skeleton/video_card_v.dart';
|
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||||
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'widgets/live_item.dart';
|
import 'widgets/live_item.dart';
|
||||||
@ -22,15 +26,26 @@ class _LivePageState extends State<LivePage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_liveController.scrollController.addListener(
|
ScrollController scrollController = _liveController.scrollController;
|
||||||
|
StreamController<bool> mainStream =
|
||||||
|
Get.find<MainController>().bottomBarStream;
|
||||||
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
if (_liveController.scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
_liveController.scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
if (!_liveController.isLoadingMore) {
|
if (!_liveController.isLoadingMore) {
|
||||||
_liveController.isLoadingMore = true;
|
_liveController.isLoadingMore = true;
|
||||||
_liveController.onLoad();
|
_liveController.onLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ScrollDirection direction =
|
||||||
|
scrollController.position.userScrollDirection;
|
||||||
|
if (direction == ScrollDirection.forward) {
|
||||||
|
mainStream.add(true);
|
||||||
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
|
mainStream.add(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
@ -33,4 +35,6 @@ class MainController extends GetxController {
|
|||||||
'label': "媒体库",
|
'label': "媒体库",
|
||||||
}
|
}
|
||||||
].obs;
|
].obs;
|
||||||
|
final StreamController<bool> bottomBarStream =
|
||||||
|
StreamController<bool>.broadcast();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -64,7 +66,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
||||||
_homeController.onRefresh();
|
_homeController.onRefresh();
|
||||||
} else {
|
} else {
|
||||||
await Future.delayed(const Duration(microseconds: 300));
|
|
||||||
_homeController.animateToTop();
|
_homeController.animateToTop();
|
||||||
}
|
}
|
||||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
@ -80,7 +81,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
||||||
_dynamicController.onRefresh();
|
_dynamicController.onRefresh();
|
||||||
} else {
|
} else {
|
||||||
await Future.delayed(const Duration(microseconds: 300));
|
|
||||||
_dynamicController.animateToTop();
|
_dynamicController.animateToTop();
|
||||||
}
|
}
|
||||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
@ -105,6 +105,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
localCache.put('sheetHeight', sheetHeight);
|
localCache.put('sheetHeight', sheetHeight);
|
||||||
localCache.put('statusBarHeight', statusBarHeight);
|
localCache.put('statusBarHeight', statusBarHeight);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
extendBody: true,
|
||||||
body: FadeTransition(
|
body: FadeTransition(
|
||||||
opacity: _fadeAnimation!,
|
opacity: _fadeAnimation!,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
@ -129,21 +130,33 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: StreamBuilder(
|
||||||
currentIndex: selectedIndex,
|
stream: _mainController.bottomBarStream.stream,
|
||||||
// type: BottomNavigationBarType.shifting,
|
initialData: true,
|
||||||
selectedItemColor: Theme.of(context).colorScheme.primary,
|
builder: (context, AsyncSnapshot snapshot) {
|
||||||
unselectedItemColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
return AnimatedSlide(
|
||||||
selectedFontSize: 12.4,
|
curve: Curves.linear,
|
||||||
onTap: (value) => setIndex(value),
|
duration: const Duration(milliseconds: 300),
|
||||||
items: [
|
offset: Offset(0, snapshot.data ? 0 : 1),
|
||||||
..._mainController.navigationBars.map((e) {
|
child: BottomNavigationBar(
|
||||||
return BottomNavigationBarItem(
|
currentIndex: selectedIndex,
|
||||||
icon: e['icon'],
|
// type: BottomNavigationBarType.shifting,
|
||||||
label: e['label'],
|
selectedItemColor: Theme.of(context).colorScheme.primary,
|
||||||
);
|
unselectedItemColor:
|
||||||
}).toList(),
|
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
],
|
selectedFontSize: 12.4,
|
||||||
|
onTap: (value) => setIndex(value),
|
||||||
|
items: [
|
||||||
|
..._mainController.navigationBars.map((e) {
|
||||||
|
return BottomNavigationBarItem(
|
||||||
|
icon: e['icon'],
|
||||||
|
label: e['label'],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.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/skeleton/video_card_v.dart';
|
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||||
@ -6,6 +9,7 @@ import 'package:pilipala/common/widgets/animated_dialog.dart';
|
|||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_v.dart';
|
import 'package:pilipala/common/widgets/video_card_v.dart';
|
||||||
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
@ -26,15 +30,26 @@ class _RcmdPageState extends State<RcmdPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_rcmdController.scrollController.addListener(
|
ScrollController scrollController = _rcmdController.scrollController;
|
||||||
|
StreamController<bool> mainStream =
|
||||||
|
Get.find<MainController>().bottomBarStream;
|
||||||
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
if (_rcmdController.scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
_rcmdController.scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
if (!_rcmdController.isLoadingMore) {
|
if (!_rcmdController.isLoadingMore) {
|
||||||
_rcmdController.isLoadingMore = true;
|
_rcmdController.isLoadingMore = true;
|
||||||
_rcmdController.onLoad();
|
_rcmdController.onLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ScrollDirection direction =
|
||||||
|
scrollController.position.userScrollDirection;
|
||||||
|
if (direction == ScrollDirection.forward) {
|
||||||
|
mainStream.add(true);
|
||||||
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
|
mainStream.add(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ class SSearchController extends GetxController {
|
|||||||
RxList<SearchSuggestItem> searchSuggestList = [SearchSuggestItem()].obs;
|
RxList<SearchSuggestItem> searchSuggestList = [SearchSuggestItem()].obs;
|
||||||
final _debouncer =
|
final _debouncer =
|
||||||
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
|
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
|
||||||
|
String hintText = '搜索';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -33,7 +34,13 @@ class SSearchController extends GetxController {
|
|||||||
}
|
}
|
||||||
// 其他页面跳转过来
|
// 其他页面跳转过来
|
||||||
if (Get.parameters.keys.isNotEmpty) {
|
if (Get.parameters.keys.isNotEmpty) {
|
||||||
onClickKeyword(Get.parameters['keyword']!);
|
if (Get.parameters['keyword'] != null) {
|
||||||
|
onClickKeyword(Get.parameters['keyword']!);
|
||||||
|
}
|
||||||
|
if (Get.parameters['hintText'] != null) {
|
||||||
|
hintText = Get.parameters['hintText']!;
|
||||||
|
searchKeyWord.value = hintText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
historyCacheList = histiryWord.get('cacheList') ?? [];
|
historyCacheList = histiryWord.get('cacheList') ?? [];
|
||||||
historyList.value = historyCacheList;
|
historyList.value = historyCacheList;
|
||||||
|
|||||||
@ -17,6 +17,13 @@ class SearchPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _SearchPageState extends State<SearchPage> with RouteAware {
|
class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||||
final SSearchController _searchController = Get.put(SSearchController());
|
final SSearchController _searchController = Get.put(SSearchController());
|
||||||
|
late Future? _futureBuilderFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = _searchController.queryHotSearchList();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// 返回当前页面时
|
// 返回当前页面时
|
||||||
@ -53,26 +60,29 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 10)
|
const SizedBox(width: 10)
|
||||||
],
|
],
|
||||||
title: Obx(
|
title: Hero(
|
||||||
() => TextField(
|
tag: 'searchWrap',
|
||||||
autofocus: true,
|
child: Obx(
|
||||||
focusNode: _searchController.searchFocusNode,
|
() => TextField(
|
||||||
controller: _searchController.controller.value,
|
autofocus: true,
|
||||||
textInputAction: TextInputAction.search,
|
focusNode: _searchController.searchFocusNode,
|
||||||
onChanged: (value) => _searchController.onChange(value),
|
controller: _searchController.controller.value,
|
||||||
decoration: InputDecoration(
|
textInputAction: TextInputAction.search,
|
||||||
hintText: '搜索',
|
onChanged: (value) => _searchController.onChange(value),
|
||||||
border: InputBorder.none,
|
decoration: InputDecoration(
|
||||||
suffixIcon: IconButton(
|
hintText: _searchController.hintText,
|
||||||
icon: Icon(
|
border: InputBorder.none,
|
||||||
Icons.clear,
|
suffixIcon: IconButton(
|
||||||
size: 22,
|
icon: Icon(
|
||||||
color: Theme.of(context).colorScheme.outline,
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _searchController.onClear(),
|
||||||
),
|
),
|
||||||
onPressed: () => _searchController.onClear(),
|
|
||||||
),
|
),
|
||||||
|
onSubmitted: (String value) => _searchController.submit(),
|
||||||
),
|
),
|
||||||
onSubmitted: (String value) => _searchController.submit(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -159,7 +169,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
final double width = boxConstraints.maxWidth;
|
final double width = boxConstraints.maxWidth;
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: _searchController.queryHotSearchList(),
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
Map data = snapshot.data as Map;
|
Map data = snapshot.data as Map;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
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/media_bangumi.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
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/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
@ -104,7 +105,18 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
addRepaintBoundaries: false,
|
addRepaintBoundaries: false,
|
||||||
itemCount: 15,
|
itemCount: 15,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return const VideoCardHSkeleton();
|
switch (widget.searchType) {
|
||||||
|
case SearchType.video:
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
case SearchType.media_bangumi:
|
||||||
|
return const MediaBangumiSkeleton();
|
||||||
|
case SearchType.bili_user:
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
case SearchType.live_room:
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
default:
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import 'package:pilipala/utils/utils.dart';
|
|||||||
Widget searchLivePanel(BuildContext context, ctr, list) {
|
Widget searchLivePanel(BuildContext context, ctr, list) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: StyleString.cardSpace, right: StyleString.cardSpace),
|
left: StyleString.safeSpace, right: StyleString.safeSpace),
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
primary: false,
|
primary: false,
|
||||||
controller: ctr!.scrollController,
|
controller: ctr!.scrollController,
|
||||||
@ -16,73 +16,82 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
|
|||||||
mainAxisSpacing: StyleString.cardSpace + 3,
|
mainAxisSpacing: StyleString.cardSpace + 3,
|
||||||
mainAxisExtent:
|
mainAxisExtent:
|
||||||
MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio +
|
MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio +
|
||||||
65,
|
60,
|
||||||
),
|
),
|
||||||
itemCount: list.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var i = list![index];
|
return LiveItem(liveItem: list![index]);
|
||||||
return Card(
|
|
||||||
elevation: 0,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {},
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: StyleString.imgRadius,
|
|
||||||
topRight: StyleString.imgRadius,
|
|
||||||
bottomLeft: StyleString.imgRadius,
|
|
||||||
bottomRight: StyleString.imgRadius,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: StyleString.aspectRatio,
|
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Hero(
|
|
||||||
tag: Utils.makeHeroTag(i.roomid),
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: i.cover,
|
|
||||||
type: 'emote',
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: 1,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
child: LiveStat(
|
|
||||||
online: i.online,
|
|
||||||
cateName: i.cateName,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
LiveContent(liveItem: i)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LiveItem extends StatelessWidget {
|
||||||
|
final dynamic liveItem;
|
||||||
|
const LiveItem({Key? key, required this.liveItem}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: StyleString.mdRadius,
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: StyleString.imgRadius,
|
||||||
|
topRight: StyleString.imgRadius,
|
||||||
|
bottomLeft: StyleString.imgRadius,
|
||||||
|
bottomRight: StyleString.imgRadius,
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: StyleString.aspectRatio,
|
||||||
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Hero(
|
||||||
|
tag: Utils.makeHeroTag(liveItem.roomid),
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: liveItem.cover,
|
||||||
|
type: 'emote',
|
||||||
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: 1,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: LiveStat(
|
||||||
|
online: liveItem.online,
|
||||||
|
cateName: liveItem.cateName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
LiveContent(liveItem: liveItem)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class LiveContent extends StatelessWidget {
|
class LiveContent extends StatelessWidget {
|
||||||
final dynamic liveItem;
|
final dynamic liveItem;
|
||||||
const LiveContent({Key? key, required this.liveItem}) : super(key: key);
|
const LiveContent({Key? key, required this.liveItem}) : super(key: key);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
@ -28,7 +29,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
|||||||
// });
|
// });
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
Reference in New Issue
Block a user