mod: merge main branch

This commit is contained in:
guozhigq
2023-08-14 10:23:14 +08:00
21 changed files with 581 additions and 415 deletions

View File

@ -14,7 +14,7 @@ class VideoCardHSkeleton extends StatelessWidget {
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(

View File

@ -57,7 +57,7 @@ class VideoCardH extends StatelessWidget {
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
@ -83,10 +83,10 @@ class VideoCardH extends StatelessWidget {
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),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
],
);
},
@ -124,7 +124,6 @@ class VideoContent extends StatelessWidget {
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@ -198,24 +197,59 @@ class VideoContent extends StatelessWidget {
// color: Theme.of(context).colorScheme.outline),
// )
const Spacer(),
// SizedBox(
// width: 20,
// height: 20,
// child: IconButton(
// tooltip: '稍后再看',
// style: ButtonStyle(
// padding: MaterialStateProperty.all(EdgeInsets.zero),
// ),
// onPressed: () async {
// var res =
// await UserHttp.toViewLater(bvid: videoItem.bvid);
// SmartDialog.showToast(res['msg']);
// },
// icon: Icon(
// Icons.more_vert_outlined,
// color: Theme.of(context).colorScheme.outline,
// size: 14,
// ),
// ),
// ),
SizedBox(
width: 20,
height: 20,
child: IconButton(
width: 24,
height: 24,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '稍后再看',
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () async {
var res =
await UserHttp.toViewLater(bvid: videoItem.bvid);
SmartDialog.showToast(res['msg']);
},
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 14,
),
position: PopupMenuPosition.under,
// constraints: const BoxConstraints(maxHeight: 35),
onSelected: (String type) {},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
onTap: () async {
var res =
await UserHttp.toViewLater(bvid: videoItem.bvid);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
],
),
),
],

View File

