Merge branch 'design'

This commit is contained in:
guozhigq
2023-08-06 21:28:23 +08:00
20 changed files with 691 additions and 340 deletions

View 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,
),
),
],
),
),
),
],
),
),
);
}
}

View File

@ -8,103 +8,86 @@ class VideoCardHSkeleton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Skeleton(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.cardSpace, 7, StyleString.cardSpace, 7),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
borderRadius: BorderRadius.circular(
StyleString.imgRadius.x),
),
);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.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),
),
),
// VideoContent(videoItem: videoItem)
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 150,
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: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 150,
width: 40,
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),
)
],
)
],
),
)),
],
),
);
},
),
),
);
}

View File

@ -56,63 +56,53 @@ class VideoCardH extends StatelessWidget {
SmartDialog.showToast(err.toString());
}
},
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 6, StyleString.safeSpace, 6),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
),
pBadge(Utils.timeFormat(videoItem.duration!),
context, null, 6.0, 6.0, null,
type: 'gray'),
if (videoItem.rcmdReason != null &&
videoItem.rcmdReason.content != '')
pBadge(videoItem.rcmdReason.content,
context, 6.0, 6.0, null, null),
],
);
},
),
),
VideoContent(videoItem: videoItem)
],
child: Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
),
pBadge(Utils.timeFormat(videoItem.duration!),
context, null, 6.0, 6.0, null,
type: 'gray'),
if (videoItem.rcmdReason != null &&
videoItem.rcmdReason.content != '')
pBadge(videoItem.rcmdReason.content, context,
6.0, 6.0, null, null),
],
);
},
),
),
);
},
),
),
// Divider(
// height: 1,
// indent: 8,
// endIndent: 12,
// color: Theme.of(context).dividerColor.withOpacity(0.08),
// )
],
VideoContent(videoItem: videoItem)
],
),
);
},
),
),
),
);

View File

@ -167,6 +167,10 @@ class Api {
// 热搜
static const String hotSearchList =
'https://s.search.bilibili.com/main/hotword';
// 默认搜索词
static const String searchDefault = '/x/web-interface/wbi/search/default';
// 搜索关键词
static const String serachSuggest =
'https://s.search.bilibili.com/main/suggest';

View File

@ -0,0 +1,3 @@
import 'package:get/get.dart';
class BangumiController extends GetxController {}

View File

@ -0,0 +1,4 @@
library bangumi_panel;
export './controller.dart';
export './view.dart';

View 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('还在开发中'),
),
);
}
}

View File

@ -1,12 +1,16 @@
import 'dart:async';
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/skeleton/dynamic_card.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.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/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
@ -36,18 +40,27 @@ class _DynamicsPageState extends State<DynamicsPage>
void initState() {
super.initState();
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
_dynamicsController.scrollController.addListener(
ScrollController scrollController = _dynamicsController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
scrollController.addListener(
() async {
if (_dynamicsController.scrollController.position.pixels >=
_dynamicsController.scrollController.position.maxScrollExtent -
200) {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
if (!_isLoadingMore) {
_isLoadingMore = true;
await _dynamicsController.queryFollowDynamic(type: 'onLoad');
_isLoadingMore = false;
}
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
},
);
}

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.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/live/index.dart';
import 'package:pilipala/pages/rcmd/index.dart';
@ -31,6 +33,14 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
'label': '热门',
'type': 'hot'
},
{
'icon': const Icon(
Icons.play_circle_outlined,
size: 15,
),
'label': '番剧',
'type': 'bangumi'
},
];
int initialIndex = 1;
late TabController tabController;
@ -38,7 +48,9 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
Get.find<LiveController>,
Get.find<RcmdController>,
Get.find<HotController>,
Get.find<BangumiController>,
];
RxString defaultSearch = '输入关键词搜索'.obs;
@override
void onInit() {
@ -48,6 +60,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
length: tabs.length,
vsync: this,
);
searchDefault();
}
void onRefresh() {
@ -61,4 +74,11 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
var ctr = ctrList[index];
ctr().animateToTop();
}
void searchDefault() async {
var res = await Request().get(Api.searchDefault);
if (res.data['code'] == 0) {
defaultSearch.value = res.data['data']['name'];
}
}
}

