Merge branch 'main' into pr/434

This commit is contained in:
orz12
2024-01-27 10:28:55 +08:00
43 changed files with 719 additions and 586 deletions

View File

@ -1,6 +1,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 '../../utils/storage.dart';
import '../constants.dart';
@ -12,55 +13,75 @@ class NetworkImgLayer extends StatelessWidget {
this.src,
required this.width,
required this.height,
this.cacheW,
this.cacheH,
this.type,
this.fadeOutDuration,
this.fadeInDuration,
// 图片质量 默认1%
this.quality,
this.origAspectRatio,
});
final String? src;
final double? width;
final double? height;
final double? cacheW;
final double? cacheH;
final double width;
final double height;
final String? type;
final Duration? fadeOutDuration;
final Duration? fadeInDuration;
final int? quality;
final double? origAspectRatio;
@override
Widget build(BuildContext context) {
final double pr = MediaQuery.of(context).devicePixelRatio;
final int picQuality =
setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10) as int;
final String imageUrl =
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? 100}q.webp';
int? memCacheWidth, memCacheHeight;
double aspectRatio = (width / height).toDouble();
// double pr = 2;
return src != ''
void setMemCacheSizes() {
if (aspectRatio > 1) {
memCacheHeight = height.cacheSize(context);
} else if (aspectRatio < 1) {
memCacheWidth = width.cacheSize(context);
} else {
if (origAspectRatio != null && origAspectRatio! > 1) {
memCacheWidth = width.cacheSize(context);
} else if (origAspectRatio != null && origAspectRatio! < 1) {
memCacheHeight = height.cacheSize(context);
} else {
memCacheWidth = width.cacheSize(context);
memCacheHeight = height.cacheSize(context);
}
}
}
setMemCacheSizes();
if (memCacheWidth == null && memCacheHeight == null) {
memCacheWidth = width.toInt();
}
return src != '' && src != null
? ClipRRect(
clipBehavior: Clip.hardEdge,
borderRadius: BorderRadius.circular(type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x),
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(
type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x,
),
child: CachedNetworkImage(
imageUrl:
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? picQuality}q.webp',
width: width ?? double.infinity,
height: height ?? double.infinity,
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
// memCacheHeight: (cacheH ?? height!).toInt(),
imageUrl: imageUrl,
width: width,
height: height,
memCacheWidth: memCacheWidth,
memCacheHeight: memCacheHeight,
fit: BoxFit.cover,
fadeOutDuration:
fadeOutDuration ?? const Duration(milliseconds: 200),
fadeOutDuration ?? const Duration(milliseconds: 120),
fadeInDuration:
fadeInDuration ?? const Duration(milliseconds: 200),
// filterQuality: FilterQuality.high,
fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.high,
errorWidget: (BuildContext context, String url, Object error) =>
placeholder(context),
placeholder: (BuildContext context, String url) =>
@ -72,9 +93,9 @@ class NetworkImgLayer extends StatelessWidget {
Widget placeholder(BuildContext context) {
return Container(
width: width ?? double.infinity,
height: height ?? double.infinity,
clipBehavior: Clip.hardEdge,
width: width,
height: height,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
borderRadius: BorderRadius.circular(type == 'avatar'
@ -84,13 +105,16 @@ class NetworkImgLayer extends StatelessWidget {
: StyleString.imgRadius.x),
),
child: Center(
child: Image.asset(
type == 'avatar'
? 'assets/images/noface.jpeg'
: 'assets/images/loading.png',
width: 300,
height: 300,
)),
child: Image.asset(
type == 'avatar'
? 'assets/images/noface.jpeg'
: 'assets/images/loading.png',
width: width,
height: height,
cacheWidth: width.cacheSize(context),
cacheHeight: height.cacheSize(context),
),
),
);
}
}

View File