@ -5,7 +5,6 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/pages/rcmd/index.dart';
import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
@ -106,17 +105,13 @@ class VideoCardV extends StatelessWidget {
}
class VideoContent extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
final videoItem;
final dynamic videoItem;
const VideoContent({Key? key, required this.videoItem}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
// 多列
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
padding: const EdgeInsets.fromLTRB(4, 8, 0, 3),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -124,12 +119,8 @@ class VideoContent extends StatelessWidget {
Text(
videoItem.title,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
maxLines: Get.find<RcmdController>().crossAxisCount,
style: const TextStyle(fontSize: 13),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
@ -181,38 +172,90 @@ class VideoContent extends StatelessWidget {
}),
),
SizedBox(
width: 20,
height: 20,
child: IconButton(
width: 24,
height: 24,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '稍后再看',
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () async {
var res =
await UserHttp.toViewLater(bvid: videoItem.bvid);
SmartDialog.showToast(res['msg']);
},
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 14,
),
position: PopupMenuPosition.under,
// constraints: const BoxConstraints(maxHeight: 35),
onSelected: (String type) {},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
onTap: () async {
var res =
await UserHttp.toViewLater(bvid: videoItem.bvid);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
],
),
),
],
),
// Row(
// children: [
// const SizedBox(width: 1),
// StatView(
// theme: 'black',
// theme: 'gray',
// view: videoItem.stat.view,
// ),
// const SizedBox(width: 6),
// const SizedBox(width: 10),
// StatDanMu(
// theme: 'black',
// theme: 'gray',
// danmu: videoItem.stat.danmaku,
// ),
// const Spacer(),
// SizedBox(
// width: 24,
// height: 24,
// child: PopupMenuButton<String>(
// padding: EdgeInsets.zero,
// tooltip: '稍后再看',
// icon: Icon(
// Icons.more_vert_outlined,
// color: Theme.of(context).colorScheme.outline,
// size: 14,
// ),
// position: PopupMenuPosition.under,
// // constraints: const BoxConstraints(maxHeight: 35),
// onSelected: (String type) {},
// itemBuilder: (BuildContext context) =>
// <PopupMenuEntry<String>>[
// PopupMenuItem<String>(
// onTap: () async {
// var res =
// await UserHttp.toViewLater(bvid: videoItem.bvid);
// SmartDialog.showToast(res['msg']);
// },
// value: 'pause',
// height: 35,
// child: const Row(
// children: [
// Icon(Icons.watch_later_outlined, size: 16),
// SizedBox(width: 6),
// Text('稍后再看', style: TextStyle(fontSize: 13))
// ],
// ),
// ),
// ],
// ),
// ),
// ],
// ),
],
@ -237,7 +280,7 @@ class VideoStat extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 45,
height: 48,
padding: const EdgeInsets.only(top: 22, left: 6, right: 6),
decoration: const BoxDecoration(
gradient: LinearGradient(

View File

@ -58,127 +58,121 @@ class _BangumiPageState extends State<BangumiPage>
@override
Widget build(BuildContext context) {
super.build(context);
return Container(
clipBehavior: Clip.hardEdge,
margin: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(StyleString.imgRadius),
),
child: RefreshIndicator(
onRefresh: () async {
await _bangumidController.queryBangumiListFeed(type: 'init');
return _bangumidController.queryBangumiFollow();
},
child: CustomScrollView(
controller: _bangumidController.scrollController,
slivers: [
SliverToBoxAdapter(
child: Obx(
() => Visibility(
visible: _bangumidController.userLogin.value,
child: Column(
children: [
Padding(
padding:
const EdgeInsets.only(top: 10, bottom: 10, left: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'最近追番',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
return RefreshIndicator(
onRefresh: () async {
await _bangumidController.queryBangumiListFeed(type: 'init');
return _bangumidController.queryBangumiFollow();
},
child: CustomScrollView(
controller: _bangumidController.scrollController,
slivers: [
SliverToBoxAdapter(
child: Obx(
() => Visibility(
visible: _bangumidController.userLogin.value,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
top: StyleString.safeSpace, bottom: 10, left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'最近追番',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
SizedBox(
height: 254,
child: FutureBuilder(
future: _bangumidController.queryBangumiFollow(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _bangumidController
.bangumiFollowList.length,
itemBuilder: (context, index) {
return Container(
width: Get.size.width / 3,
height: 254,
margin: EdgeInsets.only(
right: index <
_bangumidController
.bangumiFollowList
.length -
1
? StyleString.safeSpace
: 0),
child: BangumiCardV(
bangumiItem: _bangumidController
.bangumiFollowList[index],
),
);
},
),
);
} else {
return SizedBox();
}
),
SizedBox(
height: 258,
child: FutureBuilder(
future: _bangumidController.queryBangumiFollow(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _bangumidController
.bangumiFollowList.length,
itemBuilder: (context, index) {
return Container(
width: Get.size.width / 3,
height: 254,
margin: EdgeInsets.only(
left: StyleString.safeSpace,
right: index ==
_bangumidController
.bangumiFollowList
.length -
1
? StyleString.safeSpace
: 0),
child: BangumiCardV(
bangumiItem: _bangumidController
.bangumiFollowList[index],
),
);
},
),
);
} else {
return SizedBox();
}
},
),
} else {
return SizedBox();
}
},
),
],
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10, left: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'推荐',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
),
),
SliverPadding(
padding: EdgeInsets.zero,
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(() => contentGrid(_bangumidController,
_bangumidController.bangumiList));
} else {
return HttpError(
errMsg: data['msg'],
fn: () => {},
);
}
} else {
return contentGrid(_bangumidController, []);
}
},
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10, left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'推荐',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
),
const LoadingMore()
],
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(() => contentGrid(
_bangumidController, _bangumidController.bangumiList));
} else {
return HttpError(
errMsg: data['msg'],
fn: () => {},
);
}
} else {
return contentGrid(_bangumidController, []);
}
},
),
),
const LoadingMore()
],
),
);
}

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/index.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/common/tab_type.dart';
import 'package:pilipala/utils/storage.dart';
@ -13,7 +11,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
late TabController tabController;
late List tabsCtrList;
late List<Widget> tabsPageList;
RxString defaultSearch = '输入关键词搜索'.obs;
Box user = GStrorage.user;
RxBool userLogin = false.obs;
RxString userFace = ''.obs;
@ -22,7 +19,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
void onInit() {
super.onInit();
searchDefault();
userLogin.value = user.get(UserBoxKey.userLogin) ?? false;
userFace.value = user.get(UserBoxKey.userFace) ?? '';
@ -50,14 +46,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
ctr().animateToTop();
}
void searchDefault() async {
var res = await Request().get(Api.searchDefault);
if (res.data['code'] == 0) {
defaultSearch.value = res.data['data']['name'];
}
UserHttp.thirdLogin();
}
// 更新登录状态
void updateLoginStatus(val) {
userLogin.value = val ?? false;

View File

@ -1,12 +1,9 @@
import 'package:flutter/material.dart';
import 'package:get/get.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/pages/search/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import './controller.dart';
@ -53,49 +50,21 @@ class _HomePageState extends State<HomePage>
ctr: _homeController,
callback: showUserBottonSheet,
),
Padding(
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4),
child: Theme(
data: ThemeData(
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
height: 42,
child: Align(
alignment: Alignment.center,
child: TabBar(
controller: _homeController.tabController,
tabs: [
for (var i in _homeController.tabs) Tab(text: i['label'])
],
isScrollable: true,
indicatorWeight: 0,
indicatorPadding: const EdgeInsets.only(
top: 37, left: 18, right: 18, bottom: 6),
indicatorColor: Colors.black,
indicator: BoxDecoration(
gradient: RadialGradient(
center: Alignment.centerLeft,
radius: 20.00,
colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.background,
],
),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(2),
bottomLeft: Radius.circular(2),
bottomRight: Radius.circular(4),
),
),
indicatorSize: TabBarIndicatorSize.tab,
labelColor: Theme.of(context).colorScheme.primary,
labelStyle:
const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
dividerColor: Colors.transparent,
unselectedLabelStyle: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontWeight: FontWeight.normal,
),
unselectedLabelColor: Theme.of(context).colorScheme.outline,
enableFeedback: true,
splashBorderRadius: BorderRadius.circular(10),
onTap: (value) {
feedBack();
if (_homeController.initialIndex == value) {
@ -141,83 +110,52 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
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
? MediaQuery.of(context).padding.top + 42
: MediaQuery.of(context).padding.top,
child: Container(
padding: EdgeInsets.only(
left: 12,
right: 12,
bottom: 0,
top: MediaQuery.of(context).padding.top,
),
child: Row(children: [
Image.asset(
'assets/images/logo/logo_android_2.png',
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 4),
Expanded(
child: GestureDetector(
onTap: () {
Get.toNamed('/search',
parameters: {'hintText': ctr!.defaultSearch.value});
},
child: Container(
width: 250,
height: 40,
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: 21,
color: Theme.of(context).colorScheme.outline,
),
const SizedBox(width: 6),
Expanded(
child: Obx(
() => Text(
ctr!.defaultSearch.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline),
),
),
),
],
),
),
),
),
return AnimatedOpacity(
opacity: snapshot.data ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: AnimatedContainer(
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
height: snapshot.data
? MediaQuery.of(context).padding.top + 52
: MediaQuery.of(context).padding.top,
child: Container(
padding: EdgeInsets.only(
left: 20,
right: 20,
bottom: 0,
top: MediaQuery.of(context).padding.top + 4,
),
child: Row(
children: [
const Expanded(child: SearchPage()),
const SizedBox(width: 10),
Obx(
() => ctr!.userLogin.value
? GestureDetector(
onTap: () => callback!(),
child: NetworkImgLayer(
type: 'avatar',
width: 38,
height: 38,
src: ctr!.userFace.value,
),
? Stack(
children: [
NetworkImgLayer(
type: 'avatar',
width: 34,
height: 34,
src: ctr!.userFace.value,
),
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => callback!(),
splashColor: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.3),
borderRadius: const BorderRadius.all(
Radius.circular(50),
),
),
),
)
],
)
: SizedBox(
width: 38,
@ -242,7 +180,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
),
),
),
]),
],
),
),
),