View File

@ -1,10 +1,16 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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/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/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
import './controller.dart';
class HomePage extends StatefulWidget {
@ -18,7 +24,7 @@ class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
final HomeController _homeController = Get.put(HomeController());
List videoList = [];
// late TabController? _tabController;
Stream<bool> stream = Get.find<MainController>().bottomBarStream.stream;
@override
bool get wantKeepAlive => true;
@ -27,103 +33,231 @@ class _HomePageState extends State<HomePage>
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
titleSpacing: 0,
title: Padding(
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 0),
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',
extendBody: true,
extendBodyBehindAppBar: true,
appBar: AppBar(toolbarHeight: 0, elevation: 0),
body: Column(
children: [
CustomAppBar(stream: stream, ctr: _homeController),
Container(
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4),
child: Stack(
children: [
Align(
alignment: Alignment.center,
child: Theme(
data: ThemeData(
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
),
),
),
),
Align(
alignment: Alignment.center,
child: Theme(
data: ThemeData(
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
),
child: Padding(
padding: const EdgeInsets.only(top: 4),
child: TabBar(
controller: _homeController.tabController,
tabs: [
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'])
],
child: Padding(
padding: const EdgeInsets.only(top: 2),
child: TabBar(
controller: _homeController.tabController,
tabs: [
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,
indicatorWeight: 0,
indicatorPadding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 5),
indicator: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.8),
borderRadius:
const BorderRadius.all(Radius.circular(20)),
],
isScrollable: true,
indicatorWeight: 0,
indicatorPadding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 5),
indicator: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.8),
borderRadius:
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(
tag: 'searchTag',
child: IconButton(
onPressed: () {
feedBack();
Get.toNamed('/search');
},
icon: const Icon(CupertinoIcons.search, size: 21),
),
),
),
],
],
),
),
Expanded(
child: TabBarView(
controller: _homeController.tabController,
children: const [
LivePage(),
RcmdPage(),
HotPage(),
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),
)
],
],
),
),
),
),
);
},
);
}
}

View File

@ -1,4 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/animated_dialog.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/video_card_h.dart';
import 'package:pilipala/pages/hot/controller.dart';
import 'package:pilipala/pages/main/index.dart';
class HotPage extends StatefulWidget {
const HotPage({Key? key}) : super(key: key);
@ -26,15 +30,26 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
void initState() {
super.initState();
_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 >=
_hotController.scrollController.position.maxScrollExtent - 200) {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
if (!_hotController.isLoadingMore) {
_hotController.isLoadingMore = true;
_hotController.onLoad();
}
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
},
);
}

View File

@ -1,10 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/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/pages/main/index.dart';
import 'controller.dart';
import 'widgets/live_item.dart';
@ -22,15 +26,26 @@ class _LivePageState extends State<LivePage> {
@override
void initState() {
super.initState();
_liveController.scrollController.addListener(
ScrollController scrollController = _liveController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
scrollController.addListener(
() {
if (_liveController.scrollController.position.pixels >=
_liveController.scrollController.position.maxScrollExtent - 200) {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
if (!_liveController.isLoadingMore) {
_liveController.isLoadingMore = true;
_liveController.onLoad();
}
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
},
);
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/pages/dynamics/index.dart';
@ -33,4 +35,6 @@ class MainController extends GetxController {
'label': "媒体库",
}
].obs;
final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast();
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
@ -64,7 +66,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
_homeController.onRefresh();
} else {
await Future.delayed(const Duration(microseconds: 300));
_homeController.animateToTop();
}
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
@ -80,7 +81,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
_dynamicController.onRefresh();
} else {
await Future.delayed(const Duration(microseconds: 300));
_dynamicController.animateToTop();
}
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
@ -105,6 +105,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
localCache.put('sheetHeight', sheetHeight);
localCache.put('statusBarHeight', statusBarHeight);
return Scaffold(
extendBody: true,
body: FadeTransition(
opacity: _fadeAnimation!,
child: SlideTransition(
@ -129,21 +130,33 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
),
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: selectedIndex,
// type: BottomNavigationBarType.shifting,
selectedItemColor: Theme.of(context).colorScheme.primary,
unselectedItemColor: 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(),
],
bottomNavigationBar: StreamBuilder(
stream: _mainController.bottomBarStream.stream,
initialData: true,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedSlide(
curve: Curves.linear,
duration: const Duration(milliseconds: 300),
offset: Offset(0, snapshot.data ? 0 : 1),
child: BottomNavigationBar(
currentIndex: selectedIndex,
// type: BottomNavigationBarType.shifting,
selectedItemColor: Theme.of(context).colorScheme.primary,
unselectedItemColor:
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(),
],
),
);
},
),
);
}

