Merge branch 'main' into fix

This commit is contained in:
guozhigq
2024-07-01 22:39:21 +08:00
17 changed files with 315 additions and 180 deletions

View File

@ -548,4 +548,8 @@ class Api {
/// 收到的赞 /// 收到的赞
static const String messageLikeAPi = '/x/msgfeed/like'; static const String messageLikeAPi = '/x/msgfeed/like';
/// 系统通知
static const String messageSystemAPi =
'${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify';
} }

View File

@ -5,6 +5,8 @@ class HttpString {
static const String appBaseUrl = 'https://app.bilibili.com'; static const String appBaseUrl = 'https://app.bilibili.com';
static const String liveBaseUrl = 'https://api.live.bilibili.com'; static const String liveBaseUrl = 'https://api.live.bilibili.com';
static const String passBaseUrl = 'https://passport.bilibili.com'; static const String passBaseUrl = 'https://passport.bilibili.com';
static const String messageBaseUrl = 'https://message.bilibili.com';
static const String bangumiBaseUrl = 'https://bili.meark.me';
static const List<int> validateStatusCodes = [ static const List<int> validateStatusCodes = [
302, 302,
304, 304,

View File

@ -27,11 +27,13 @@ class Request {
late bool enableSystemProxy; late bool enableSystemProxy;
late String systemProxyHost; late String systemProxyHost;
late String systemProxyPort; late String systemProxyPort;
static final RegExp spmPrefixExp = RegExp(r'<meta name="spm_prefix" content="([^"]+?)">'); static final RegExp spmPrefixExp =
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
/// 设置cookie /// 设置cookie
static setCookie() async { static setCookie() async {
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
Box setting = GStrorage.setting;
final String cookiePath = await Utils.getCookiePath(); final String cookiePath = await Utils.getCookiePath();
final PersistCookieJar cookieJar = PersistCookieJar( final PersistCookieJar cookieJar = PersistCookieJar(
ignoreExpires: true, ignoreExpires: true,
@ -54,7 +56,11 @@ class Request {
} }
} }
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null); setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
String baseUrlType = 'default';
if (setting.get(SettingBoxKey.enableGATMode, defaultValue: false)) {
baseUrlType = 'bangumi';
}
setBaseUrl(type: baseUrlType);
try { try {
await buvidActivate(); await buvidActivate();
} catch (e) { } catch (e) {
@ -98,8 +104,7 @@ class Request {
List<int>.generate(32, (_) => rand.nextInt(256)) + List<int>.generate(32, (_) => rand.nextInt(256)) +
List<int>.filled(4, 0) + List<int>.filled(4, 0) +
[73, 69, 78, 68] + [73, 69, 78, 68] +
List<int>.generate(4, (_) => rand.nextInt(256)) List<int>.generate(4, (_) => rand.nextInt(256)));
);
String jsonData = json.encode({ String jsonData = json.encode({
'3064': 1, '3064': 1,
@ -110,11 +115,9 @@ class Request {
}, },
}); });
await Request().post( await Request().post(Api.activateBuvidApi,
Api.activateBuvidApi,
data: {'payload': jsonData}, data: {'payload': jsonData},
options: Options(contentType: 'application/json') options: Options(contentType: 'application/json'));
);
} }
/* /*
@ -294,4 +297,17 @@ class Request {
} }
return headerUa; return headerUa;
} }
static setBaseUrl({String type = 'default'}) {
switch (type) {
case 'default':
dio.options.baseUrl = HttpString.apiBaseUrl;
break;
case 'bangumi':
dio.options.baseUrl = HttpString.bangumiBaseUrl;
break;
default:
dio.options.baseUrl = HttpString.apiBaseUrl;
}
}
} }

View File

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:pilipala/models/msg/like.dart'; import 'package:pilipala/models/msg/like.dart';
import 'package:pilipala/models/msg/reply.dart'; import 'package:pilipala/models/msg/reply.dart';
import 'package:pilipala/models/msg/system.dart';
import '../models/msg/account.dart'; import '../models/msg/account.dart';
import '../models/msg/session.dart'; import '../models/msg/session.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
@ -149,7 +150,7 @@ class MsgHttp {
'msg[msg_status]': 0, 'msg[msg_status]': 0,
'msg[content]': jsonEncode(content), 'msg[content]': jsonEncode(content),
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000, 'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
'msg[new_face_version]': 0, 'msg[new_face_version]': 1,
'msg[dev_id]': getDevId(), 'msg[dev_id]': getDevId(),
'from_firework': 0, 'from_firework': 0,
'build': 0, 'build': 0,
@ -287,4 +288,28 @@ class MsgHttp {
return {'status': false, 'date': [], 'msg': res.data['message']}; return {'status': false, 'date': [], 'msg': res.data['message']};
} }
} }
static Future messageSystem() async {
var res = await Request().get(Api.messageSystemAPi, data: {
'csrf': await Request.getCsrf(),
'page_size': 20,
'build': 0,
'mobi_app': 'web',
});
if (res.data['code'] == 0) {
try {
print(res.data['data']['system_notify_list']);
return {
'status': true,
'data': res.data['data']['system_notify_list']
.map<MessageSystemModel>((e) => MessageSystemModel.fromJson(e))
.toList(),
};
} catch (err) {
return {'status': false, 'date': [], 'msg': err.toString()};
}
} else {
return {'status': false, 'date': [], 'msg': res.data['message']};
}
}
} }

View File

@ -1,95 +0,0 @@
enum SubtitleType {
// 中文(中国)
zhCN,
// 中文(自动翻译)
aizh,
// 英语(自动生成)
aien,
// 中文(简体)
zhHans,
// 英文(美国)
enUS,
// 中文繁体
zhTW,
//
en,
//
pt,
//
es,
}
extension SubtitleTypeExtension on SubtitleType {
String get description {
switch (this) {
case SubtitleType.zhCN:
return '中文(中国)';
case SubtitleType.aizh:
return '中文(自动翻译)';
case SubtitleType.aien:
return '英语(自动生成)';
case SubtitleType.zhHans:
return '中文(简体)';
case SubtitleType.enUS:
return '英文(美国)';
case SubtitleType.zhTW:
return '中文(繁体)';
case SubtitleType.en:
return '英文';
case SubtitleType.pt:
return '葡萄牙语';
case SubtitleType.es:
return '西班牙语';
}
}
}
extension SubtitleIdExtension on SubtitleType {
String get id {
switch (this) {
case SubtitleType.zhCN:
return 'zh-CN';
case SubtitleType.aizh:
return 'ai-zh';
case SubtitleType.aien:
return 'ai-en';
case SubtitleType.zhHans:
return 'zh-Hans';
case SubtitleType.enUS:
return 'en-US';
case SubtitleType.zhTW:
return 'zh-TW';
case SubtitleType.en:
return 'en';
case SubtitleType.pt:
return 'pt';
case SubtitleType.es:
return 'es';
}
}
}
extension SubtitleCodeExtension on SubtitleType {
int get code {
switch (this) {
case SubtitleType.zhCN:
return 1;
case SubtitleType.aizh:
return 2;
case SubtitleType.aien:
return 3;
case SubtitleType.zhHans:
return 4;
case SubtitleType.enUS:
return 5;
case SubtitleType.zhTW:
return 6;
case SubtitleType.en:
return 7;
case SubtitleType.pt:
return 8;
case SubtitleType.es:
return 9;
}
}
}

View File

@ -0,0 +1,77 @@
import 'dart:convert';
class MessageSystemModel {
int? id;
int? cursor;
int? type;
String? title;
Map? content;
Source? source;
String? timeAt;
int? cardType;
String? cardBrief;
String? cardMsgBrief;
String? cardCover;
String? cardStoryTitle;
String? cardLink;
String? mc;
int? isStation;
int? isSend;
int? notifyCursor;
MessageSystemModel({
this.id,
this.cursor,
this.type,
this.title,
this.content,
this.source,
this.timeAt,
this.cardType,
this.cardBrief,
this.cardMsgBrief,
this.cardCover,
this.cardStoryTitle,
this.cardLink,
this.mc,
this.isStation,
this.isSend,
this.notifyCursor,
});
factory MessageSystemModel.fromJson(Map<String, dynamic> jsons) =>
MessageSystemModel(
id: jsons["id"],
cursor: jsons["cursor"],
type: jsons["type"],
title: jsons["title"],
content: json.decode(jsons["content"]),
source: Source.fromJson(jsons["source"]),
timeAt: jsons["time_at"],
cardType: jsons["card_type"],
cardBrief: jsons["card_brief"],
cardMsgBrief: jsons["card_msg_brief"],
cardCover: jsons["card_cover"],
cardStoryTitle: jsons["card_story_title"],
cardLink: jsons["card_link"],
mc: jsons["mc"],
isStation: jsons["is_station"],
isSend: jsons["is_send"],
notifyCursor: jsons["notify_cursor"],
);
}
class Source {
String? name;
String? logo;
Source({
this.name,
this.logo,
});
factory Source.fromJson(Map<String, dynamic> json) => Source(
name: json["name"],
logo: json["logo"],
);
}

View File

@ -1,6 +1,3 @@
import 'package:get/get.dart';
import '../../common/subtitle_type.dart';
class SubTitlteModel { class SubTitlteModel {
SubTitlteModel({ SubTitlteModel({
this.aid, this.aid,
@ -78,11 +75,6 @@ class SubTitlteItemModel {
aiType: json["ai_type"], aiType: json["ai_type"],
aiStatus: json["ai_status"], aiStatus: json["ai_status"],
title: json["lan_doc"], title: json["lan_doc"],
code: SubtitleType.values
.firstWhereOrNull(
(element) => element.id.toString() == json["lan"])
?.index ??
-1,
content: '', content: '',
body: [], body: [],
); );

View File

@ -1,3 +1,15 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/msg.dart';
import 'package:pilipala/models/msg/system.dart';
class MessageSystemController extends GetxController {} class MessageSystemController extends GetxController {
RxList<MessageSystemModel> systemItems = <MessageSystemModel>[].obs;
Future queryMessageSystem({String type = 'init'}) async {
var res = await MsgHttp.messageSystem();
if (res['status']) {
systemItems.addAll(res['data']);
}
return res;
}
}

View File

@ -1,4 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/models/msg/system.dart';
import 'controller.dart';
class MessageSystemPage extends StatefulWidget { class MessageSystemPage extends StatefulWidget {
const MessageSystemPage({super.key}); const MessageSystemPage({super.key});
@ -8,12 +12,112 @@ class MessageSystemPage extends StatefulWidget {
} }
class _MessageSystemPageState extends State<MessageSystemPage> { class _MessageSystemPageState extends State<MessageSystemPage> {
final MessageSystemController _messageSystemCtr =
Get.put(MessageSystemController());
late Future _futureBuilderFuture;
final ScrollController scrollController = ScrollController();
@override
void initState() {
super.initState();
_futureBuilderFuture = _messageSystemCtr.queryMessageSystem();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('系统通知'), title: const Text('系统通知'),
), ),
body: RefreshIndicator(
onRefresh: () async {
await _messageSystemCtr.queryMessageSystem();
},
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
final systemItems = _messageSystemCtr.systemItems;
print(systemItems.length);
return Obx(
() => ListView.separated(
controller: scrollController,
itemBuilder: (context, index) => SystemItem(
item: systemItems[index],
index: index,
messageSystemCtr: _messageSystemCtr,
),
itemCount: systemItems.length,
separatorBuilder: (BuildContext context, int index) {
return Divider(
indent: 14,
endIndent: 14,
height: 1,
color: Colors.grey.withOpacity(0.1),
);
},
),
);
} else {
// 请求错误
return CustomScrollView(
slivers: [
HttpError(
errMsg: snapshot.data['msg'],
fn: () {
setState(() {
_futureBuilderFuture =
_messageSystemCtr.queryMessageSystem();
});
},
)
],
);
}
} else {
return const SizedBox();
}
},
),
),
);
}
}
class SystemItem extends StatelessWidget {
final MessageSystemModel item;
final int index;
final MessageSystemController messageSystemCtr;
const SystemItem(
{super.key,
required this.item,
required this.index,
required this.messageSystemCtr});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(14, 14, 14, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title!,
style:
const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(
item.timeAt!,
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 6),
Text(item.content!['web']),
],
),
); );
} }
} }

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/video/play/ao_output.dart'; import 'package:pilipala/models/video/play/ao_output.dart';
import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
@ -163,6 +164,14 @@ class _PlaySettingState extends State<PlaySetting> {
callFn: (bool val) { callFn: (bool val) {
GlobalData().enablePlayerControlAnimation = val; GlobalData().enablePlayerControlAnimation = val;
}), }),
SetSwitchItem(
title: '港澳台模式',
setKey: SettingBoxKey.enableGATMode,
defaultVal: false,
callFn: (bool val) {
Request.setBaseUrl(type: val ? 'bangumi' : 'default');
},
),
ListTile( ListTile(
dense: false, dense: false,
title: Text('默认视频画质', style: titleStyle), title: Text('默认视频画质', style: titleStyle),

View File

@ -141,13 +141,7 @@ class VideoDetailController extends GetxController
if (Platform.isAndroid) { if (Platform.isAndroid) {
floating = Floating(); floating = Floating();
} }
headerControl = HeaderControl(
controller: plPlayerController,
videoDetailCtr: this,
floating: floating,
bvid: bvid,
videoType: videoType,
);
// CDN优化 // CDN优化
enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true);
// 预设的画质 // 预设的画质
@ -158,7 +152,18 @@ class VideoDetailController extends GetxController
defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa, defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.hiRes.code); defaultValue: AudioQuality.hiRes.code);
oid.value = IdUtils.bv2av(Get.parameters['bvid']!); oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
getSubtitle(); getSubtitle().then(
(subtitles) {
headerControl = HeaderControl(
controller: plPlayerController,
videoDetailCtr: this,
floating: floating,
bvid: bvid,
videoType: videoType,
showSubtitleBtn: subtitles.isNotEmpty,
);
},
);
} }
showReplyReplyPanel(oid, fRpid, firstFloor) { showReplyReplyPanel(oid, fRpid, firstFloor) {
@ -432,6 +437,14 @@ class VideoDetailController extends GetxController
if (result['status']) { if (result['status']) {
if (result['data'].subtitles.isNotEmpty) { if (result['data'].subtitles.isNotEmpty) {
subtitles = result['data'].subtitles; subtitles = result['data'].subtitles;
getDanmaku(subtitles);
}
return subtitles;
}
}
// 获取弹幕
Future getDanmaku(List subtitles) async {
if (subtitles.isNotEmpty) { if (subtitles.isNotEmpty) {
for (var i in subtitles) { for (var i in subtitles) {
final Map<String, dynamic> res = await VideoHttp.getSubtitleContent( final Map<String, dynamic> res = await VideoHttp.getSubtitleContent(
@ -442,18 +455,6 @@ class VideoDetailController extends GetxController
} }
} }
} }
return result['data'];
}
}
// 获取字幕内容
// Future getSubtitleContent(String url) async {
// var res = await Request().get('https:$url');
// subtitleContents.value = res.data['body'].map<SubTitileContentModel>((e) {
// return SubTitileContentModel.fromJson(e);
// }).toList();
// setSubtitleContent();
// }
setSubtitleContent() { setSubtitleContent() {
plPlayerController.subtitleContent.value = ''; plPlayerController.subtitleContent.value = '';

View File

@ -435,7 +435,8 @@ class VideoIntroController extends GetxController {
videoDetailCtr.danmakuCid.value = cid; videoDetailCtr.danmakuCid.value = cid;
videoDetailCtr.cover.value = cover; videoDetailCtr.cover.value = cover;
videoDetailCtr.queryVideoUrl(); videoDetailCtr.queryVideoUrl();
videoDetailCtr.getSubtitle(); videoDetailCtr.clearSubtitleContent();
await videoDetailCtr.getSubtitle();
videoDetailCtr.setSubtitleContent(); videoDetailCtr.setSubtitleContent();
// 重新请求评论 // 重新请求评论
try { try {

View File

@ -188,7 +188,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data; var data = snapshot.data;
if (_videoReplyController.replyList.isNotEmpty || if (_videoReplyController.replyList.isNotEmpty ||
(data && data['status'])) { (data != null && data['status'])) {
// 请求成功 // 请求成功
return Obx( return Obx(
() => _videoReplyController.isLoadingMore && () => _videoReplyController.isLoadingMore &&

View File

@ -30,6 +30,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
this.floating, this.floating,
this.bvid, this.bvid,
this.videoType, this.videoType,
this.showSubtitleBtn,
super.key, super.key,
}); });
final PlPlayerController? controller; final PlPlayerController? controller;
@ -37,6 +38,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
final Floating? floating; final Floating? floating;
final String? bvid; final String? bvid;
final SearchType? videoType; final SearchType? videoType;
final bool? showSubtitleBtn;
@override @override
State<HeaderControl> createState() => _HeaderControlState(); State<HeaderControl> createState() => _HeaderControlState();
@ -426,7 +428,12 @@ class _HeaderControlState extends State<HeaderControl> {
/// 选择字幕 /// 选择字幕
void showSubtitleDialog() async { void showSubtitleDialog() async {
int tempThemeValue = widget.controller!.subTitleCode.value; int tempThemeValue = widget.controller!.subTitleCode.value;
int len = widget.videoDetailCtr!.subtitles.length; final List subtitles = widget.videoDetailCtr!.subtitles;
int len = subtitles.length;
if (subtitles.firstWhereOrNull((element) => element.id == tempThemeValue) ==
null) {
tempThemeValue = -1;
}
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -458,7 +465,7 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
...widget.videoDetailCtr!.subtitles ...widget.videoDetailCtr!.subtitles
.map((e) => RadioListTile( .map((e) => RadioListTile(
value: e.code, value: e.id,
title: Text(e.title), title: Text(e.title),
groupValue: tempThemeValue, groupValue: tempThemeValue,
onChanged: (value) { onChanged: (value) {
@ -1322,6 +1329,7 @@ class _HeaderControlState extends State<HeaderControl> {
], ],
/// 字幕 /// 字幕
if (widget.showSubtitleBtn!)
ComBtn( ComBtn(
icon: const Icon( icon: const Icon(
Icons.closed_caption_off, Icons.closed_caption_off,

View File

@ -71,7 +71,7 @@ class _WhisperPageState extends State<WhisperPage> {
..._whisperController.noticesList.map((element) { ..._whisperController.noticesList.map((element) {
return InkWell( return InkWell(
onTap: () { onTap: () {
if (['/messageAt', '/messageSystem'] if (['/messageAt']
.contains(element['path'])) { .contains(element['path'])) {
SmartDialog.showToast('功能开发中'); SmartDialog.showToast('功能开发中');
return; return;

View File

@ -123,7 +123,7 @@ class PlPlayerController {
PreferredSizeWidget? headerControl; PreferredSizeWidget? headerControl;
PreferredSizeWidget? bottomControl; PreferredSizeWidget? bottomControl;
Widget? danmuWidget; Widget? danmuWidget;
late RxList subtitles; RxList subtitles = [].obs;
String videoType = 'archive'; String videoType = 'archive';
/// 数据加载监听 /// 数据加载监听
@ -642,10 +642,6 @@ class PlPlayerController {
const Duration(seconds: 1), const Duration(seconds: 1),
() => videoPlayerServiceHandler.onPositionChange(event)); () => videoPlayerServiceHandler.onPositionChange(event));
}), }),
// onSubTitleOpenChanged.listen((bool event) {
// toggleSubtitle(event ? subTitleCode.value : -1);
// })
], ],
); );
} }
@ -1049,25 +1045,6 @@ class PlPlayerController {
void toggleSubtitle(int code) { void toggleSubtitle(int code) {
_subTitleOpen.value = code != -1; _subTitleOpen.value = code != -1;
_subTitleCode.value = code; _subTitleCode.value = code;
// if (code == -1) {
// // 关闭字幕
// _subTitleOpen.value = false;
// _subTitleCode.value = code;
// _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no());
// return;
// }
// final SubTitlteItemModel? subtitle = subtitles?.firstWhereOrNull(
// (element) => element.code == code,
// );
// _subTitleOpen.value = true;
// _subTitleCode.value = code;
// _videoPlayerController?.setSubtitleTrack(
// SubtitleTrack.data(
// subtitle!.content!,
// title: subtitle.title,
// language: subtitle.lan,
// ),
// );
} }
void querySubtitleContent(double progress) { void querySubtitleContent(double progress) {
@ -1079,7 +1056,7 @@ class PlPlayerController {
return; return;
} }
final SubTitlteItemModel? subtitle = subtitles.firstWhereOrNull( final SubTitlteItemModel? subtitle = subtitles.firstWhereOrNull(
(element) => element.code == subTitleCode.value, (element) => element.id == subTitleCode.value,
); );
if (subtitle != null && subtitle.body!.isNotEmpty) { if (subtitle != null && subtitle.body!.isNotEmpty) {
for (var content in subtitle.body!) { for (var content in subtitle.body!) {

View File

@ -104,6 +104,8 @@ class SettingBoxKey {
enablePlayerControlAnimation = 'enablePlayerControlAnimation', enablePlayerControlAnimation = 'enablePlayerControlAnimation',
// 默认音频输出方式 // 默认音频输出方式
defaultAoOutput = 'defaultAoOutput', defaultAoOutput = 'defaultAoOutput',
// 港澳台模式
enableGATMode = 'enableGATMode',
// youtube 双击快进快退 // youtube 双击快进快退
enableQuickDouble = 'enableQuickDouble', enableQuickDouble = 'enableQuickDouble',