View File

@ -3,6 +3,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/widgets/animated_dialog.dart';
import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/skeleton/video_card_h.dart';
@ -59,52 +60,57 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
super.build(context);
return Scaffold(
body: RefreshIndicator(
displacement: kToolbarHeight + MediaQuery.of(context).padding.top,
onRefresh: () async {
return await _hotController.onRefresh();
},
child: CustomScrollView(
controller: _hotController.scrollController,
slivers: [
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return VideoCardH(
videoItem: _hotController.videoList[index],
longPress: () {
_hotController.popupDialog = _createPopupDialog(
_hotController.videoList[index]);
Overlay.of(context)
.insert(_hotController.popupDialog!);
},
longPressEnd: () {
_hotController.popupDialog?.remove();
},
);
}, childCount: _hotController.videoList.length),
),
);
SliverPadding(
// 单列布局 EdgeInsets.zero
padding:
const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0),
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
return VideoCardH(
videoItem: _hotController.videoList[index],
longPress: () {
_hotController.popupDialog = _createPopupDialog(
_hotController.videoList[index]);
Overlay.of(context)
.insert(_hotController.popupDialog!);
},
longPressEnd: () {
_hotController.popupDialog?.remove();
},
);
}, childCount: _hotController.videoList.length),
),
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
} else {
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton();
}, childCount: 10),
);
}
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton();
}, childCount: 10),
);
}
},
},
),
),
SliverToBoxAdapter(
child: SizedBox(

View File

@ -70,7 +70,8 @@ class _LivePageState extends State<LivePage> {
slivers: [
SliverPadding(
// 单列布局 EdgeInsets.zero
padding: EdgeInsets.zero,
padding:
const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0),
sliver: FutureBuilder(
future: _liveController.queryLiveList('init'),
builder: (context, snapshot) {
@ -118,13 +119,13 @@ class _LivePageState extends State<LivePage> {
return SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// 行间距
mainAxisSpacing: StyleString.cardSpace + 2,
mainAxisSpacing: StyleString.cardSpace + 4,
// 列间距
crossAxisSpacing: StyleString.cardSpace + 3,
crossAxisSpacing: StyleString.cardSpace + 4,
// 列数
crossAxisCount: ctr.crossAxisCount,
mainAxisExtent:
Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 60,
Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 64,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {

View File

@ -103,7 +103,7 @@ class LiveContent extends StatelessWidget {
return Expanded(
child: Padding(
// 多列
padding: const EdgeInsets.fromLTRB(4, 5, 6, 6),
padding: const EdgeInsets.fromLTRB(4, 8, 0, 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -154,7 +154,7 @@ class VideoStat extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 45,
height: 50,
padding: const EdgeInsets.only(top: 22, left: 10, right: 10),
decoration: const BoxDecoration(
gradient: LinearGradient(

View File

@ -15,23 +15,35 @@ class MainController extends GetxController {
RxList navigationBars = [
{
'icon': const Icon(
Icons.motion_photos_on_outlined,
Icons.favorite_outline,
size: 21,
),
'label': "推荐",
'selectIcon': const Icon(
Icons.favorite,
size: 21,
),
'label': "首页",
},
{
'icon': const Icon(
Icons.bolt,
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
},
{
'icon': const Icon(
Icons.folder_open_outlined,
Icons.folder_outlined,
size: 20,
),
'selectIcon': const Icon(
Icons.folder,
size: 21,
),
'label': "媒体库",
}
].obs;

View File

@ -135,21 +135,17 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
initialData: true,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedSlide(
curve: Curves.linear,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 1000),
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.outline.withOpacity(0.5),
selectedFontSize: 12.4,
onTap: (value) => setIndex(value),
items: [
child: NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return BottomNavigationBarItem(
return NavigationDestination(
icon: e['icon'],
selectedIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),

View File

@ -77,7 +77,7 @@ class _RcmdPageState extends State<RcmdPage>
// 单列布局 EdgeInsets.zero
padding: _rcmdController.crossAxisCount == 1
? EdgeInsets.zero
: const EdgeInsets.fromLTRB(0, 0, 0, 0),
: const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0),
sliver: FutureBuilder(
future: _rcmdController.queryRcmdFeed('init'),
builder: (context, snapshot) {
@ -124,13 +124,13 @@ class _RcmdPageState extends State<RcmdPage>
return SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// 行间距
mainAxisSpacing: StyleString.cardSpace + 2,
mainAxisSpacing: StyleString.cardSpace + 4,
// 列间距
crossAxisSpacing: StyleString.cardSpace + 3,
crossAxisSpacing: StyleString.cardSpace + 4,
// 列数
crossAxisCount: ctr.crossAxisCount,
mainAxisExtent:
Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 60,
Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 64,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/index.dart';
import 'package:pilipala/http/search.dart';
import 'package:pilipala/models/search/hot.dart';
import 'package:pilipala/models/search/suggest.dart';
@ -20,10 +21,12 @@ class SSearchController extends GetxController {
final _debouncer =
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
String hintText = '搜索';
RxString defaultSearch = '输入关键词搜索'.obs;
@override
void onInit() {
super.onInit();
searchDefault();
if (hotKeyword.get('cacheList') != null &&
hotKeyword.get('cacheList').isNotEmpty) {
List<HotSearchItem> list = [];
@ -121,4 +124,12 @@ class SSearchController extends GetxController {
historyList.refresh();
histiryWord.put('cacheList', []);
}
void searchDefault() async {
var res = await Request().get(Api.searchDefault);
if (res.data['code'] == 0) {
searchKeyWord.value =
hintText = defaultSearch.value = res.data['data']['name'];
}
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'controller.dart';
@ -41,61 +42,117 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
shape: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
width: 1,
return OpenContainer(
closedElevation: 0,
openElevation: 0,
openColor: Theme.of(context).colorScheme.background,
middleColor: Theme.of(context).colorScheme.background,
closedColor: Theme.of(context).colorScheme.background,
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30.0))),
openShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30.0))),
closedBuilder: (BuildContext context, VoidCallback openContainer) {
return Container(
width: 250,
height: 44,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(25)),
),
),
titleSpacing: 0,
actions: [
Hero(
tag: 'searchTag',
child: IconButton(
onPressed: () => _searchController.submit(),
icon: const Icon(CupertinoIcons.search, size: 22)),
),
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: _searchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
Icons.clear,
size: 22,
color: Theme.of(context).colorScheme.outline,
),
onPressed: () => _searchController.onClear(),
child: Material(
color:
Theme.of(context).colorScheme.secondaryContainer.withAlpha(115),
child: InkWell(
splashColor: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.3),
onTap: openContainer,
child: Row(
children: [
const SizedBox(width: 14),
Icon(
Icons.search_outlined,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
const SizedBox(width: 10),
Expanded(
child: Obx(
() => Text(
_searchController.defaultSearch.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
),
],
),
),
onSubmitted: (String value) => _searchController.submit(),
),
),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 12),
// 搜索建议
_searchSuggest(),
// 热搜
hotSearch(),
// 搜索历史
_history()
],
),
),
);
},
openBuilder: (BuildContext context, VoidCallback _) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
shape: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
width: 1,
),
),
titleSpacing: 0,
actions: [
Hero(
tag: 'searchTag',
child: IconButton(
onPressed: () => _searchController.submit(),
icon: const Icon(CupertinoIcons.search, size: 22)),
),
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: _searchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
Icons.clear,
size: 22,
color: Theme.of(context).colorScheme.outline,
),
onPressed: () => _searchController.onClear(),
),
),
onSubmitted: (String value) => _searchController.submit(),
),
),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 12),
// 搜索建议
_searchSuggest(),
// 热搜
hotSearch(),
// 搜索历史
_history()
],
),
),
);
},
);
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/plugin/pl_player/models/fullscreen_mode.dart';
import 'package:pilipala/utils/storage.dart';
import 'widgets/switch_item.dart';
@ -17,6 +18,7 @@ class _PlaySettingState extends State<PlaySetting> {
late dynamic defaultVideoQa;
late dynamic defaultAudioQa;
late dynamic defaultDecode;
late int defaultFullScreenMode;
@override
void initState() {
@ -27,6 +29,8 @@ class _PlaySettingState extends State<PlaySetting> {
defaultValue: AudioQuality.values.last.code);
defaultDecode = setting.get(SettingBoxKey.defaultDecode,
defaultValue: VideoDecodeFormats.values.last.code);
defaultFullScreenMode = setting.get(SettingBoxKey.fullScreenMode,
defaultValue: FullScreenMode.values.first.code);
}
@override
@ -68,7 +72,7 @@ class _PlaySettingState extends State<PlaySetting> {
),
trailing: PopupMenuButton(
initialValue: defaultVideoQa,
icon: const Icon(Icons.arrow_forward_rounded, size: 22),
icon: const Icon(Icons.more_vert_outlined, size: 22),
onSelected: (item) {
defaultVideoQa = item;
setting.put(SettingBoxKey.defaultVideoQa, item);
@ -93,7 +97,7 @@ class _PlaySettingState extends State<PlaySetting> {
),
trailing: PopupMenuButton(
initialValue: defaultAudioQa,
icon: const Icon(Icons.arrow_forward_rounded, size: 22),
icon: const Icon(Icons.more_vert_outlined, size: 22),
onSelected: (item) {
defaultAudioQa = item;
setting.put(SettingBoxKey.defaultAudioQa, item);
@ -119,7 +123,7 @@ class _PlaySettingState extends State<PlaySetting> {
),
trailing: PopupMenuButton(
initialValue: defaultDecode,
icon: const Icon(Icons.arrow_forward_rounded, size: 22),
icon: const Icon(Icons.more_vert_outlined, size: 22),
onSelected: (item) {
defaultDecode = item;
setting.put(SettingBoxKey.defaultDecode, item);
@ -135,6 +139,33 @@ class _PlaySettingState extends State<PlaySetting> {
],
),
),
ListTile(
dense: false,
title: Text('默认全屏方式', style: titleStyle),
subtitle: Text(
'当前全屏方式:' +
FullScreenModeCode.fromCode(defaultFullScreenMode)!
.description,
style: subTitleStyle,
),
trailing: PopupMenuButton(
initialValue: defaultFullScreenMode,
icon: const Icon(Icons.more_vert_outlined, size: 22),
onSelected: (item) {
defaultFullScreenMode = item;
setting.put(SettingBoxKey.fullScreenMode, item);
setState(() {});
},
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
for (var i in FullScreenMode.values) ...[
PopupMenuItem(
value: i.code,
child: Text(i.description),
),
]
],
),
),
],
),
);

View File

@ -102,10 +102,11 @@ class _FavPanelState extends State<FavPanel> {
),
Padding(
padding: EdgeInsets.only(
left: 20,
right: 20,
top: 12,
bottom: MediaQuery.of(context).padding.bottom),
left: 20,
right: 20,
top: 12,
bottom: MediaQuery.of(context).padding.bottom + 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [

View File

@ -7,3 +7,20 @@ enum FullScreenMode {
// 始终横屏
horizontal
}
extension FullScreenModeDesc on FullScreenMode {
String get description => ['自适应', '始终竖屏', '始终横屏'][index];
}
extension FullScreenModeCode on FullScreenMode {
static final List<int> _codeList = [0, 1, 2];
int get code => _codeList[index];
static FullScreenMode? fromCode(int code) {
final index = _codeList.indexOf(code);
if (index != -1) {
return FullScreenMode.values[index];
}
return null;
}
}

View File

@ -5,14 +5,17 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:pilipala/common/widgets/app_bar_ani.dart';
import 'package:pilipala/plugin/pl_player/controller.dart';
import 'package:pilipala/plugin/pl_player/models/duration.dart';
import 'package:pilipala/plugin/pl_player/models/fullscreen_mode.dart';
import 'package:pilipala/plugin/pl_player/models/play_status.dart';
import 'package:pilipala/plugin/pl_player/utils.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:volume_controller/volume_controller.dart';
@ -62,6 +65,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
bool _volumeInterceptEventStream = false;
Box setting = GStrorage.setting;
late FullScreenMode mode;
void onDoubleTapSeekBackward() {
setState(() {
_mountSeekBackwardButton = true;
@ -149,16 +155,37 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
Future<void> triggerFullScreen() async {
PlPlayerController _ = widget.controller;
mode = FullScreenModeCode.fromCode(
setting.get(SettingBoxKey.fullScreenMode, defaultValue: 0))!;
if (!_.isFullScreen.value) {
/// 按照视频宽高比决定全屏方向
if (_.direction.value == 'horizontal') {
/// 进入全屏
await enterFullScreen();
// 横
// await landScape();
} else {
// 竖屏
await verticalScreen();
switch (mode) {
case FullScreenMode.auto:
if (_.direction.value == 'horizontal') {
/// 进入全
await enterFullScreen();
// 横屏
// await landScape();
} else {
// 竖屏
await verticalScreen();
}
break;
case FullScreenMode.vertical:
/// 进入全屏
await enterFullScreen();
// 横屏
// await landScape();
break;
case FullScreenMode.horizontal:
/// 进入全屏
await enterFullScreen();
// 横屏
// await landScape();
break;
}
_.toggleFullScreen(true);

View File

@ -31,6 +31,10 @@ class GStrorage {
localCache = await Hive.openBox('localCache');
// 设置
setting = await Hive.openBox('setting');
// 热搜关键词
hotKeyword = await Hive.openBox('hotKeyword');
// 搜索历史
historyword = await Hive.openBox('historyWord');
}
static regAdapter() {
@ -45,10 +49,6 @@ class GStrorage {
}
static Future<void> lazyInit() async {
// 热搜关键词
hotKeyword = await Hive.openBox('hotKeyword');
// 搜索历史
historyword = await Hive.openBox('historyWord');
// 视频设置
video = await Hive.openBox('video');
}
@ -81,6 +81,7 @@ class SettingBoxKey {
static const String defaultPicQa = 'defaultPicQa';
static const String danmakuEnable = 'danmakuEnable';
static const String fullScreenMode = 'fullScreenMode';
}
class LocalCacheKey {