View File

@ -1,4 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/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/overlay_pop.dart';
import 'package:pilipala/common/widgets/video_card_v.dart';
import 'package:pilipala/pages/main/index.dart';
import 'controller.dart';
@ -26,15 +30,26 @@ class _RcmdPageState extends State<RcmdPage>
@override
void initState() {
super.initState();
_rcmdController.scrollController.addListener(
ScrollController scrollController = _rcmdController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
scrollController.addListener(
() {
if (_rcmdController.scrollController.position.pixels >=
_rcmdController.scrollController.position.maxScrollExtent - 200) {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
if (!_rcmdController.isLoadingMore) {
_rcmdController.isLoadingMore = true;
_rcmdController.onLoad();
}
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
},
);
}

View File

@ -19,6 +19,7 @@ class SSearchController extends GetxController {
RxList<SearchSuggestItem> searchSuggestList = [SearchSuggestItem()].obs;
final _debouncer =
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
String hintText = '搜索';
@override
void onInit() {
@ -33,7 +34,13 @@ class SSearchController extends GetxController {
}
// 其他页面跳转过来
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') ?? [];
historyList.value = historyCacheList;

View File

@ -17,6 +17,13 @@ class SearchPage extends StatefulWidget {
class _SearchPageState extends State<SearchPage> with RouteAware {
final SSearchController _searchController = Get.put(SSearchController());
late Future? _futureBuilderFuture;
@override
void initState() {
super.initState();
_futureBuilderFuture = _searchController.queryHotSearchList();
}
@override
// 返回当前页面时
@ -53,26 +60,29 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
),
const SizedBox(width: 10)
],
title: Obx(
() => TextField(
autofocus: true,
focusNode: _searchController.searchFocusNode,
controller: _searchController.controller.value,
textInputAction: TextInputAction.search,
onChanged: (value) => _searchController.onChange(value),
decoration: InputDecoration(
hintText: '搜索',
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
Icons.clear,
size: 22,
color: Theme.of(context).colorScheme.outline,
title: Hero(
tag: 'searchWrap',
child: Obx(
() => TextField(
autofocus: true,
focusNode: _searchController.searchFocusNode,
controller: _searchController.controller.value,
textInputAction: TextInputAction.search,
onChanged: (value) => _searchController.onChange(value),
decoration: InputDecoration(
hintText: _searchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
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) {
final double width = boxConstraints.maxWidth;
return FutureBuilder(
future: _searchController.queryHotSearchList(),
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.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/widgets/http_error.dart';
import 'package:pilipala/models/common/search_type.dart';
@ -104,7 +105,18 @@ class _SearchPanelState extends State<SearchPanel>
addRepaintBoundaries: false,
itemCount: 15,
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();
}
},
);
}

View File

@ -6,7 +6,7 @@ import 'package:pilipala/utils/utils.dart';
Widget searchLivePanel(BuildContext context, ctr, list) {
return Padding(
padding: const EdgeInsets.only(
left: StyleString.cardSpace, right: StyleString.cardSpace),
left: StyleString.safeSpace, right: StyleString.safeSpace),
child: GridView.builder(
primary: false,
controller: ctr!.scrollController,
@ -16,73 +16,82 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
mainAxisSpacing: StyleString.cardSpace + 3,
mainAxisExtent:
MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio +
65,
60,
),
itemCount: list.length,
itemBuilder: (context, index) {
var i = 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)
],
),
),
);
return LiveItem(liveItem: list![index]);
},
),
);
}
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 {
final dynamic liveItem;
const LiveContent({Key? key, required this.liveItem}) : super(key: key);

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/search.dart';
@ -28,7 +29,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
// });
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [