Merge branch 'main' into design

This commit is contained in:
guozhigq
2024-09-23 00:14:27 +08:00
29 changed files with 728 additions and 326 deletions

View File

@ -1,5 +1,6 @@
import UIKit
import Flutter
import AVFoundation
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@ -8,6 +9,14 @@ import Flutter
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
//
do {
try AVAudioSession.sharedInstance().setCategory(.playback, options: [.duckOthers])
} catch {
print("Failed to set audio session category: \(error)")
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
}

View File

@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/extension.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import '../../utils/storage.dart';
import '../constants.dart';
@ -33,7 +33,7 @@ class NetworkImgLayer extends StatelessWidget {
@override
Widget build(BuildContext context) {
final int defaultImgQuality = GlobalData().imgQuality;
final int defaultImgQuality = GlobalDataCache().imgQuality;
if (src == '' || src == null) {
return placeholder(context);
}

View File

@ -571,4 +571,8 @@ class Api {
/// 直播间发送弹幕
static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send';
/// 我的关注 - 正在直播
static const String getFollowingLive =
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
}

View File

@ -46,7 +46,8 @@ class ApiInterceptor extends Interceptor {
// 处理网络请求错误
// handler.next(err);
String url = err.requestOptions.uri.toString();
if (!url.contains('heartbeat')) {
final excludedPatterns = RegExp(r'heartbeat|seg\.so|online/total');
if (!excludedPatterns.hasMatch(url)) {
SmartDialog.showToast(
await dioError(err),
displayType: SmartToastType.onlyRefresh,

View File

@ -1,3 +1,5 @@
import 'package:pilipala/models/live/follow.dart';
import '../models/live/item.dart';
import '../models/live/room_info.dart';
import '../models/live/room_info_h5.dart';
@ -117,4 +119,27 @@ class LiveHttp {
};
}
}
// 我的关注 正在直播
static Future liveFollowing({int? pn, int? ps}) async {
var res = await Request().get(Api.getFollowingLive, data: {
'page': pn,
'page_size': ps,
'platform': 'web',
'ignoreRecord': 1,
'hit_ab': true,
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': LiveFollowingModel.fromJson(res.data['data'])
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
}

View File

@ -20,7 +20,7 @@ import 'package:pilipala/pages/main/view.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/app_scheme.dart';
import 'package:pilipala/utils/data.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:media_kit/media_kit.dart';
import 'package:pilipala/utils/recommend_filter.dart';
@ -33,7 +33,6 @@ void main() async {
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
await GStrorage.init();
await setupServiceLocator();
clearLogs();
Request();
await Request.setCookie();
@ -65,6 +64,7 @@ void main() async {
}
PiliSchame.init();
await GlobalDataCache().initialize();
}
class MyApp extends StatelessWidget {
@ -266,10 +266,10 @@ class BuildMainApp extends StatelessWidget {
VideoDetailPage.routeObserver,
SearchPage.routeObserver,
],
onInit: () {
onReady: () async {
RecommendFilter();
Data.init();
GlobalData();
setupServiceLocator();
},
);
}

126
lib/models/live/follow.dart Normal file
View File

@ -0,0 +1,126 @@
class LiveFollowingModel {
int? count;
List<LiveFollowingItemModel>? list;
int? liveCount;
int? neverLivedCount;
List? neverLivedFaces;
int? pageSize;
String? title;
int? totalPage;
LiveFollowingModel({
this.count,
this.list,
this.liveCount,
this.neverLivedCount,
this.neverLivedFaces,
this.pageSize,
this.title,
this.totalPage,
});
LiveFollowingModel.fromJson(Map<String, dynamic> json) {
count = json['count'];
if (json['list'] != null) {
list = <LiveFollowingItemModel>[];
json['list'].forEach((v) {
list!.add(LiveFollowingItemModel.fromJson(v));
});
}
liveCount = json['live_count'];
neverLivedCount = json['never_lived_count'];
if (json['never_lived_faces'] != null) {
neverLivedFaces = <dynamic>[];
json['never_lived_faces'].forEach((v) {
neverLivedFaces!.add(v);
});
}
pageSize = json['pageSize'];
title = json['title'];
totalPage = json['totalPage'];
}
}
class LiveFollowingItemModel {
int? roomId;
int? uid;
String? uname;
String? title;
String? face;
int? liveStatus;
int? recordNum;
String? recentRecordId;
int? isAttention;
int? clipNum;
int? fansNum;
String? areaName;
String? areaValue;
String? tags;
String? recentRecordIdV2;
int? recordNumV2;
int? recordLiveTime;
String? areaNameV2;
String? roomNews;
String? watchIcon;
String? textSmall;
String? roomCover;
String? pic;
int? parentAreaId;
int? areaId;
LiveFollowingItemModel({
this.roomId,
this.uid,
this.uname,
this.title,
this.face,
this.liveStatus,
this.recordNum,
this.recentRecordId,
this.isAttention,
this.clipNum,
this.fansNum,
this.areaName,
this.areaValue,
this.tags,
this.recentRecordIdV2,
this.recordNumV2,
this.recordLiveTime,
this.areaNameV2,
this.roomNews,
this.watchIcon,
this.textSmall,
this.roomCover,
this.pic,
this.parentAreaId,
this.areaId,
});
LiveFollowingItemModel.fromJson(Map<String, dynamic> json) {
roomId = json['roomid'];
uid = json['uid'];
uname = json['uname'];
title = json['title'];
face = json['face'];
liveStatus = json['live_status'];
recordNum = json['record_num'];
recentRecordId = json['recent_record_id'];
isAttention = json['is_attention'];
clipNum = json['clipnum'];
fansNum = json['fans_num'];
areaName = json['area_name'];
areaValue = json['area_value'];
tags = json['tags'];
recentRecordIdV2 = json['recent_record_id_v2'];
recordNumV2 = json['record_num_v2'];
recordLiveTime = json['record_live_time'];
areaNameV2 = json['area_name_v2'];
roomNews = json['room_news'];
watchIcon = json['watch_icon'];
textSmall = json['text_small'];
roomCover = json['room_cover'];
pic = json['room_cover'];
parentAreaId = json['parent_area_id'];
areaId = json['area_id'];
}
}

View File

@ -1,11 +1,13 @@
class RoomInfoModel {
RoomInfoModel({
this.roomId,
this.isPortrait,
this.liveStatus,
this.liveTime,
this.playurlInfo,
});
int? roomId;
bool? isPortrait;
int? liveStatus;
int? liveTime;
PlayurlInfo? playurlInfo;
@ -13,6 +15,7 @@ class RoomInfoModel {
RoomInfoModel.fromJson(Map<String, dynamic> json) {
roomId = json['room_id'];
liveStatus = json['live_status'];
isPortrait = json['is_portrait'];
liveTime = json['live_time'];
playurlInfo = PlayurlInfo.fromJson(json['playurl_info']);
}

View File

@ -48,7 +48,7 @@ class FollowSearchController extends GetxController {
return {'status': true, 'data': <FollowItemModel>[].obs};
}
if (type == 'init') {
ps = 1;
pn = 1;
}
var res = await MemberHttp.getfollowSearch(
mid: mid,

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/live.dart';
import 'package:pilipala/models/live/follow.dart';
import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/utils/storage.dart';
@ -11,6 +12,8 @@ class LiveController extends GetxController {
int _currentPage = 1;
RxInt crossAxisCount = 2.obs;
RxList<LiveItemModel> liveList = <LiveItemModel>[].obs;
RxList<LiveFollowingItemModel> liveFollowingList =
<LiveFollowingItemModel>[].obs;
bool flag = false;
OverlayEntry? popupDialog;
Box setting = GStrorage.setting;
@ -44,6 +47,7 @@ class LiveController extends GetxController {
// 下拉刷新
Future onRefresh() async {
queryLiveList('init');
fetchLiveFollowing();
}
// 上拉加载
@ -61,4 +65,17 @@ class LiveController extends GetxController {
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
}
}
//
Future fetchLiveFollowing() async {
var res = await LiveHttp.liveFollowing(pn: 1, ps: 20);
if (res['status']) {
liveFollowingList.value =
(res['data'].list as List<LiveFollowingItemModel>)
.where((LiveFollowingItemModel item) =>
item.liveStatus == 1 && item.recordLiveTime == 0) // 根据条件过滤
.toList();
}
return res;
}
}

View File

@ -6,6 +6,8 @@ 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/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/live/follow.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'controller.dart';
@ -22,6 +24,7 @@ class _LivePageState extends State<LivePage>
with AutomaticKeepAliveClientMixin {
final LiveController _liveController = Get.put(LiveController());
late Future _futureBuilderFuture;
late Future _futureBuilderFuture2;
late ScrollController scrollController;
@override
@ -31,6 +34,7 @@ class _LivePageState extends State<LivePage>
void initState() {
super.initState();
_futureBuilderFuture = _liveController.queryLiveList('init');
_futureBuilderFuture2 = _liveController.fetchLiveFollowing();
scrollController = _liveController.scrollController;
scrollController.addListener(
() {
@ -69,6 +73,7 @@ class _LivePageState extends State<LivePage>
child: CustomScrollView(
controller: _liveController.scrollController,
slivers: [
buildFollowingList(),
SliverPadding(
// 单列布局 EdgeInsets.zero
padding:
@ -147,4 +152,148 @@ class _LivePageState extends State<LivePage>
),
);
}
// 关注的up直播
Widget buildFollowingList() {
return SliverPadding(
padding: const EdgeInsets.only(top: 16),
sliver: SliverToBoxAdapter(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(
() => Text.rich(
TextSpan(
children: [
const TextSpan(
text: ' 我的关注 ',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
TextSpan(
text: ' ${_liveController.liveFollowingList.length}',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.primary,
),
),
TextSpan(
text: '人正在直播',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
),
),
FutureBuilder(
future: _futureBuilderFuture2,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
Map? data = snapshot.data;
if (data?['status']) {
RxList list = _liveController.liveFollowingList;
// ignore: invalid_use_of_protected_member
return Obx(() => LiveFollowingListView(list: list.value));
} else {
return SizedBox(
height: 80,
child: Center(
child: Text(
data?['msg'] ?? '',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: 12,
),
),
),
);
}
} else {
return const SizedBox();
}
},
),
],
),
),
);
}
}
class LiveFollowingListView extends StatelessWidget {
final List list;
const LiveFollowingListView({super.key, required this.list});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final LiveFollowingItemModel item = list[index];
return Padding(
padding: const EdgeInsets.fromLTRB(3, 12, 3, 0),
child: Column(
children: [
InkWell(
onTap: () {
Get.toNamed(
'/liveRoom?roomid=${item.roomId}',
arguments: {
'liveItem': item,
'heroTag': item.roomId.toString()
},
);
},
child: Container(
width: 54,
height: 54,
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(27),
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 1.5,
),
),
child: NetworkImgLayer(
width: 50,
height: 50,
type: 'avatar',
src: list[index].face,
),
),
),
const SizedBox(height: 6),
SizedBox(
width: 62,
child: Text(
list[index].uname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
),
],
),
);
},
itemCount: list.length,
),
);
}
}

View File

@ -48,6 +48,7 @@ class LiveRoomController extends GetxController {
// 直播间弹幕开关 默认打开
RxBool danmakuSwitch = true.obs;
late String buvid;
RxBool isPortrait = false.obs;
@override
void onInit() {
@ -58,11 +59,12 @@ class LiveRoomController extends GetxController {
if (Get.arguments != null) {
liveItem = Get.arguments['liveItem'];
heroTag = Get.arguments['heroTag'] ?? '';
if (liveItem != null && liveItem.pic != null && liveItem.pic != '') {
cover = liveItem.pic;
}
if (liveItem != null && liveItem.cover != null && liveItem.cover != '') {
cover = liveItem.cover;
if (liveItem != null) {
cover = (liveItem.pic != null && liveItem.pic != '')
? liveItem.pic
: (liveItem.cover != null && liveItem.cover != '')
? liveItem.cover
: null;
}
Request.getBuvid().then((value) => buvid = value);
}
@ -100,6 +102,7 @@ class LiveRoomController extends GetxController {
Future queryLiveInfo() async {
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: currentQn);
if (res['status']) {
isPortrait.value = res['data'].isPortrait;
List<CodecItem> codec =
res['data'].playurlInfo.playurl.stream.first.format.first.codec;
CodecItem item = codec.first;

View File

@ -115,6 +115,9 @@ class _LiveRoomPageState extends State<LiveRoomPage>
plPlayerController = _liveRoomController.plPlayerController;
return PLVideoPlayer(
controller: plPlayerController,
alignment: _liveRoomController.isPortrait.value
? Alignment.topCenter
: Alignment.center,
bottomControl: BottomControl(
controller: plPlayerController,
liveRoomCtr: _liveRoomController,
@ -178,64 +181,18 @@ class _LiveRoomPageState extends State<LiveRoomPage>
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppBar(
centerTitle: false,
titleSpacing: 0,
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
toolbarHeight:
MediaQuery.of(context).orientation == Orientation.portrait
? 56
: 0,
title: FutureBuilder(
future: _futureBuilder,
builder: (context, snapshot) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => Row(
children: [
NetworkImgLayer(
width: 34,
height: 34,
type: 'avatar',
src: _liveRoomController
.roomInfoH5.value.anchorInfo!.baseInfo!.face,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_liveRoomController.roomInfoH5.value
.anchorInfo!.baseInfo!.uname!,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 1),
if (_liveRoomController
.roomInfoH5.value.watchedShow !=
null)
Text(
_liveRoomController.roomInfoH5.value
.watchedShow!['text_large'] ??
'',
style: const TextStyle(fontSize: 12),
),
],
),
],
),
);
} else {
return const SizedBox();
}
},
Obx(
() => SizedBox(
height: MediaQuery.of(context).padding.top +
(_liveRoomController.isPortrait.value ||
MediaQuery.of(context).orientation ==
Orientation.landscape
? 0
: kToolbarHeight),
),
),
PopScope(
@ -249,66 +206,141 @@ class _LiveRoomPageState extends State<LiveRoomPage>
verticalScreen();
}
},
child: SizedBox(
width: Get.size.width,
height: MediaQuery.of(context).orientation ==
Orientation.landscape
? Get.size.height
: Get.size.width * 9 / 16,
child: videoPlayerPanel,
child: Obx(
() => Container(
width: Get.size.width,
height: MediaQuery.of(context).orientation ==
Orientation.landscape
? Get.size.height
: !_liveRoomController.isPortrait.value
? Get.size.width * 9 / 16
: Get.size.height -
MediaQuery.of(context).padding.top,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(6)),
),
child: videoPlayerPanel,
),
),
),
// 显示消息的列表
buildMessageListUI(
],
),
// 定位 快速滑动到底部
Positioned(
right: 20,
bottom: MediaQuery.of(context).padding.bottom + 80,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 4),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: ElevatedButton.icon(
onPressed: () {
_scrollToBottom();
},
icon: const Icon(Icons.keyboard_arrow_down), // 图标
label: const Text('新消息'), // 文字
style: ElevatedButton.styleFrom(
// primary: Colors.blue, // 按钮背景颜色
// onPrimary: Colors.white, // 按钮文字颜色
padding: const EdgeInsets.fromLTRB(14, 12, 20, 12), // 按钮内边距
),
),
),
),
// 顶栏
Positioned(
top: 0,
left: 0,
right: 0,
child: AppBar(
centerTitle: false,
titleSpacing: 0,
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
toolbarHeight:
MediaQuery.of(context).orientation == Orientation.portrait
? 56
: 0,
title: FutureBuilder(
future: _futureBuilder,
builder: (context, snapshot) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => Row(
children: [
NetworkImgLayer(
width: 34,
height: 34,
type: 'avatar',
src: _liveRoomController
.roomInfoH5.value.anchorInfo!.baseInfo!.face,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_liveRoomController.roomInfoH5.value.anchorInfo!
.baseInfo!.uname!,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 1),
if (_liveRoomController
.roomInfoH5.value.watchedShow !=
null)
Text(
_liveRoomController.roomInfoH5.value
.watchedShow!['text_large'] ??
'',
style: const TextStyle(fontSize: 12),
),
],
),
],
),
);
} else {
return const SizedBox();
}
},
),
),
),
// 消息列表
Obx(
() => Positioned(
top: MediaQuery.of(context).padding.top +
kToolbarHeight +
(_liveRoomController.isPortrait.value
? Get.size.width
: Get.size.width * 9 / 16),
bottom: 90 + MediaQuery.of(context).padding.bottom,
left: 0,
right: 0,
child: buildMessageListUI(
context,
_liveRoomController,
_scrollController,
),
// Container(
// padding: const EdgeInsets.only(
// left: 14, right: 14, top: 4, bottom: 4),
// margin: const EdgeInsets.only(
// bottom: 6,
// left: 14,
// ),
// decoration: BoxDecoration(
// color: Colors.grey.withOpacity(0.1),
// borderRadius: const BorderRadius.all(Radius.circular(20)),
// ),
// child: Obx(
// () => AnimatedSwitcher(
// duration: const Duration(milliseconds: 300),
// transitionBuilder:
// (Widget child, Animation<double> animation) {
// return FadeTransition(opacity: animation, child: child);
// },
// child: Text.rich(
// key:
// ValueKey(_liveRoomController.joinRoomTip['userName']),
// TextSpan(
// style: const TextStyle(color: Colors.white),
// children: [
// TextSpan(
// text:
// '${_liveRoomController.joinRoomTip['userName']} ',
// style: TextStyle(
// color: Colors.white.withOpacity(0.6),
// ),
// ),
// TextSpan(
// text:
// '${_liveRoomController.joinRoomTip['message']}',
// style: const TextStyle(color: Colors.white),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
const SizedBox(height: 10),
// 弹幕输入框
Container(
),
),
// 消息输入框
Visibility(
visible: MediaQuery.of(context).orientation == Orientation.portrait,
child: Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: EdgeInsets.only(
left: 14,
right: 14,
@ -384,32 +416,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
],
),
),
],
),
// 定位 快速滑动到底部
Positioned(
right: 20,
bottom: MediaQuery.of(context).padding.bottom + 80,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 4),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: ElevatedButton.icon(
onPressed: () {
_scrollToBottom();
},
icon: const Icon(Icons.keyboard_arrow_down), // 图标
label: const Text('新消息'), // 文字
style: ElevatedButton.styleFrom(
// primary: Colors.blue, // 按钮背景颜色
// onPrimary: Colors.white, // 按钮文字颜色
padding: const EdgeInsets.fromLTRB(14, 12, 20, 12), // 按钮内边距
),
),
),
),
],
@ -467,7 +473,9 @@ Widget buildMessageListUI(
alignment: Alignment.centerLeft,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
color: liveRoomController.isPortrait.value
? Colors.black.withOpacity(0.3)
: Colors.grey.withOpacity(0.1),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
margin: EdgeInsets.only(

View File

@ -10,7 +10,6 @@ import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/event_bus.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart';
import './controller.dart';
@ -30,6 +29,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
int? _lastSelectTime; //上次点击时间
Box setting = GStrorage.setting;
late bool enableMYBar;
@override
void initState() {
@ -37,6 +37,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
_mainController.pageController =
PageController(initialPage: _mainController.selectedIndex);
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
}
void setIndex(int value) async {
@ -171,7 +172,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1),
child: GlobalData().enableMYBar
child: enableMYBar
? Obx(
() => NavigationBar(
onDestinationSelected: (value) => setIndex(value),

View File

@ -406,7 +406,7 @@ class _MemberPageState extends State<MemberPage>
? '个人认证:'
: '企业认证:',
style: TextStyle(
color: Theme.of(context).primaryColor,
color: Theme.of(context).colorScheme.primary,
),
children: [
TextSpan(

View File

@ -27,10 +27,13 @@ class MemberArchiveController extends GetxController {
// 获取用户投稿
Future getMemberArchive(type) async {
if (isLoading.value) {
return;
}
isLoading.value = true;
if (type == 'init') {
pn = 1;
archivesList.clear();
isLoading.value = true;
}
var res = await MemberHttp.memberArchive(
mid: mid,

View File

@ -21,7 +21,7 @@ class MemberSeasonsController extends GetxController {
mid = int.parse(Get.parameters['mid']!);
category = Get.parameters['category']!;
if (category == '0') {
seasonId = int.parse(Get.parameters['seriesId']!);
seasonId = int.parse(Get.parameters['seasonId']!);
}
if (category == '1') {
seriesId = int.parse(Get.parameters['seriesId']!);

View File

@ -203,9 +203,9 @@ class LikeItem extends StatelessWidget {
Text.rich(TextSpan(children: [
TextSpan(text: nickNameList.join('')),
const TextSpan(text: ' '),
if (item.users!.length > 1)
if (item.counts! > 1)
TextSpan(
text: '等总计${item.users!.length}',
text: '等总计${item.counts}',
style: TextStyle(color: outline),
),
TextSpan(

View File

@ -261,90 +261,97 @@ class VideoPanelController extends GetxController {
onShowFilterSheet(searchPanelCtr) {
showModalBottomSheet(
context: Get.context!,
isScrollControlled: true,
builder: (context) {
return StatefulBuilder(
builder: (context, StateSetter setState) {
return Container(
color: Theme.of(Get.context!).colorScheme.surface,
padding: const EdgeInsets.only(top: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
return Padding(
padding: EdgeInsets.only(
top: 12, bottom: MediaQuery.of(context).padding.bottom + 20),
child: Wrap(
children: [
const ListTile(
title: Text('内容时长'),
),
Padding(
padding: const EdgeInsets.only(
left: 14,
right: 14,
bottom: 14,
),
child: Wrap(
spacing: 10,
runSpacing: 10,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (var i in timeFiltersList)
Obx(
() => SearchText(
searchText: i['label'],
searchTextIdx: i['value'],
isSelect:
currentTimeFilterval.value == i['value'],
onSelect: (value) async {
currentTimeFilterval.value = i['value'];
setState(() {});
SmartDialog.showToast("${i['label']}」的筛选结果");
SearchPanelController ctr =
Get.find<SearchPanelController>(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('内容时长'),
),
Padding(
padding: const EdgeInsets.only(
left: 14,
right: 14,
bottom: 14,
),
child: Wrap(
spacing: 10,
runSpacing: 10,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (var i in timeFiltersList)
Obx(
() => SearchText(
searchText: i['label'],
searchTextIdx: i['value'],
isSelect:
currentTimeFilterval.value == i['value'],
onSelect: (value) async {
currentTimeFilterval.value = i['value'];
setState(() {});
SmartDialog.showToast(
"${i['label']}」的筛选结果");
SearchPanelController ctr = Get.find<
SearchPanelController>(
tag: 'video${searchPanelCtr.keyword!}');
ctr.duration.value = i['value'];
Get.back();
SmartDialog.showLoading(msg: '获取中');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongSelect: (value) => {},
),
)
],
),
),
const ListTile(
title: Text('内容分区'),
),
Padding(
padding: const EdgeInsets.only(left: 14, right: 14),
child: Wrap(
spacing: 10,
runSpacing: 10,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (var i in partFiltersList)
SearchText(
searchText: i['label'],
searchTextIdx: i['value'],
isSelect: currentPartFilterval.value == i['value'],
onSelect: (value) async {
currentPartFilterval.value = i['value'];
setState(() {});
SmartDialog.showToast("${i['label']}」的筛选结果");
SearchPanelController ctr =
Get.find<SearchPanelController>(
ctr.duration.value = i['value'];
Get.back();
SmartDialog.showLoading(msg: '获取中');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongSelect: (value) => {},
),
)
],
),
),
const ListTile(
title: Text('内容分区'),
),
Padding(
padding: const EdgeInsets.only(left: 14, right: 14),
child: Wrap(
spacing: 10,
runSpacing: 10,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (var i in partFiltersList)
SearchText(
searchText: i['label'],
searchTextIdx: i['value'],
isSelect:
currentPartFilterval.value == i['value'],
onSelect: (value) async {
currentPartFilterval.value = i['value'];
setState(() {});
SmartDialog.showToast("${i['label']}」的筛选结果");
SearchPanelController ctr = Get.find<
SearchPanelController>(
tag: 'video${searchPanelCtr.keyword!}');
ctr.tids.value = i['value'];
Get.back();
SmartDialog.showLoading(msg: '获取中');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongSelect: (value) => {},
)
],
),
)
ctr.tids.value = i['value'];
Get.back();
SmartDialog.showLoading(msg: '获取中');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongSelect: (value) => {},
)
],
),
)
],
),
],
),
);

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/common/action_type.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import '../../../utils/storage.dart';
class ActionMenuSetPage extends StatefulWidget {
@ -38,7 +38,7 @@ class _ActionMenuSetPageState extends State<ActionMenuSetPage> {
.map<String>((i) => (i['value'] as ActionType).value)
.toList();
setting.put(SettingBoxKey.actionTypeSort, sortedTabbar);
GlobalData().actionTypeSort = sortedTabbar;
GlobalDataCache().actionTypeSort = sortedTabbar;
SmartDialog.showToast('操作成功');
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import '../../../models/common/gesture_mode.dart';
import '../../../utils/storage.dart';
@ -64,11 +64,11 @@ class _PlayGesturePageState extends State<PlayGesturePage> {
},
);
if (result != null) {
GlobalData().fullScreenGestureMode = FullScreenGestureMode
GlobalDataCache().fullScreenGestureMode = FullScreenGestureMode
.values
.firstWhere((element) => element.values == result);
fullScreenGestureMode =
GlobalData().fullScreenGestureMode.index;
GlobalDataCache().fullScreenGestureMode.index;
setting.put(
SettingBoxKey.fullScreenGestureMode, fullScreenGestureMode);
setState(() {});

View File

@ -9,7 +9,7 @@ import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart';
import '../../models/live/quality.dart';
@ -162,7 +162,7 @@ class _PlaySettingState extends State<PlaySetting> {
setKey: SettingBoxKey.enablePlayerControlAnimation,
defaultVal: true,
callFn: (bool val) {
GlobalData().enablePlayerControlAnimation = val;
GlobalDataCache().enablePlayerControlAnimation = val;
}),
SetSwitchItem(
title: '港澳台模式',

View File

@ -8,7 +8,7 @@ import 'package:pilipala/models/common/theme_type.dart';
import 'package:pilipala/pages/setting/pages/color_select.dart';
import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
import 'package:pilipala/pages/setting/widgets/slide_dialog.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart';
import '../../models/common/dynamic_badge_mode.dart';
@ -176,7 +176,7 @@ class _StyleSettingState extends State<StyleSetting> {
SettingBoxKey.defaultPicQa, picQuality);
Get.back();
settingController.picQuality.value = picQuality;
GlobalData().imgQuality = picQuality;
GlobalDataCache().imgQuality = picQuality;
SmartDialog.showToast('设置成功');
},
child: const Text('确定'),

View File

@ -20,7 +20,7 @@ import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import '../../../../http/user.dart';
@ -570,7 +570,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}
Widget actionGrid(BuildContext context, videoIntroController) {
final actionTypeSort = GlobalData().actionTypeSort;
final actionTypeSort = GlobalDataCache().actionTypeSort;
Widget progressWidget(progress) {
return SizedBox(

View File

@ -18,6 +18,7 @@ import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:status_bar_control/status_bar_control.dart';
@ -277,50 +278,19 @@ class PlPlayerController {
// 添加一个私有构造函数
PlPlayerController._internal(this.videoType) {
isOpenDanmu.value =
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
blockTypes =
localCache.get(LocalCacheKey.danmakuBlockType, defaultValue: []);
showArea = localCache.get(LocalCacheKey.danmakuShowArea, defaultValue: 0.5);
// 不透明度
opacityVal =
localCache.get(LocalCacheKey.danmakuOpacity, defaultValue: 1.0);
// 字体大小
fontSizeVal =
localCache.get(LocalCacheKey.danmakuFontScale, defaultValue: 1.0);
// 弹幕时间
danmakuDurationVal =
localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0);
// 描边粗细
strokeWidth = localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5);
playRepeat = PlayRepeat.values.toList().firstWhere(
(e) =>
e.value ==
videoStorage.get(VideoBoxKey.playRepeat,
defaultValue: PlayRepeat.pause.value),
);
_playbackSpeed.value =
videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0);
enableAutoLongPressSpeed = setting
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
if (!enableAutoLongPressSpeed) {
_longPressSpeed.value = videoStorage
.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0);
}
// 自定义倍速集合
speedsList = List<double>.from(videoStorage
.get(VideoBoxKey.customSpeedsList, defaultValue: <double>[]));
// 默认倍速
speedsList = List<double>.from(videoStorage
.get(VideoBoxKey.customSpeedsList, defaultValue: <double>[]));
//playSpeedSystem
final List<double> playSpeedSystem =
videoStorage.get(VideoBoxKey.playSpeedSystem, defaultValue: playSpeed);
// for (final PlaySpeed i in PlaySpeed.values) {
speedsList.addAll(playSpeedSystem);
// }
final cache = GlobalDataCache();
isOpenDanmu.value = cache.isOpenDanmu;
blockTypes = cache.blockTypes;
showArea = cache.showArea;
opacityVal = cache.opacityVal;
fontSizeVal = cache.fontSizeVal;
danmakuDurationVal = cache.danmakuDurationVal;
strokeWidth = cache.strokeWidth;
playRepeat = cache.playRepeat;
_playbackSpeed.value = cache.playbackSpeed;
enableAutoLongPressSpeed = cache.enableAutoLongPressSpeed;
_longPressSpeed.value = cache.longPressSpeed;
speedsList = cache.speedsList;
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
// if (status == PlayerStatus.playing) {
// WakelockPlus.enable();

View File

@ -19,7 +19,7 @@ import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:screen_brightness/screen_brightness.dart';
import '../../utils/global_data.dart';
import '../../utils/global_data_cache.dart';
import 'models/bottom_control_type.dart';
import 'models/bottom_progress_behavior.dart';
import 'widgets/app_bar_ani.dart';
@ -41,6 +41,7 @@ class PLVideoPlayer extends StatefulWidget {
this.customWidgets,
this.showEposideCb,
this.fullScreenCb,
this.alignment = Alignment.center,
super.key,
});
@ -55,6 +56,7 @@ class PLVideoPlayer extends StatefulWidget {
final List<Widget>? customWidgets;
final Function? showEposideCb;
final Function? fullScreenCb;
final Alignment? alignment;
@override
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
@ -88,7 +90,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
late bool enableBackgroundPlay;
late double screenWidth;
final FullScreenGestureMode fullScreenGestureMode =
GlobalData().fullScreenGestureMode;
GlobalDataCache().fullScreenGestureMode;
// 用于记录上一次全屏切换手势触发时间,避免误触
DateTime? lastFullScreenToggleTime;
@ -133,7 +135,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
screenWidth = Get.size.width;
animationController = AnimationController(
vsync: this,
duration: GlobalData().enablePlayerControlAnimation
duration: GlobalDataCache().enablePlayerControlAnimation
? const Duration(milliseconds: 150)
: const Duration(milliseconds: 10),
);
@ -393,6 +395,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
key: ValueKey(_.videoFit.value),
controller: videoController,
controls: NoVideoControls,
alignment: widget.alignment!,
pauseUponEnteringBackgroundMode: !enableBackgroundPlay,
resumeUponEnteringForegroundMode: true,
subtitleViewConfiguration: const SubtitleViewConfiguration(

View File

@ -1,24 +0,0 @@
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
import '../models/common/index.dart';
Box setting = GStrorage.setting;
class GlobalData {
int imgQuality = 10;
FullScreenGestureMode fullScreenGestureMode =
FullScreenGestureMode.values.last;
bool enablePlayerControlAnimation = true;
final bool enableMYBar =
setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
List<String> actionTypeSort = setting.get(SettingBoxKey.actionTypeSort,
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
// 私有构造函数
GlobalData._();
// 单例实例
static final GlobalData _instance = GlobalData._();
// 获取全局实例
factory GlobalData() => _instance;
}

View File

@ -0,0 +1,106 @@
import 'package:hive/hive.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/plugin/pl_player/models/play_speed.dart';
import 'package:pilipala/utils/storage.dart';
import '../models/common/index.dart';
Box setting = GStrorage.setting;
Box localCache = GStrorage.localCache;
Box videoStorage = GStrorage.video;
Box userInfoCache = GStrorage.userInfo;
class GlobalDataCache {
late int imgQuality;
late FullScreenGestureMode fullScreenGestureMode;
late bool enablePlayerControlAnimation;
late List<String> actionTypeSort;
/// 播放器相关
// 弹幕开关
late bool isOpenDanmu;
// 弹幕屏蔽类型
late List<dynamic> blockTypes;
// 弹幕展示区域
late double showArea;
// 弹幕透明度
late double opacityVal;
// 弹幕字体大小
late double fontSizeVal;
// 弹幕显示时间
late double danmakuDurationVal;
// 弹幕描边宽度
late double strokeWidth;
// 播放器循环模式
late PlayRepeat playRepeat;
// 播放器默认播放速度
late double playbackSpeed;
// 播放器自动长按速度
late bool enableAutoLongPressSpeed;
// 播放器长按速度
late double longPressSpeed;
// 播放器速度列表
late List<double> speedsList;
// 用户信息
UserInfoData? userInfo;
// 私有构造函数
GlobalDataCache._();
// 单例实例
static final GlobalDataCache _instance = GlobalDataCache._();
// 获取全局实例
factory GlobalDataCache() => _instance;
// 异步初始化方法
Future<void> initialize() async {
imgQuality = await setting.get(SettingBoxKey.defaultPicQa,
defaultValue: 10); // 设置全局变量
fullScreenGestureMode = FullScreenGestureMode.values[setting.get(
SettingBoxKey.fullScreenGestureMode,
defaultValue: FullScreenGestureMode.values.last.index) as int];
enablePlayerControlAnimation = setting
.get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true);
actionTypeSort = await setting.get(SettingBoxKey.actionTypeSort,
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
isOpenDanmu =
await setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
blockTypes =
await localCache.get(LocalCacheKey.danmakuBlockType, defaultValue: []);
showArea =
await localCache.get(LocalCacheKey.danmakuShowArea, defaultValue: 0.5);
opacityVal =
await localCache.get(LocalCacheKey.danmakuOpacity, defaultValue: 1.0);
fontSizeVal =
await localCache.get(LocalCacheKey.danmakuFontScale, defaultValue: 1.0);
danmakuDurationVal =
await localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0);
strokeWidth =
await localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5);
var defaultPlayRepeat = await videoStorage.get(VideoBoxKey.playRepeat,
defaultValue: PlayRepeat.pause.value);
playRepeat = PlayRepeat.values
.toList()
.firstWhere((e) => e.value == defaultPlayRepeat);
playbackSpeed =
await videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0);
enableAutoLongPressSpeed = await setting
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
if (!enableAutoLongPressSpeed) {
longPressSpeed = await videoStorage.get(VideoBoxKey.longPressSpeedDefault,
defaultValue: 2.0);
} else {
longPressSpeed = 2.0;
}
speedsList = List<double>.from(await videoStorage
.get(VideoBoxKey.customSpeedsList, defaultValue: <double>[]));
final List<double> playSpeedSystem = await videoStorage
.get(VideoBoxKey.playSpeedSystem, defaultValue: playSpeed);
speedsList.addAll(playSpeedSystem);
userInfo = userInfoCache.get('userInfoCache');
}
}

View File

@ -2,8 +2,6 @@ import 'dart:io';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pilipala/models/user/info.dart';
import '../models/common/gesture_mode.dart';
import 'global_data.dart';
class GStrorage {
static late final Box<dynamic> userInfo;
@ -42,13 +40,6 @@ class GStrorage {
);
// 视频设置
video = await Hive.openBox('video');
GlobalData().imgQuality =
setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); // 设置全局变量
GlobalData().fullScreenGestureMode = FullScreenGestureMode.values[
setting.get(SettingBoxKey.fullScreenGestureMode,
defaultValue: FullScreenGestureMode.values.last.index) as int];
GlobalData().enablePlayerControlAnimation = setting
.get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true);
}
static void regAdapter() {