@ -69,7 +69,7 @@ class VideoCardH extends StatelessWidget {
final double width = (boxConstraints.maxWidth -
StyleString.cardSpace *
6 /
MediaQuery.of(context).textScaleFactor) /
MediaQuery.textScalerOf(context).scale(1.0)) /
2;
return Container(
constraints: const BoxConstraints(minHeight: 88),

View File

@ -326,7 +326,8 @@ class VideoStat extends StatelessWidget {
maxLines: 1,
text: TextSpan(
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
fontSize: MediaQuery.textScalerOf(context)
.scale(Theme.of(context).textTheme.labelSmall!.fontSize!),
color: Theme.of(context).colorScheme.outline,
),
children: [

View File

@ -185,7 +185,7 @@ class Api {
static const String searchDefault = '/x/web-interface/wbi/search/default';
// 搜索关键词
static const String serachSuggest =
static const String searchSuggest =
'https://s.search.bilibili.com/main/suggest';
// 分类搜索
@ -467,4 +467,7 @@ class Api {
/// page_size
static const getSeasonDetailApi =
'/x/polymer/web-space/seasons_archives_list';
/// 获取未读动态数
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
}

17
lib/http/common.dart Normal file
View File

@ -0,0 +1,17 @@
import 'index.dart';
class CommonHttp {
static Future unReadDynamic() async {
var res = await Request().get(Api.getUnreadDynamic,
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']['dyn_basic_infos']};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
}

View File

@ -8,6 +8,7 @@ import 'package:dio/io.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/id_utils.dart';
import '../utils/storage.dart';
import '../utils/utils.dart';
import 'constants.dart';
@ -77,10 +78,11 @@ class Request {
static setOptionsHeaders(userInfo, bool status) {
if (status) {
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
dio.options.headers['x-bili-aurora-eid'] =
IdUtils.genAuroraEid(userInfo.mid);
}
dio.options.headers['env'] = 'prod';
dio.options.headers['app-key'] = 'android64';
dio.options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
dio.options.headers['x-bili-aurora-zone'] = 'sh001';
dio.options.headers['referer'] = 'https://www.bilibili.com/';
}

View File

@ -70,14 +70,14 @@ class ApiInterceptor extends Interceptor {
case DioExceptionType.sendTimeout:
return '发送请求超时,请检查网络设置';
case DioExceptionType.unknown:
final String res = await checkConect();
final String res = await checkConnect();
return '$res \n 网络异常,请稍后重试!';
// default:
// return 'Dio异常';
}
}
static Future<String> checkConect() async {
static Future<String> checkConnect() async {
final ConnectivityResult connectivityResult =
await Connectivity().checkConnectivity();
if (connectivityResult == ConnectivityResult.mobile) {

View File

@ -36,7 +36,7 @@ class SearchHttp {
// 获取搜索建议
static Future searchSuggest({required term}) async {
var res = await Request().get(Api.serachSuggest,
var res = await Request().get(Api.searchSuggest,
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
if (res.data is String) {
Map<String, dynamic> resultMap = json.decode(res.data);

View File

@ -158,9 +158,8 @@ class MyApp extends StatelessWidget {
return FlutterSmartDialog(
toastBuilder: (String msg) => CustomToast(msg: msg),
child: MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor:
MediaQuery.of(context).textScaleFactor * textScale),
data: MediaQuery.of(context)
.copyWith(textScaler: TextScaler.linear(textScale)),
child: child!,
),
);

View File

@ -9,6 +9,7 @@ enum TabType { live, rcmd, hot, bangumi }
extension TabTypeDesc on TabType {
String get description => ['直播', '推荐', '热门', '番剧'][index];
String get id => ['live', 'rcmd', 'hot', 'bangumi'][index];
}
List tabsConfig = [

View File

@ -1,8 +1,3 @@
import 'package:hive/hive.dart';
part 'result.g.dart';
@HiveType(typeId: 0)
class RecVideoItemAppModel {
RecVideoItemAppModel({
this.id,
@ -27,47 +22,27 @@ class RecVideoItemAppModel {
this.adInfo,
});
@HiveField(0)
int? id;
@HiveField(1)
int? aid;
@HiveField(2)
String? bvid;
@HiveField(3)
int? cid;
@HiveField(4)
String? pic;
@HiveField(5)
RcmdStat? stat;
@HiveField(6)
int? duration;
@HiveField(7)
String? title;
@HiveField(8)
int? isFollowed;
@HiveField(9)
RcmdOwner? owner;
@HiveField(10)
RcmdReason? rcmdReason;
@HiveField(11)
String? goto;
@HiveField(12)
int? param;
@HiveField(13)
String? uri;
@HiveField(14)
String? talkBack;
// 番剧
@HiveField(15)
String? bangumiView;
@HiveField(16)
String? bangumiFollow;
@HiveField(17)
String? bangumiBadge;
@HiveField(18)
String? cardType;
@HiveField(19)
Map? adInfo;
RecVideoItemAppModel.fromJson(Map<String, dynamic> json) {
@ -116,18 +91,14 @@ class RecVideoItemAppModel {
}
}
@HiveType(typeId: 1)
class RcmdStat {
RcmdStat({
this.view,
this.like,
this.danmu,
});
@HiveField(0)
String? view;
@HiveField(1)
String? like;
@HiveField(2)
String? danmu;
RcmdStat.fromJson(Map<String, dynamic> json) {
@ -136,13 +107,10 @@ class RcmdStat {
}
}
@HiveType(typeId: 2)
class RcmdOwner {
RcmdOwner({this.name, this.mid});
@HiveField(0)
String? name;
@HiveField(1)
int? mid;
RcmdOwner.fromJson(Map<String, dynamic> json) {
@ -155,13 +123,11 @@ class RcmdOwner {
}
}
@HiveType(typeId: 8)
class RcmdReason {
RcmdReason({
this.content,
});
@HiveField(0)
String? content;
RcmdReason.fromJson(Map<String, dynamic> json) {

View File

@ -1,209 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'result.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class RecVideoItemAppModelAdapter extends TypeAdapter<RecVideoItemAppModel> {
@override
final int typeId = 0;
@override
RecVideoItemAppModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RecVideoItemAppModel(
id: fields[0] as int?,
aid: fields[1] as int?,
bvid: fields[2] as String?,
cid: fields[3] as int?,
pic: fields[4] as String?,
stat: fields[5] as RcmdStat?,
duration: fields[6] as int?,
title: fields[7] as String?,
isFollowed: fields[8] as int?,
owner: fields[9] as RcmdOwner?,
rcmdReason: fields[10] as RcmdReason?,
goto: fields[11] as String?,
param: fields[12] as int?,
uri: fields[13] as String?,
talkBack: fields[14] as String?,
bangumiView: fields[15] as String?,
bangumiFollow: fields[16] as String?,
bangumiBadge: fields[17] as String?,
cardType: fields[18] as String?,
adInfo: (fields[19] as Map?)?.cast<dynamic, dynamic>(),
);
}
@override
void write(BinaryWriter writer, RecVideoItemAppModel obj) {
writer
..writeByte(20)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.aid)
..writeByte(2)
..write(obj.bvid)
..writeByte(3)
..write(obj.cid)
..writeByte(4)
..write(obj.pic)
..writeByte(5)
..write(obj.stat)
..writeByte(6)
..write(obj.duration)
..writeByte(7)
..write(obj.title)
..writeByte(8)
..write(obj.isFollowed)
..writeByte(9)
..write(obj.owner)
..writeByte(10)
..write(obj.rcmdReason)
..writeByte(11)
..write(obj.goto)
..writeByte(12)
..write(obj.param)
..writeByte(13)
..write(obj.uri)
..writeByte(14)
..write(obj.talkBack)
..writeByte(15)
..write(obj.bangumiView)
..writeByte(16)
..write(obj.bangumiFollow)
..writeByte(17)
..write(obj.bangumiBadge)
..writeByte(18)
..write(obj.cardType)
..writeByte(19)
..write(obj.adInfo);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RecVideoItemAppModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class RcmdStatAdapter extends TypeAdapter<RcmdStat> {
@override
final int typeId = 1;
@override
RcmdStat read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RcmdStat(
view: fields[0] as String?,
like: fields[1] as String?,
danmu: fields[2] as String?,
);
}
@override
void write(BinaryWriter writer, RcmdStat obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.view)
..writeByte(1)
..write(obj.like)
..writeByte(2)
..write(obj.danmu);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RcmdStatAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class RcmdOwnerAdapter extends TypeAdapter<RcmdOwner> {
@override
final int typeId = 2;
@override
RcmdOwner read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RcmdOwner(
name: fields[0] as String?,
mid: fields[1] as int?,
);
}
@override
void write(BinaryWriter writer, RcmdOwner obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.mid);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RcmdOwnerAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class RcmdReasonAdapter extends TypeAdapter<RcmdReason> {
@override
final int typeId = 8;
@override
RcmdReason read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RcmdReason(
content: fields[0] as String?,
);
}
@override
void write(BinaryWriter writer, RcmdReason obj) {
writer
..writeByte(1)
..writeByte(0)
..write(obj.content);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RcmdReasonAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@ -86,6 +86,14 @@ class _AboutPageState extends State<AboutPage> {
style: subTitleStyle,
),
),
ListTile(
onTap: () => _aboutController.webSiteUrl(),
title: const Text('访问官网'),
trailing: Text(
'https://pilipalanet.mysxl.cn',
style: subTitleStyle,
),
),
ListTile(
onTap: () => _aboutController.panDownload(),
title: const Text('网盘下载'),
@ -244,4 +252,12 @@ class AboutController extends GetxController {
print(e);
}
}
// 官网
webSiteUrl() {
launchUrl(
Uri.parse('https://pilipalanet.mysxl.cn'),
mode: LaunchMode.externalApplication,
);
}
}

View File

@ -224,7 +224,7 @@ class _BangumiPageState extends State<BangumiPage>
// 列数
crossAxisCount: 3,
mainAxisExtent: Get.size.width / 3 / 0.65 +
32 * MediaQuery.of(context).textScaleFactor,
MediaQuery.textScalerOf(context).scale(32.0),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {

View File

@ -1,5 +1,6 @@
// 内容
import 'package:flutter/material.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/preview/index.dart';
@ -48,6 +49,13 @@ class _ContentState extends State<Content> {
WidgetSpan(
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth.truncateToDouble();
double maxHeight = box.maxWidth * 0.6; // 设置最大高度
double height = maxWidth *
0.5 *
(pictureItem.height != null && pictureItem.width != null
? pictureItem.height! / pictureItem.width!
: 1);
return GestureDetector(
onTap: () {
showDialog(
@ -58,18 +66,29 @@ class _ContentState extends State<Content> {
},
);
},
child: Padding(
padding: const EdgeInsets.only(top: 4),
child: NetworkImgLayer(
src: pictureItem.url,
child: Container(
padding: const EdgeInsets.only(top: 4),
constraints: BoxConstraints(maxHeight: maxHeight),
width: box.maxWidth / 2,
height: box.maxWidth *
0.5 *
(pictureItem.height != null && pictureItem.width != null
? pictureItem.height! / pictureItem.width!
: 1),
),
),
height: height,
child: Stack(
children: [
Positioned.fill(
child: NetworkImgLayer(
src: pictureItem.url,
width: maxWidth / 2,
height: height,
),
),
height > maxHeight
? const PBadge(
text: '长图',
right: 8,
bottom: 8,
)
: const SizedBox(),
],
)),
);
},
),
@ -83,6 +102,7 @@ class _ContentState extends State<Content> {
list.add(
LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth.truncateToDouble();
return GestureDetector(
onTap: () {
showDialog(
@ -95,8 +115,10 @@ class _ContentState extends State<Content> {
},
child: NetworkImgLayer(
src: pics[i].url,
width: box.maxWidth,
height: box.maxWidth,
width: maxWidth,
height: maxWidth,
origAspectRatio:
pics[i].width!.toInt() / pics[i].height!.toInt(),
),
);
},
@ -107,7 +129,7 @@ class _ContentState extends State<Content> {
WidgetSpan(
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth;
double maxWidth = box.maxWidth.truncateToDouble();
double crossCount = len < 3 ? 2 : 3;
double height = maxWidth /
crossCount *

View File

@ -5,15 +5,17 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/common/tab_type.dart';
import 'package:pilipala/utils/storage.dart';
import '../../http/index.dart';
class HomeController extends GetxController with GetTickerProviderStateMixin {
bool flag = false;
late List tabs;
late RxList tabs = [].obs;
RxInt initialIndex = 1.obs;
late TabController tabController;
late List tabsCtrList;
late List<Widget> tabsPageList;
Box userInfoCache = GStrorage.userInfo;
Box settingStorage = GStrorage.setting;
RxBool userLogin = false.obs;
RxString userFace = ''.obs;
var userInfo;
@ -21,6 +23,9 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
late final StreamController<bool> searchBarStream =
StreamController<bool>.broadcast();
late bool hideSearchBar;
late List defaultTabs;
late List<String> tabbarSort;
RxString defaultSearch = ''.obs;
@override
void onInit() {
@ -28,19 +33,13 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
userFace.value = userInfo != null ? userInfo.face : '';
// 进行tabs配置
tabs = tabsConfig;
tabsCtrList = tabsConfig.map((e) => e['ctr']).toList();
tabsPageList = tabsConfig.map<Widget>((e) => e['page']).toList();
tabController = TabController(
initialIndex: initialIndex.value,
length: tabs.length,
vsync: this,
);
setTabConfig();
hideSearchBar =
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
searchDefault();
}
}
void onRefresh() {
@ -62,4 +61,49 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
if (val) return;
userFace.value = userInfo != null ? userInfo.face : '';
}
void setTabConfig() async {
defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
tabs.value = defaultTabs
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
.toList();
if (tabbarSort.contains(TabType.rcmd.id)) {
initialIndex.value = tabbarSort.indexOf(TabType.rcmd.id);
} else {
initialIndex.value = 0;
}
tabsCtrList = tabs.map((e) => e['ctr']).toList();
tabsPageList = tabs.map<Widget>((e) => e['page']).toList();
tabController = TabController(
initialIndex: initialIndex.value,
length: tabs.length,
vsync: this,
);
// 监听 tabController 切换
tabController.animation!.addListener(() {
if (tabController.indexIsChanging) {
if (initialIndex.value != tabController.index) {
initialIndex.value = tabController.index;
}
} else {
final int temp = tabController.animation!.value.round();
if (initialIndex.value != temp) {
initialIndex.value = temp;
tabController.index = initialIndex.value;
}
}
});
}
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,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import './controller.dart';
@ -46,6 +46,13 @@ class _HomePageState extends State<HomePage>
@override
Widget build(BuildContext context) {
super.build(context);
Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
// 设置状态栏图标的亮度
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarIconBrightness: currentBrightness == Brightness.light
? Brightness.dark
: Brightness.light,
));
return Scaffold(
extendBody: true,
extendBodyBehindAppBar: true,
@ -82,7 +89,11 @@ class _HomePageState extends State<HomePage>
ctr: _homeController,
callback: showUserBottomSheet,
),
const CustomTabs(),
if (_homeController.tabs.length > 1) ...[
const CustomTabs(),
] else ...[
const SizedBox(height: 6),
],
Expanded(
child: TabBarView(
controller: _homeController.tabController,
@ -129,9 +140,10 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
height: snapshot.data ? top + 52 : top,
padding: EdgeInsets.fromLTRB(14, top, 14, 0),
padding: EdgeInsets.fromLTRB(14, top + 6, 14, 0),
child: UserInfoWidget(
top: top,
ctr: ctr,
userLogin: isUserLoggedIn,
userFace: ctr?.userFace.value,
callback: () => callback!(),
@ -150,18 +162,20 @@ class UserInfoWidget extends StatelessWidget {
required this.userLogin,
required this.userFace,
required this.callback,
required this.ctr,
}) : super(key: key);
final double top;
final RxBool userLogin;
final String? userFace;
final VoidCallback? callback;
final HomeController? ctr;
@override
Widget build(BuildContext context) {
return Row(
children: [
const SearchBar(),
SearchBar(ctr: ctr),
if (userLogin.value) ...[
const SizedBox(width: 4),
ClipRect(
@ -242,17 +256,6 @@ class CustomTabs extends StatefulWidget {
class _CustomTabsState extends State<CustomTabs> {
final HomeController _homeController = Get.put(HomeController());
int currentTabIndex = 1;
@override
void initState() {
super.initState();
_homeController.tabController.addListener(listen);
}
void listen() {
_homeController.initialIndex.value = _homeController.tabController.index;
}
void onTap(int index) {
feedBack();
@ -263,34 +266,30 @@ class _CustomTabsState extends State<CustomTabs> {
_homeController.tabController.index = index;
}
@override
void dispose() {
super.dispose();
_homeController.tabController.removeListener(listen);
}
@override
Widget build(BuildContext context) {
return Container(
height: 44,
margin: const EdgeInsets.only(top: 4),
child: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 14.0),
scrollDirection: Axis.horizontal,
itemCount: _homeController.tabs.length,
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(width: 10);
},
itemBuilder: (BuildContext context, int index) {
String label = _homeController.tabs[index]['label'];
return Obx(
() => CustomChip(
onTap: () => onTap(index),
label: label,
selected: index == _homeController.initialIndex.value,
),
);
},
child: Obx(
() => ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 14.0),
scrollDirection: Axis.horizontal,
itemCount: _homeController.tabs.length,
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(width: 10);
},
itemBuilder: (BuildContext context, int index) {
String label = _homeController.tabs[index]['label'];
return Obx(
() => CustomChip(
onTap: () => onTap(index),
label: label,
selected: index == _homeController.initialIndex.value,
),
);
},
),
),
);
}
@ -338,11 +337,15 @@ class CustomChip extends StatelessWidget {
}
class SearchBar extends StatelessWidget {
const SearchBar({super.key});
const SearchBar({
Key? key,
required this.ctr,
}) : super(key: key);
final HomeController? ctr;
@override
Widget build(BuildContext context) {
final SSearchController searchController = Get.put(SSearchController());
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return Expanded(
child: Container(
@ -356,7 +359,10 @@ class SearchBar extends StatelessWidget {
color: colorScheme.onSecondaryContainer.withOpacity(0.05),
child: InkWell(
splashColor: colorScheme.primaryContainer.withOpacity(0.3),
onTap: () => Get.toNamed('/search'),
onTap: () => Get.toNamed(
'/search',
parameters: {'hintText': ctr!.defaultSearch.value},
),
child: Row(
children: [
const SizedBox(width: 14),
@ -365,14 +371,12 @@ class SearchBar extends StatelessWidget {
color: colorScheme.onSecondaryContainer,
),
const SizedBox(width: 10),
Expanded(
child: Obx(
() => Text(
searchController.defaultSearch.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: colorScheme.outline),
),
Obx(
() => Text(
ctr!.defaultSearch.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: colorScheme.outline),
),
),
],

View File

@ -162,8 +162,9 @@ class _LivePageState extends State<LivePage>
crossAxisCount: crossAxisCount,
mainAxisExtent:
Get.size.width / crossAxisCount / StyleString.aspectRatio +
(crossAxisCount == 1 ? 48 : 68) *
MediaQuery.of(context).textScaleFactor,
MediaQuery.textScalerOf(context).scale(
(crossAxisCount == 1 ? 48 : 68),
),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {

View File

@ -1,9 +1,11 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/common.dart';
import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/media/index.dart';
@ -27,6 +29,7 @@ class MainController extends GetxController {
size: 21,
),
'label': "首页",
'count': 0,
},
{
'icon': const Icon(
@ -38,6 +41,7 @@ class MainController extends GetxController {
size: 21,
),
'label': "动态",
'count': 0,
},
{
'icon': const Icon(
@ -49,6 +53,7 @@ class MainController extends GetxController {
size: 21,
),
'label': "媒体库",
'count': 0,
}
].obs;
final StreamController<bool> bottomBarStream =
@ -56,6 +61,10 @@ class MainController extends GetxController {
Box setting = GStrorage.setting;
DateTime? _lastPressedAt;
late bool hideTabBar;
late PageController pageController;
int selectedIndex = 0;
Box userInfoCache = GStrorage.userInfo;
RxBool userLogin = false.obs;
@override
void onInit() {
@ -64,17 +73,47 @@ class MainController extends GetxController {
Utils.checkUpdata();
}
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
var userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
getUnreadDynamic();
}
Future<bool> onBackPressed(BuildContext context) {
void onBackPressed(BuildContext context) {
if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt!) >
const Duration(seconds: 2)) {
// 两次点击时间间隔超过2秒重新记录时间戳
_lastPressedAt = DateTime.now();
if (selectedIndex != 0) {
pageController.jumpTo(0);
}
SmartDialog.showToast("再按一次退出Pili");
return Future.value(false); // 不退出应用
return; // 不退出应用
}
return Future.value(true); // 退出应用
SystemNavigator.pop(); // 退出应用
}
void getUnreadDynamic() async {
if (!userLogin.value) {
return;
}
int dynamicItemIndex =
navigationBars.indexWhere((item) => item['label'] == "动态");
var res = await CommonHttp.unReadDynamic();
var data = res['data'];
if (dynamicItemIndex != -1) {
navigationBars[dynamicItemIndex]['count'] =
data == null ? 0 : data.length; // 修改 count 属性为新的值
}
navigationBars.refresh();
}
void clearUnread() async {
int dynamicItemIndex =
navigationBars.indexWhere((item) => item['label'] == "动态");
if (dynamicItemIndex != -1) {
navigationBars[dynamicItemIndex]['count'] = 0; // 修改 count 属性为新的值
}
navigationBars.refresh();
}
}

View File

@ -24,8 +24,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
final DynamicsController _dynamicController = Get.put(DynamicsController());
final MediaController _mediaController = Get.put(MediaController());
PageController? _pageController;
int selectedIndex = 0;
int? _lastSelectTime; //上次点击时间
Box setting = GStrorage.setting;
late bool enableMYBar;
@ -34,13 +32,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
void initState() {
super.initState();
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
_pageController = PageController(initialPage: selectedIndex);
_mainController.pageController =
PageController(initialPage: _mainController.selectedIndex);
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
}
void setIndex(int value) async {
feedBack();
_pageController!.jumpToPage(value);
_mainController.pageController.jumpToPage(value);
var currentPage = _mainController.pages[value];
if (currentPage is HomePage) {
if (_homeController.flag) {
@ -68,6 +67,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
}
_dynamicController.flag = true;
_mainController.clearUnread();
} else {
_dynamicController.flag = false;
}
@ -94,14 +94,17 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
localCache.put('sheetHeight', sheetHeight);
localCache.put('statusBarHeight', statusBarHeight);
return PopScope(
onPopInvoked: (bool status) => _mainController.onBackPressed(context),
canPop: false,
onPopInvoked: (bool didPop) async {
_mainController.onBackPressed(context);
},
child: Scaffold(
extendBody: true,
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
controller: _mainController.pageController,
onPageChanged: (index) {
selectedIndex = index;
_mainController.selectedIndex = index;
setState(() {});
},
children: _mainController.pages,
@ -116,36 +119,48 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1),
child: enableMYBar
? NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return NavigationDestination(
icon: e['icon'],
selectedIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
)
: BottomNavigationBar(
currentIndex: selectedIndex,
onTap: (value) => setIndex(value),
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
items: [
..._mainController.navigationBars.map((e) {
return BottomNavigationBarItem(
icon: e['icon'],
activeIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
child: Obx(
() => enableMYBar
? NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: _mainController.selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return NavigationDestination(
icon: Badge(
label: Text(e['count'].toString()),
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: e['count'] > 0,
child: e['icon'],
),
selectedIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
)
: BottomNavigationBar(
currentIndex: _mainController.selectedIndex,
onTap: (value) => setIndex(value),
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
items: [
..._mainController.navigationBars.map((e) {
return BottomNavigationBarItem(
icon: Badge(
label: Text(e['count'].toString()),
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: e['count'] > 0,
child: e['icon'],
),
activeIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
),
);
},
),

View File

@ -163,7 +163,7 @@ class _MediaPageState extends State<MediaPage>
// const SizedBox(height: 10),
SizedBox(
width: double.infinity,
height: 200 * MediaQuery.of(context).textScaleFactor,
height: MediaQuery.textScalerOf(context).scale(200),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {

View File

@ -13,7 +13,6 @@ class RcmdController extends GetxController {
// RxList<RecVideoItemModel> webVideoList = <RecVideoItemModel>[].obs;
bool isLoadingMore = true;
OverlayEntry? popupDialog;
Box recVideo = GStrorage.recVideo;
Box setting = GStrorage.setting;
RxInt crossAxisCount = 2.obs;
late bool enableSaveLastData;
@ -25,15 +24,6 @@ class RcmdController extends GetxController {
super.onInit();
crossAxisCount.value =
setting.get(SettingBoxKey.customRows, defaultValue: 2);
// 读取app端缓存内容
// if (recVideo.get('cacheList') != null &&
// recVideo.get('cacheList').isNotEmpty) {
// List<RecVideoItemAppModel> list = [];
// for (var i in recVideo.get('cacheList')) {
// list.add(i);
// }
// videoList.value = list;
// }
enableSaveLastData =
setting.get(SettingBoxKey.enableSaveLastData, defaultValue: false);
defaultRcmdType =
@ -84,10 +74,6 @@ class RcmdController extends GetxController {
} else if (type == 'onLoad') {
videoList.addAll(res['data']);
}
// 目前仅支持app端系列保存缓存
if (defaultRcmdType != 'web') {
recVideo.put('cacheList', res['data']);
}
_currentPage += 1;
// 若videoList数量太小可能会影响翻页此时再次请求
// 为避免请求到的数据太少时还在反复请求要求本次返回数据大于1条才触发

View File

@ -2,7 +2,6 @@ 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';
@ -27,9 +26,6 @@ class SSearchController extends GetxController {
@override
void onInit() {
super.onInit();
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
searchDefault();
}
// 其他页面跳转过来
if (Get.parameters.keys.isNotEmpty) {
if (Get.parameters['keyword'] != null) {
@ -130,12 +126,4 @@ 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

@ -26,10 +26,9 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(builder: (context, boxConstraints) {
double width = (boxConstraints.maxWidth -
StyleString.cardSpace *
6 /
MediaQuery.of(context).textScaleFactor) /
2;
StyleString.cardSpace *
6 /
MediaQuery.textScalerOf(context).scale(2.0));
return Container(
constraints: const BoxConstraints(minHeight: 88),
height: width / StyleString.aspectRatio,

View File

@ -17,7 +17,7 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
mainAxisSpacing: StyleString.cardSpace + 3,
mainAxisExtent:
MediaQuery.sizeOf(context).width / 2 / StyleString.aspectRatio +
66 * MediaQuery.of(context).textScaleFactor),
MediaQuery.textScalerOf(context).scale(66.0)),
itemCount: list.length,
itemBuilder: (context, index) {
return LiveItem(liveItem: list![index]);

View File

@ -67,11 +67,11 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
TextSpan(
text: i['text'],
style: TextStyle(
fontSize: Theme.of(context)
fontSize: MediaQuery.textScalerOf(context)
.scale(Theme.of(context)
.textTheme
.titleSmall!
.fontSize! *
MediaQuery.of(context).textScaleFactor,
.fontSize!),
fontWeight: FontWeight.bold,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary

View File

@ -90,7 +90,7 @@ class SearchVideoPanel extends StatelessWidget {
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () => controller.onShowFilterDialog(),
onPressed: () => controller.onShowFilterDialog(ctr),
icon: Icon(
Icons.filter_list_outlined,
size: 18,
@ -175,7 +175,7 @@ class VideoPanelController extends GetxController {
super.onInit();
}
onShowFilterDialog() {
onShowFilterDialog(searchPanelCtr) {
SmartDialog.show(
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) {
@ -199,7 +199,8 @@ class VideoPanelController extends GetxController {
SmartDialog.dismiss();
SmartDialog.showToast("${i['label']}」的筛选结果");
SearchPanelController ctr =
Get.find<SearchPanelController>(tag: 'video');
Get.find<SearchPanelController>(
tag: 'video${searchPanelCtr.keyword!}');
ctr.duration.value = i['value'];
SmartDialog.showLoading(msg: 'loooad');
await ctr.onRefresh();

View File

@ -86,7 +86,8 @@ class _SearchResultPageState extends State<SearchResultPage>
onTap: (index) {
if (index == _searchResultController!.tabIndex) {
Get.find<SearchPanelController>(
tag: SearchType.values[index].type)
tag: SearchType.values[index].type +
_searchResultController!.keyword!)
.animateToTop();
}

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/common/tab_type.dart';
import 'package:pilipala/utils/storage.dart';
class TabbarSetPage extends StatefulWidget {
const TabbarSetPage({super.key});
@override
State<TabbarSetPage> createState() => _TabbarSetPageState();
}
class _TabbarSetPageState extends State<TabbarSetPage> {
Box settingStorage = GStrorage.setting;
late List defaultTabs;
late List<String> tabbarSort;
@override
void initState() {
super.initState();
defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
}
void saveEdit() {
List<String> sortedTabbar = defaultTabs
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
.map<String>((i) => (i['type'] as TabType).id)
.toList();
if (sortedTabbar.isEmpty) {
SmartDialog.showToast('请至少设置一项!');
return;
}
settingStorage.put(SettingBoxKey.tabbarSort, sortedTabbar);
SmartDialog.showToast('保存成功,下次启动时生效');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final tabsItem = defaultTabs.removeAt(oldIndex);
defaultTabs.insert(newIndex, tabsItem);
});
}
@override
Widget build(BuildContext context) {
final listTiles = [
for (int i = 0; i < defaultTabs.length; i++) ...[
CheckboxListTile(
key: Key(defaultTabs[i]['label']),
value: tabbarSort.contains((defaultTabs[i]['type'] as TabType).id),
onChanged: (bool? newValue) {
String tabTypeId = (defaultTabs[i]['type'] as TabType).id;
if (!newValue!) {
tabbarSort.remove(tabTypeId);
} else {
tabbarSort.add(tabTypeId);
}
setState(() {});
},
title: Text(defaultTabs[i]['label']),
secondary: const Icon(Icons.drag_indicator_rounded),
)
]
];
return Scaffold(
appBar: AppBar(
title: const Text('Tabbar编辑'),
actions: [
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
const SizedBox(width: 12)
],
),
body: ReorderableListView(
onReorder: onReorder,
physics: const NeverScrollableScrollPhysics(),
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 30,
),
children: listTiles,
),
);
}
}

View File

@ -254,6 +254,11 @@ class _StyleSettingState extends State<StyleSetting> {
onTap: () => Get.toNamed('/fontSizeSetting'),
title: Text('字体大小', style: titleStyle),
),
ListTile(
dense: false,
onTap: () => Get.toNamed('/tabbarSetting'),
title: Text('首页tabbar', style: titleStyle),
),
if (Platform.isAndroid)
ListTile(
dense: false,

View File

@ -9,7 +9,8 @@ import './controller.dart';
class RelatedVideoPanel extends StatelessWidget {
final ReleatedController _releatedController =
Get.put(ReleatedController(), tag: Get.arguments['heroTag']);
Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
RelatedVideoPanel({super.key});
@override
Widget build(BuildContext context) {

View File

@ -849,6 +849,13 @@ InlineSpan buildContent(
WidgetSpan(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints box) {
double maxHeight = box.maxWidth * 0.6; // 设置最大高度
// double width = (box.maxWidth / 2).truncateToDouble();
double height = ((box.maxWidth /
2 *
pictureItem['img_height'] /
pictureItem['img_width']))
.truncateToDouble();
return GestureDetector(
onTap: () {
showDialog(
@ -859,15 +866,28 @@ InlineSpan buildContent(
},
);
},
child: Padding(
child: Container(
padding: const EdgeInsets.only(top: 4),
child: NetworkImgLayer(
src: pictureItem['img_src'],
width: box.maxWidth / 2,
height: box.maxWidth *
0.5 *
pictureItem['img_height'] /
pictureItem['img_width'],
constraints: BoxConstraints(maxHeight: maxHeight),
width: box.maxWidth / 2,
height: height,
child: Stack(
children: [
Positioned.fill(
child: NetworkImgLayer(
src: pictureItem['img_src'],
width: box.maxWidth / 2,
height: height,
),
),
height > maxHeight
? const PBadge(
text: '长图',
right: 8,
bottom: 8,
)
: const SizedBox(),
],
),
),
);

View File

@ -2,12 +2,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/video.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
class VideoReplyNewDialog extends StatefulWidget {
final int? oid;
@ -34,25 +32,16 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
final TextEditingController _replyContentController = TextEditingController();
final FocusNode replyContentFocusNode = FocusNode();
final GlobalKey _formKey = GlobalKey<FormState>();
double _keyboardHeight = 0.0; // 键盘高度
final _debouncer = Debouncer(milliseconds: 100); // 设置延迟时间
bool ableClean = false;
Timer? timer;
Box localCache = GStrorage.localCache;
late double sheetHeight;
@override
void initState() {
super.initState();
// 监听输入框聚焦
// replyContentFocusNode.addListener(_onFocus);
_replyContentController.addListener(_printLatestValue);
// 界面观察者 必须
WidgetsBinding.instance.addObserver(this);
// 自动聚焦
_autoFocus();
sheetHeight = localCache.get('sheetHeight');
}
_autoFocus() async {
@ -62,12 +51,6 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
}
}
_printLatestValue() {
setState(() {
ableClean = _replyContentController.text != '';
});
}
Future submitReplyAdd() async {
feedBack();
String message = _replyContentController.text;
@ -90,24 +73,6 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
}
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
WidgetsBinding.instance.addPostFrameCallback((_) {
// 键盘高度
final viewInsets = EdgeInsets.fromViewPadding(
View.of(context).viewInsets, View.of(context).devicePixelRatio);
_debouncer.run(() {
if (mounted) {
setState(() {
_keyboardHeight =
_keyboardHeight == 0.0 ? viewInsets.bottom : _keyboardHeight;
});
}
});
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
@ -117,8 +82,10 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
@override
Widget build(BuildContext context) {
double keyboardHeight = EdgeInsets.fromViewPadding(
View.of(context).viewInsets, View.of(context).devicePixelRatio)
.bottom;
return Container(
height: 500,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
@ -130,26 +97,32 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 200,
minHeight: 120,
),
child: Container(
padding: const EdgeInsets.only(
top: 6, right: 15, left: 15, bottom: 10),
child: Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: TextField(
controller: _replyContentController,
minLines: 1,
maxLines: null,
autofocus: false,
focusNode: replyContentFocusNode,
decoration: const InputDecoration(
hintText: "输入回复内容",
border: InputBorder.none,
hintStyle: TextStyle(
fontSize: 14,
)),
style: Theme.of(context).textTheme.bodyLarge,
top: 12, right: 15, left: 15, bottom: 10),
child: SingleChildScrollView(
child: Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: TextField(
controller: _replyContentController,
minLines: 1,
maxLines: null,
autofocus: false,
focusNode: replyContentFocusNode,
decoration: const InputDecoration(
hintText: "输入回复内容",
border: InputBorder.none,
hintStyle: TextStyle(
fontSize: 14,
)),
style: Theme.of(context).textTheme.bodyLarge,
),
),
),
),
@ -168,22 +141,23 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
width: 36,
height: 36,
child: IconButton(
onPressed: () {
FocusScope.of(context)
.requestFocus(replyContentFocusNode);
},
icon: Icon(Icons.keyboard,
size: 22,
color: Theme.of(context).colorScheme.onBackground),
highlightColor:
Theme.of(context).colorScheme.onInverseSurface,
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((states) {
return Theme.of(context).highlightColor;
}),
)),
onPressed: () {
FocusScope.of(context)
.requestFocus(replyContentFocusNode);
},
icon: Icon(Icons.keyboard,
size: 22,
color: Theme.of(context).colorScheme.onBackground),
highlightColor:
Theme.of(context).colorScheme.onInverseSurface,
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((states) {
return Theme.of(context).highlightColor;
}),
),
),
),
const Spacer(),
TextButton(
@ -196,7 +170,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
duration: const Duration(milliseconds: 300),
child: SizedBox(
width: double.infinity,
height: _keyboardHeight,
height: keyboardHeight,
),
),
],
@ -204,22 +178,3 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
);
}
}
typedef DebounceCallback = void Function();
class Debouncer {
DebounceCallback? callback;
final int? milliseconds;
Timer? _timer;
Debouncer({this.milliseconds});
run(DebounceCallback callback) {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds!), () {
callback();
});
}
}

View File

@ -122,6 +122,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
plPlayerController!.triggerFullScreen(status: false);
}
shutdownTimerService.handleWaitingFinished();
/// 顺序播放 列表循环
if (plPlayerController!.playRepeat != PlayRepeat.pause &&
plPlayerController!.playRepeat != PlayRepeat.singleCycle) {
@ -234,7 +235,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoIntroController.isPaused = false;
if (_extendNestCtr.position.pixels == 0 && autoplay) {
await Future.delayed(const Duration(milliseconds: 300));
plPlayerController!.seekTo(videoDetailController.defaultST);
plPlayerController?.seekTo(videoDetailController.defaultST);
plPlayerController?.play();
}
plPlayerController?.addStatusLister(playerListener);
@ -308,7 +309,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
body: ExtendedNestedScrollView(
controller: _extendNestCtr,
headerSliverBuilder:
(BuildContext _context, bool innerBoxIsScrolled) {
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
Obx(
() => SliverAppBar(
@ -418,7 +419,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
),
),
Obx(
() => Visibility(
visible: videoDetailController

View File

@ -621,7 +621,7 @@ class PlPlayerController {
if (duration.value.inSeconds != 0) {
if (type != 'slider') {
/// 拖动进度条调节时,不等待第一帧,防止抖动
await _videoPlayerController!.stream.buffer.first;
await _videoPlayerController?.stream.buffer.first;
}
await _videoPlayerController?.seek(position);
// if (playerStatus.stopped) {
@ -786,7 +786,7 @@ class PlPlayerController {
volume.value = volumeNew;
try {
FlutterVolumeController.showSystemUI = false;
FlutterVolumeController.updateShowSystemUI(false);
await FlutterVolumeController.setVolume(volumeNew);
} catch (err) {
print(err);
@ -1086,12 +1086,13 @@ class PlPlayerController {
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
localCache.put(LocalCacheKey.danmakuFontScale, fontSizeVal);
localCache.put(LocalCacheKey.danmakuDuration, danmakuDurationVal);
var pp = _videoPlayerController!.platform as NativePlayer;
await pp.setProperty('audio-files', '');
removeListeners();
await _videoPlayerController?.dispose();
_videoPlayerController = null;
if (_videoPlayerController != null) {
var pp = _videoPlayerController!.platform as NativePlayer;
await pp.setProperty('audio-files', '');
removeListeners();
await _videoPlayerController?.dispose();
_videoPlayerController = null;
}
_instance = null;
// 关闭所有视频页面恢复亮度
resetBrightness();

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
@ -130,7 +129,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
setting.get(SettingBoxKey.enableBackgroundPlay, defaultValue: false);
Future.microtask(() async {
try {
FlutterVolumeController.showSystemUI = true;
FlutterVolumeController.updateShowSystemUI(true);
_ctr.volumeValue.value = (await FlutterVolumeController.getVolume())!;
FlutterVolumeController.addListener((double value) {
if (mounted && !_ctr.volumeInterceptEventStream.value) {
@ -154,7 +153,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
Future<void> setVolume(double value) async {
try {
FlutterVolumeController.showSystemUI = false;
FlutterVolumeController.updateShowSystemUI(false);
await FlutterVolumeController.setVolume(value);
} catch (_) {}
_ctr.volumeValue.value = value;
@ -703,7 +702,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
),
);
} else {
return nil;
return const SizedBox();
}
}),

View File

@ -36,6 +36,7 @@ import '../pages/setting/index.dart';
import '../pages/setting/pages/color_select.dart';
import '../pages/setting/pages/display_mode.dart';
import '../pages/setting/pages/font_size_select.dart';
import '../pages/setting/pages/home_tabbar_set.dart';
import '../pages/setting/pages/play_speed_set.dart';
import '../pages/setting/recommend_setting.dart';
import '../pages/setting/play_setting.dart';
@ -114,6 +115,8 @@ class Routes {
//
CustomGetPage(name: '/blackListPage', page: () => const BlackListPage()),
CustomGetPage(name: '/colorSetting', page: () => const ColorSelectPage()),
// 首页tabbar
CustomGetPage(name: '/tabbarSetting', page: () => const TabbarSetPage()),
CustomGetPage(
name: '/fontSizeSetting', page: () => const FontSizeSelectPage()),
// 屏幕帧率

7
lib/utils/extension.dart Normal file
View File

@ -0,0 +1,7 @@
import 'package:flutter/material.dart';
extension ImageExtension on num {
int cacheSize(BuildContext context) {
return (this * MediaQuery.of(context).devicePixelRatio).round();
}
}

View File

@ -1,5 +1,6 @@
// ignore_for_file: constant_identifier_names
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
@ -72,4 +73,19 @@ class IdUtils {
}
return result;
}
// eid生成
static String? genAuroraEid(int uid) {
if (uid == 0) {
return null;
}
String uidString = uid.toString();
List<int> resultBytes = List.generate(
uidString.length,
(i) => uidString.codeUnitAt(i) ^ "ad1va46a7lza".codeUnitAt(i % 12),
);
String auroraEid = base64Url.encode(resultBytes);
auroraEid = auroraEid.replaceAll(RegExp(r'=*$', multiLine: true), '');
return auroraEid;
}
}

View File

@ -3,13 +3,11 @@ import 'dart:io';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pilipala/models/home/rcmd/result.dart';
import 'package:pilipala/models/model_owner.dart';
import 'package:pilipala/models/search/hot.dart';
import 'package:pilipala/models/user/info.dart';
class GStrorage {
static late final Box<dynamic> recVideo;
static late final Box<dynamic> userInfo;
static late final Box<dynamic> historyword;
static late final Box<dynamic> localCache;
@ -21,13 +19,6 @@ class GStrorage {
final String path = dir.path;
await Hive.initFlutter('$path/hive');
regAdapter();
// 首页推荐视频
recVideo = await Hive.openBox(
'recVideo',
compactionStrategy: (int entries, int deletedEntries) {
return deletedEntries > 12;
},
);
// 登录用户信息
userInfo = await Hive.openBox(
'userInfo',
@ -54,10 +45,6 @@ class GStrorage {
}
static void regAdapter() {
Hive.registerAdapter(RecVideoItemAppModelAdapter());
Hive.registerAdapter(RcmdReasonAdapter());
Hive.registerAdapter(RcmdStatAdapter());
Hive.registerAdapter(RcmdOwnerAdapter());
Hive.registerAdapter(OwnerAdapter());
Hive.registerAdapter(UserInfoDataAdapter());
Hive.registerAdapter(LevelInfoAdapter());
@ -73,8 +60,6 @@ class GStrorage {
static Future<void> close() async {
// user.compact();
// user.close();
recVideo.compact();
recVideo.close();
userInfo.compact();
userInfo.close();
historyword.compact();
@ -151,7 +136,8 @@ class SettingBoxKey {
customRows = 'customRows', // 自定义列
enableMYBar = 'enableMYBar',
hideSearchBar = 'hideSearchBar', // 收起顶栏
hideTabBar = 'hideTabBar'; // 收起底栏
hideTabBar = 'hideTabBar', // 收起底栏
tabbarSort = 'tabbarSort'; // 首页tabbar
}
class LocalCacheKey {