Merge branch 'main' into design
This commit is contained in:
@ -3,7 +3,9 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:scrollview_observer/scrollview_observer.dart';
|
||||
import '../models/common/video_episode_type.dart';
|
||||
@ -20,6 +22,8 @@ class EpisodeBottomSheet {
|
||||
final double? sheetHeight;
|
||||
bool isFullScreen = false;
|
||||
final UgcSeason? ugcSeason;
|
||||
final int? currentEpisodeIndex;
|
||||
final int? currentIndex;
|
||||
|
||||
EpisodeBottomSheet({
|
||||
required this.episodes,
|
||||
@ -30,6 +34,8 @@ class EpisodeBottomSheet {
|
||||
this.sheetHeight,
|
||||
this.isFullScreen = false,
|
||||
this.ugcSeason,
|
||||
this.currentEpisodeIndex,
|
||||
this.currentIndex,
|
||||
});
|
||||
|
||||
Widget buildShowContent() {
|
||||
@ -42,6 +48,8 @@ class EpisodeBottomSheet {
|
||||
sheetHeight: sheetHeight,
|
||||
isFullScreen: isFullScreen,
|
||||
ugcSeason: ugcSeason,
|
||||
currentEpisodeIndex: currentEpisodeIndex,
|
||||
currentIndex: currentIndex,
|
||||
);
|
||||
}
|
||||
|
||||
@ -67,6 +75,8 @@ class PagesBottomSheet extends StatefulWidget {
|
||||
this.sheetHeight,
|
||||
this.isFullScreen = false,
|
||||
this.ugcSeason,
|
||||
this.currentEpisodeIndex,
|
||||
this.currentIndex,
|
||||
});
|
||||
|
||||
final List<dynamic> episodes;
|
||||
@ -77,41 +87,38 @@ class PagesBottomSheet extends StatefulWidget {
|
||||
final double? sheetHeight;
|
||||
final bool isFullScreen;
|
||||
final UgcSeason? ugcSeason;
|
||||
final int? currentEpisodeIndex;
|
||||
final int? currentIndex;
|
||||
|
||||
@override
|
||||
State<PagesBottomSheet> createState() => _PagesBottomSheetState();
|
||||
}
|
||||
|
||||
class _PagesBottomSheetState extends State<PagesBottomSheet> {
|
||||
class _PagesBottomSheetState extends State<PagesBottomSheet>
|
||||
with TickerProviderStateMixin {
|
||||
final ScrollController _listScrollController = ScrollController();
|
||||
late ListObserverController _listObserverController;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
late int currentIndex;
|
||||
TabController? tabController;
|
||||
List<ListObserverController>? _listObserverControllerList;
|
||||
List<ScrollController>? _listScrollControllerList;
|
||||
final String heroTag = Get.arguments['heroTag'];
|
||||
VideoDetailController? _videoDetailController;
|
||||
RxInt isSubscribe = (-1).obs;
|
||||
bool isVisible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
currentIndex =
|
||||
currentIndex = widget.currentIndex ??
|
||||
widget.episodes.indexWhere((dynamic e) => e.cid == widget.currentCid);
|
||||
_listObserverController =
|
||||
ListObserverController(controller: _listScrollController);
|
||||
_scrollToInit();
|
||||
_scrollPositionInit();
|
||||
if (widget.dataType == VideoEpidoesType.videoEpisode) {
|
||||
_listObserverController.initialIndexModel = ObserverIndexPositionModel(
|
||||
index: currentIndex,
|
||||
isFixedHeight: true,
|
||||
);
|
||||
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
||||
_getSubscribeStatus();
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.dataType != VideoEpidoesType.videoEpisode) {
|
||||
double itemHeight = (widget.isFullScreen
|
||||
? 400
|
||||
: Get.size.width - 3 * StyleString.safeSpace) /
|
||||
5.2;
|
||||
double offset = ((currentIndex - 1) / 2).ceil() * itemHeight;
|
||||
_scrollController.jumpTo(offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String prefix() {
|
||||
@ -126,9 +133,117 @@ class _PagesBottomSheetState extends State<PagesBottomSheet> {
|
||||
return '选集';
|
||||
}
|
||||
|
||||
// 滚动器初始化
|
||||
void _scrollToInit() {
|
||||
/// 单个
|
||||
_listObserverController =
|
||||
ListObserverController(controller: _listScrollController);
|
||||
|
||||
if (widget.dataType == VideoEpidoesType.videoEpisode &&
|
||||
widget.ugcSeason?.sections != null &&
|
||||
widget.ugcSeason!.sections!.length > 1) {
|
||||
tabController = TabController(
|
||||
length: widget.ugcSeason!.sections!.length,
|
||||
vsync: this,
|
||||
initialIndex: widget.currentEpisodeIndex ?? 0,
|
||||
);
|
||||
|
||||
/// 多tab
|
||||
_listScrollControllerList = List.generate(
|
||||
widget.ugcSeason!.sections!.length,
|
||||
(index) {
|
||||
return ScrollController();
|
||||
},
|
||||
);
|
||||
_listObserverControllerList = List.generate(
|
||||
widget.ugcSeason!.sections!.length,
|
||||
(index) {
|
||||
return ListObserverController(
|
||||
controller: _listScrollControllerList![index],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动器位置初始化
|
||||
void _scrollPositionInit() {
|
||||
if (widget.dataType == VideoEpidoesType.videoEpisode) {
|
||||
// 单个 多tab
|
||||
if (widget.ugcSeason?.sections != null) {
|
||||
if (widget.ugcSeason!.sections!.length == 1) {
|
||||
_listObserverController.initialIndexModel =
|
||||
ObserverIndexPositionModel(
|
||||
index: currentIndex,
|
||||
isFixedHeight: true,
|
||||
);
|
||||
} else {
|
||||
_listObserverControllerList![widget.currentEpisodeIndex!]
|
||||
.initialIndexModel = ObserverIndexPositionModel(
|
||||
index: currentIndex,
|
||||
isFixedHeight: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.dataType != VideoEpidoesType.videoEpisode) {
|
||||
double itemHeight = (widget.isFullScreen
|
||||
? 400
|
||||
: Get.size.width - 3 * StyleString.safeSpace) /
|
||||
5.2;
|
||||
double offset = ((currentIndex - 1) / 2).ceil() * itemHeight;
|
||||
_scrollController.jumpTo(offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取订阅状态
|
||||
void _getSubscribeStatus() async {
|
||||
var res =
|
||||
await VideoHttp.getSubscribeStatus(bvid: _videoDetailController!.bvid);
|
||||
if (res['status']) {
|
||||
isSubscribe.value = res['data']['season_fav'] ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 更改订阅状态
|
||||
void _changeSubscribeStatus() async {
|
||||
if (isSubscribe.value == -1) {
|
||||
return;
|
||||
}
|
||||
dynamic result = await VideoHttp.seasonFav(
|
||||
isFav: isSubscribe.value == 1,
|
||||
seasonId: widget.ugcSeason!.id,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast(isSubscribe.value == 1 ? '取消订阅成功' : '订阅成功');
|
||||
isSubscribe.value = isSubscribe.value == 1 ? 0 : 1;
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
// 更改展开状态
|
||||
void _changeVisible() {
|
||||
setState(() {
|
||||
isVisible = !isVisible;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listObserverController.controller?.dispose();
|
||||
try {
|
||||
_listObserverController.controller?.dispose();
|
||||
_listScrollController.dispose();
|
||||
for (var element in _listObserverControllerList!) {
|
||||
element.controller?.dispose();
|
||||
}
|
||||
for (var element in _listScrollControllerList!) {
|
||||
element.dispose();
|
||||
}
|
||||
} catch (_) {}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -145,36 +260,46 @@ class _PagesBottomSheetState extends State<PagesBottomSheet> {
|
||||
isFullScreen: widget.isFullScreen,
|
||||
),
|
||||
if (widget.ugcSeason != null) ...[
|
||||
UgcSeasonBuild(ugcSeason: widget.ugcSeason!),
|
||||
UgcSeasonBuild(
|
||||
ugcSeason: widget.ugcSeason!,
|
||||
isSubscribe: isSubscribe,
|
||||
isVisible: isVisible,
|
||||
changeFucCall: _changeSubscribeStatus,
|
||||
changeVisible: _changeVisible,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: widget.dataType == VideoEpidoesType.videoEpisode
|
||||
? ListViewObserver(
|
||||
controller: _listObserverController,
|
||||
child: ListView.builder(
|
||||
controller: _listScrollController,
|
||||
itemCount: widget.episodes.length + 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
bool isLastItem = index == widget.episodes.length;
|
||||
bool isCurrentIndex = currentIndex == index;
|
||||
return isLastItem
|
||||
? SizedBox(
|
||||
height:
|
||||
MediaQuery.of(context).padding.bottom +
|
||||
? (widget.ugcSeason!.sections!.length == 1
|
||||
? ListViewObserver(
|
||||
controller: _listObserverController,
|
||||
child: ListView.builder(
|
||||
controller: _listScrollController,
|
||||
itemCount: widget.episodes.length + 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
bool isLastItem =
|
||||
index == widget.episodes.length;
|
||||
bool isCurrentIndex = currentIndex == index;
|
||||
return isLastItem
|
||||
? SizedBox(
|
||||
height: MediaQuery.of(context)
|
||||
.padding
|
||||
.bottom +
|
||||
20,
|
||||
)
|
||||
: EpisodeListItem(
|
||||
episode: widget.episodes[index],
|
||||
index: index,
|
||||
isCurrentIndex: isCurrentIndex,
|
||||
dataType: widget.dataType,
|
||||
changeFucCall: widget.changeFucCall,
|
||||
isFullScreen: widget.isFullScreen,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
: EpisodeListItem(
|
||||
episode: widget.episodes[index],
|
||||
index: index,
|
||||
isCurrentIndex: isCurrentIndex,
|
||||
dataType: widget.dataType,
|
||||
changeFucCall: widget.changeFucCall,
|
||||
isFullScreen: widget.isFullScreen,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: buildTabBar())
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0), // 设置左右间距为12
|
||||
@ -206,6 +331,65 @@ class _PagesBottomSheetState extends State<PagesBottomSheet> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildTabBar() {
|
||||
return Column(
|
||||
children: [
|
||||
// 背景色
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: TabBar(
|
||||
controller: tabController,
|
||||
isScrollable: true,
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
tabAlignment: TabAlignment.start,
|
||||
splashBorderRadius: BorderRadius.circular(4),
|
||||
tabs: [
|
||||
...widget.ugcSeason!.sections!.map((SectionItem section) {
|
||||
return Tab(
|
||||
text: section.title,
|
||||
);
|
||||
}).toList()
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: tabController,
|
||||
children: [
|
||||
...widget.ugcSeason!.sections!.map((SectionItem section) {
|
||||
final int fIndex = widget.ugcSeason!.sections!.indexOf(section);
|
||||
return ListViewObserver(
|
||||
controller: _listObserverControllerList![fIndex],
|
||||
child: ListView.builder(
|
||||
controller: _listScrollControllerList![fIndex],
|
||||
itemCount: section.episodes!.length + 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final bool isLastItem = index == section.episodes!.length;
|
||||
return isLastItem
|
||||
? SizedBox(
|
||||
height:
|
||||
MediaQuery.of(context).padding.bottom + 20,
|
||||
)
|
||||
: EpisodeListItem(
|
||||
episode: section.episodes![index], // 调整索引
|
||||
index: index, // 调整索引
|
||||
isCurrentIndex: widget.currentCid ==
|
||||
section.episodes![index].cid,
|
||||
dataType: widget.dataType,
|
||||
changeFucCall: widget.changeFucCall,
|
||||
isFullScreen: widget.isFullScreen,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList()
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TitleBar extends StatelessWidget {
|
||||
@ -507,77 +691,134 @@ class EpisodeGridItem extends StatelessWidget {
|
||||
|
||||
class UgcSeasonBuild extends StatelessWidget {
|
||||
final UgcSeason ugcSeason;
|
||||
final RxInt isSubscribe;
|
||||
final bool isVisible;
|
||||
final Function changeFucCall;
|
||||
final Function changeVisible;
|
||||
|
||||
const UgcSeasonBuild({
|
||||
Key? key,
|
||||
required this.ugcSeason,
|
||||
required this.isSubscribe,
|
||||
required this.isVisible,
|
||||
required this.changeFucCall,
|
||||
required this.changeVisible,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'合集:${ugcSeason.title}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (ugcSeason.intro != null && ugcSeason.intro != '') ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final Color outline = theme.colorScheme.outline;
|
||||
final Color surface = theme.colorScheme.surface;
|
||||
final Color primary = theme.colorScheme.primary;
|
||||
final Color onPrimary = theme.colorScheme.onPrimary;
|
||||
final Color onInverseSurface = theme.colorScheme.onInverseSurface;
|
||||
final TextStyle titleMedium = theme.textTheme.titleMedium!;
|
||||
final TextStyle labelMedium = theme.textTheme.labelMedium!;
|
||||
final Color dividerColor = theme.dividerColor.withOpacity(0.1);
|
||||
|
||||
return isVisible
|
||||
? Container(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||
color: surface,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(ugcSeason.intro ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline)),
|
||||
Divider(height: 1, thickness: 1, color: dividerColor),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'合集:${ugcSeason.title}',
|
||||
style: titleMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Obx(
|
||||
() => isSubscribe.value == -1
|
||||
? const SizedBox(height: 32)
|
||||
: SizedBox(
|
||||
height: 32,
|
||||
child: FilledButton.tonal(
|
||||
onPressed: () => changeFucCall.call(),
|
||||
style: TextButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 8, right: 8),
|
||||
foregroundColor: isSubscribe.value == 1
|
||||
? outline
|
||||
: onPrimary,
|
||||
backgroundColor: isSubscribe.value == 1
|
||||
? onInverseSurface
|
||||
: primary,
|
||||
),
|
||||
child:
|
||||
Text(isSubscribe.value == 1 ? '已订阅' : '订阅'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// SizedBox(
|
||||
// height: 32,
|
||||
// child: FilledButton.tonal(
|
||||
// onPressed: () {},
|
||||
// style: ButtonStyle(
|
||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// ),
|
||||
// child: const Text('订阅'),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 6),
|
||||
if (ugcSeason.intro != null && ugcSeason.intro != '') ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
ugcSeason.intro!,
|
||||
style: TextStyle(color: outline, fontSize: 12),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: labelMedium.fontSize, color: outline),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${Utils.numFormat(ugcSeason.stat!.view)}播放'),
|
||||
const TextSpan(text: ' · '),
|
||||
TextSpan(
|
||||
text:
|
||||
'${Utils.numFormat(ugcSeason.stat!.danmaku)}弹幕'),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
color: surface,
|
||||
child: InkWell(
|
||||
onTap: () => changeVisible.call(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10, horizontal: 0),
|
||||
child: Text(
|
||||
'收起简介',
|
||||
style: TextStyle(color: primary, fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(height: 1, thickness: 1, color: dividerColor),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
)
|
||||
: Align(
|
||||
alignment: Alignment.center,
|
||||
child: InkWell(
|
||||
onTap: () => changeVisible.call(),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 10, horizontal: 0),
|
||||
child: Text(
|
||||
'展开简介',
|
||||
style: TextStyle(color: primary, fontSize: 12),
|
||||
),
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: '${Utils.numFormat(ugcSeason.stat!.view)}播放'),
|
||||
const TextSpan(text: ' · '),
|
||||
TextSpan(text: '${Utils.numFormat(ugcSeason.stat!.danmaku)}弹幕'),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
Box<dynamic> setting = GStrorage.setting;
|
||||
Box<dynamic> setting = GStorage.setting;
|
||||
|
||||
class CustomToast extends StatelessWidget {
|
||||
const CustomToast({super.key, required this.msg});
|
||||
|
||||
@ -6,7 +6,7 @@ import 'package:pilipala/utils/global_data_cache.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
Box<dynamic> setting = GStrorage.setting;
|
||||
Box<dynamic> setting = GStorage.setting;
|
||||
|
||||
class NetworkImgLayer extends StatelessWidget {
|
||||
const NetworkImgLayer({
|
||||
|
||||
@ -495,7 +495,7 @@ class Api {
|
||||
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
||||
|
||||
/// 获取字幕配置
|
||||
static const getSubtitleConfig = '/x/player/v2';
|
||||
static const getSubtitleConfig = '/x/player/wbi/v2';
|
||||
|
||||
/// 我的订阅
|
||||
static const userSubFolder = '/x/v3/fav/folder/collected/list';
|
||||
@ -609,4 +609,14 @@ class Api {
|
||||
|
||||
/// @我的
|
||||
static const String messageAtAPi = '/x/msgfeed/at?';
|
||||
|
||||
/// 订阅
|
||||
static const String confirmSub = '/x/v3/fav/season/fav';
|
||||
|
||||
/// 订阅状态
|
||||
static const String videoRelation = '/x/web-interface/archive/relation';
|
||||
|
||||
/// 获取空降区间
|
||||
static const String getSkipSegments =
|
||||
'${HttpString.sponsorBlockBaseUrl}/api/skipSegments';
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'package:pilipala/models/sponsor_block/segment.dart';
|
||||
|
||||
import 'index.dart';
|
||||
|
||||
class CommonHttp {
|
||||
@ -14,4 +16,31 @@ class CommonHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future querySkipSegments({required String bvid}) async {
|
||||
var res = await Request().getWithoutCookie(Api.getSkipSegments, data: {
|
||||
'videoID': bvid,
|
||||
});
|
||||
if (res.data is List && res.data.isNotEmpty) {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data
|
||||
.map<SegmentDataModel>((e) => SegmentDataModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': 'sponsorBlock数据解析失败: $err',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ class HttpString {
|
||||
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 String sponsorBlockBaseUrl = 'https://www.bsbsb.top';
|
||||
static const List<int> validateStatusCodes = [
|
||||
302,
|
||||
304,
|
||||
|
||||
@ -21,8 +21,8 @@ class Request {
|
||||
static late CookieManager cookieManager;
|
||||
static late final Dio dio;
|
||||
factory Request() => _instance;
|
||||
Box setting = GStrorage.setting;
|
||||
static Box localCache = GStrorage.localCache;
|
||||
Box setting = GStorage.setting;
|
||||
static Box localCache = GStorage.localCache;
|
||||
late bool enableSystemProxy;
|
||||
late String systemProxyHost;
|
||||
late String systemProxyPort;
|
||||
@ -32,8 +32,8 @@ class Request {
|
||||
|
||||
/// 设置cookie
|
||||
static setCookie() async {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box setting = GStrorage.setting;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
Box setting = GStorage.setting;
|
||||
final String cookiePath = await Utils.getCookiePath();
|
||||
final PersistCookieJar cookieJar = PersistCookieJar(
|
||||
ignoreExpires: true,
|
||||
@ -217,6 +217,13 @@ class Request {
|
||||
if (extra['ua'] != null) {
|
||||
options.headers = {'user-agent': headerUa(type: extra['ua'])};
|
||||
}
|
||||
if (extra['opus-goback'] != null) {
|
||||
if (extra['opus-goback'] != null) {
|
||||
String cookieHeader = dio.options.headers['cookie'];
|
||||
options.headers!['cookie'] =
|
||||
'$cookieHeader; opus-goback = ${extra['opus-goback']}';
|
||||
}
|
||||
}
|
||||
}
|
||||
options.responseType = resType;
|
||||
|
||||
|
||||
@ -470,8 +470,8 @@ class MemberHttp {
|
||||
SmartDialog.dismiss();
|
||||
if (res.data['code'] == 0) {
|
||||
String accessKey = res.data['data']['access_token'];
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box localCache = GStorage.localCache;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
var userInfo = userInfoCache.get('userInfoCache');
|
||||
localCache.put(
|
||||
LocalCacheKey.accessKey, {'mid': userInfo.mid, 'value': accessKey});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:pilipala/models/read/opus.dart';
|
||||
import 'package:pilipala/models/read/read.dart';
|
||||
@ -64,7 +65,7 @@ class ReadHttp {
|
||||
static Future parseArticleCv({required String id}) async {
|
||||
var res = await Request().get(
|
||||
'https://www.bilibili.com/read/cv$id',
|
||||
extra: {'ua': 'pc'},
|
||||
extra: {'ua': 'pc', 'opus-goback': '1'},
|
||||
);
|
||||
String scriptContent =
|
||||
extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||
|
||||
@ -11,7 +11,7 @@ import '../utils/storage.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class SearchHttp {
|
||||
static Box setting = GStrorage.setting;
|
||||
static Box setting = GStorage.setting;
|
||||
static Future hotSearchList() async {
|
||||
var res = await Request().get(Api.hotSearchList);
|
||||
if (res.data is String) {
|
||||
|
||||
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/common/reply_type.dart';
|
||||
import '../models/home/rcmd/result.dart';
|
||||
@ -24,11 +25,11 @@ import 'init.dart';
|
||||
/// 返回{'status': bool, 'data': List}
|
||||
/// view层根据 status 判断渲染逻辑
|
||||
class VideoHttp {
|
||||
static Box localCache = GStrorage.localCache;
|
||||
static Box setting = GStrorage.setting;
|
||||
static Box localCache = GStorage.localCache;
|
||||
static Box setting = GStorage.setting;
|
||||
static bool enableRcmdDynamic =
|
||||
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
||||
static Box userInfoCache = GStrorage.userInfo;
|
||||
static Box userInfoCache = GStorage.userInfo;
|
||||
|
||||
// 首页推荐视频
|
||||
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
||||
@ -509,10 +510,11 @@ class VideoHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future getSubtitle({int? cid, String? bvid}) async {
|
||||
static Future getSubtitle({int? cid, String? bvid, String? aid}) async {
|
||||
var res = await Request().get(Api.getSubtitleConfig, data: {
|
||||
'cid': cid,
|
||||
'bvid': bvid,
|
||||
if (bvid != null) 'bvid': bvid,
|
||||
if (aid != null) 'aid': aid,
|
||||
});
|
||||
try {
|
||||
if (res.data['code'] == 0) {
|
||||
@ -559,4 +561,50 @@ class VideoHttp {
|
||||
final List body = res.data['body'];
|
||||
return {'content': content, 'body': body};
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getSubscribeStatus(
|
||||
{required dynamic bvid}) async {
|
||||
var res = await Request().get(
|
||||
Api.videoRelation,
|
||||
data: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'bvid': bvid,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future seasonFav({
|
||||
required bool isFav,
|
||||
required dynamic seasonId,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
isFav ? Api.cancelSub : Api.confirmSub,
|
||||
data: {
|
||||
'platform': 'web',
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ void main() async {
|
||||
MediaKit.ensureInitialized();
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
await GStrorage.init();
|
||||
await GStorage.init();
|
||||
clearLogs();
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
@ -73,7 +73,7 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
// 主题色
|
||||
Color defaultColor =
|
||||
colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)]
|
||||
|
||||
26
lib/models/sponsor_block/action_type.dart
Normal file
26
lib/models/sponsor_block/action_type.dart
Normal file
@ -0,0 +1,26 @@
|
||||
// 片段类型枚举
|
||||
enum ActionType {
|
||||
skip,
|
||||
mute,
|
||||
full,
|
||||
poi,
|
||||
chapter,
|
||||
}
|
||||
|
||||
extension ActionTypeExtension on ActionType {
|
||||
String get value => [
|
||||
'skip',
|
||||
'mute',
|
||||
'full',
|
||||
'poi',
|
||||
'chapter',
|
||||
][index];
|
||||
|
||||
String get label => [
|
||||
'跳过',
|
||||
'静音',
|
||||
'完整观看',
|
||||
'亮点',
|
||||
'章节切换',
|
||||
][index];
|
||||
}
|
||||
43
lib/models/sponsor_block/segment.dart
Normal file
43
lib/models/sponsor_block/segment.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'action_type.dart';
|
||||
import 'segment_type.dart';
|
||||
|
||||
class SegmentDataModel {
|
||||
final SegmentType? category;
|
||||
final ActionType? actionType;
|
||||
final List? segment;
|
||||
final String? uuid;
|
||||
final num? videoDuration;
|
||||
final int? locked;
|
||||
final int? votes;
|
||||
final String? description;
|
||||
// 是否已经跳过
|
||||
bool isSkip = false;
|
||||
|
||||
SegmentDataModel({
|
||||
this.category,
|
||||
this.actionType,
|
||||
this.segment,
|
||||
this.uuid,
|
||||
this.videoDuration,
|
||||
this.locked,
|
||||
this.votes,
|
||||
this.description,
|
||||
});
|
||||
|
||||
factory SegmentDataModel.fromJson(Map<String, dynamic> json) {
|
||||
return SegmentDataModel(
|
||||
category: SegmentType.values.firstWhere(
|
||||
(e) => e.value == json['category'],
|
||||
orElse: () => SegmentType.sponsor),
|
||||
actionType: ActionType.values.firstWhere(
|
||||
(e) => e.value == json['actionType'],
|
||||
orElse: () => ActionType.skip),
|
||||
segment: json['segment'],
|
||||
uuid: json['UUID'],
|
||||
videoDuration: json['videoDuration'],
|
||||
locked: json['locked'],
|
||||
votes: json['votes'],
|
||||
description: json['description'],
|
||||
);
|
||||
}
|
||||
}
|
||||
46
lib/models/sponsor_block/segment_type.dart
Normal file
46
lib/models/sponsor_block/segment_type.dart
Normal file
@ -0,0 +1,46 @@
|
||||
// 片段类型枚举
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
enum SegmentType {
|
||||
sponsor,
|
||||
intro,
|
||||
outro,
|
||||
interaction,
|
||||
selfpromo,
|
||||
music_offtopic,
|
||||
preview,
|
||||
poi_highlight,
|
||||
filler,
|
||||
exclusive_access,
|
||||
chapter,
|
||||
}
|
||||
|
||||
extension SegmentTypeExtension on SegmentType {
|
||||
String get value => [
|
||||
'sponsor',
|
||||
'intro',
|
||||
'outro',
|
||||
'interaction',
|
||||
'selfpromo',
|
||||
'music_offtopic',
|
||||
'preview',
|
||||
'poi_highlight',
|
||||
'filler',
|
||||
'exclusive_access',
|
||||
'chapter',
|
||||
][index];
|
||||
|
||||
String get label => [
|
||||
'赞助',
|
||||
'开场介绍',
|
||||
'片尾致谢',
|
||||
'互动',
|
||||
'自我推广',
|
||||
'音乐',
|
||||
'预览',
|
||||
'亮点',
|
||||
'无效填充',
|
||||
'独家访问',
|
||||
'章节',
|
||||
][index];
|
||||
}
|
||||
@ -29,7 +29,7 @@ class ReplyMember {
|
||||
avatar = json['avatar'];
|
||||
level = json['level_info']['current_level'];
|
||||
pendant = Pendant.fromJson(json['pendant']);
|
||||
officialVerify = json['officia_verify'];
|
||||
officialVerify = json['official_verify'];
|
||||
vip = json['vip'];
|
||||
fansDetail = json['fans_detail'];
|
||||
userSailing = json['user_sailing'] != null
|
||||
|
||||
@ -641,6 +641,7 @@ class EpisodeItem {
|
||||
this.page,
|
||||
this.bvid,
|
||||
this.cover,
|
||||
this.pages,
|
||||
});
|
||||
int? seasonId;
|
||||
int? sectionId;
|
||||
@ -655,6 +656,7 @@ class EpisodeItem {
|
||||
int? pubdate;
|
||||
int? duration;
|
||||
Stat? stat;
|
||||
List<Page>? pages;
|
||||
|
||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||
seasonId = json['season_id'];
|
||||
@ -670,6 +672,7 @@ class EpisodeItem {
|
||||
pubdate = json['arc']['pubdate'];
|
||||
duration = json['arc']['duration'];
|
||||
stat = Stat.fromJson(json['arc']['stat']);
|
||||
pages = json['pages'].map<Page>((e) => Page.fromJson(e)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@ -712,3 +715,18 @@ class Vip {
|
||||
status = json['status'];
|
||||
}
|
||||
}
|
||||
|
||||
class Page {
|
||||
Page({
|
||||
this.cid,
|
||||
this.page,
|
||||
});
|
||||
|
||||
int? cid;
|
||||
int? page;
|
||||
|
||||
Page.fromJson(Map<String, dynamic> json) {
|
||||
cid = json['cid'];
|
||||
page = json['page'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ class BangumiController extends GetxController {
|
||||
RxInt total = 0.obs;
|
||||
int _currentPage = 1;
|
||||
bool isLoadingMore = true;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
RxBool userLogin = false.obs;
|
||||
late int mid;
|
||||
var userInfo;
|
||||
|
||||
@ -48,7 +48,7 @@ class BangumiIntroController extends GetxController {
|
||||
RxBool hasCoin = false.obs;
|
||||
// 是否收藏
|
||||
RxBool hasFav = false.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
bool userLogin = false;
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
List addMediaIdsNew = [];
|
||||
|
||||
@ -116,7 +116,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
String heroTag = Get.arguments['heroTag'];
|
||||
late final BangumiIntroController bangumiIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box localCache = GStorage.localCache;
|
||||
late double sheetHeight;
|
||||
int? cid;
|
||||
bool isProcessing = false;
|
||||
|
||||
@ -4,7 +4,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box localCache = GStorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
class IntroDetail extends StatelessWidget {
|
||||
|
||||
@ -36,7 +36,7 @@ class BangumiPanel extends StatefulWidget {
|
||||
class _BangumiPanelState extends State<BangumiPanel> {
|
||||
late RxInt currentIndex = (-1).obs;
|
||||
final ScrollController listViewScrollCtr = ScrollController();
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
dynamic userInfo;
|
||||
// 默认未开通
|
||||
int vipStatus = 0;
|
||||
|
||||
@ -22,7 +22,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Future? _futureBuilderFuture;
|
||||
bool _isLoadingMore = false;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
||||
@ -32,7 +32,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
late PlDanmakuController _plDanmakuController;
|
||||
DanmakuController? _controller;
|
||||
// bool danmuPlayStatus = true;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late bool enableShowDanmaku;
|
||||
late List blockTypes;
|
||||
late double showArea;
|
||||
|
||||
@ -50,11 +50,11 @@ class DynamicsController extends GetxController {
|
||||
];
|
||||
bool flag = false;
|
||||
RxInt initialValue = 0.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
RxBool userLogin = false.obs;
|
||||
var userInfo;
|
||||
RxBool isLoadingDynamic = false.obs;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
|
||||
@ -24,7 +24,7 @@ class DynamicDetailController extends GetxController {
|
||||
ReplySortType _sortType = ReplySortType.time;
|
||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
RxInt replyReqCode = 200.obs;
|
||||
bool isEnd = false;
|
||||
|
||||
@ -37,13 +37,13 @@ class DynamicDetailController extends GetxController {
|
||||
acount.value =
|
||||
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
||||
}
|
||||
int deaultReplySortIndex =
|
||||
int defaultReplySortIndex =
|
||||
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
|
||||
if (deaultReplySortIndex == 2) {
|
||||
if (defaultReplySortIndex == 2) {
|
||||
setting.put(SettingBoxKey.replySortType, 0);
|
||||
deaultReplySortIndex = 0;
|
||||
defaultReplySortIndex = 0;
|
||||
}
|
||||
_sortType = ReplySortType.values[deaultReplySortIndex];
|
||||
_sortType = ReplySortType.values[defaultReplySortIndex];
|
||||
sortTypeTitle.value = _sortType.titles;
|
||||
sortTypeLabel.value = _sortType.labels;
|
||||
}
|
||||
|
||||
@ -9,9 +9,7 @@ import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/no_data.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/global_data_cache.dart';
|
||||
import 'package:pilipala/utils/main_stream.dart';
|
||||
import 'package:pilipala/utils/route_push.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
@ -19,7 +17,6 @@ import 'package:pilipala/utils/storage.dart';
|
||||
import '../mine/controller.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/dynamic_panel.dart';
|
||||
import 'up_dynamic/route_panel.dart';
|
||||
import 'widgets/up_panel.dart';
|
||||
|
||||
class DynamicsPage extends StatefulWidget {
|
||||
@ -35,7 +32,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
final MineController mineController = Get.put(MineController());
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _futureBuilderFutureUp;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
late ScrollController scrollController;
|
||||
|
||||
@override
|
||||
@ -209,21 +206,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
return Obx(
|
||||
() => UpPanel(
|
||||
upData: _dynamicsController.upData.value,
|
||||
onClickUpCb: (data) {
|
||||
if (GlobalDataCache().enableDynamicSwitch) {
|
||||
Navigator.push(
|
||||
context,
|
||||
PlPopupRoute(
|
||||
child: OverlayPanel(
|
||||
ctr: _dynamicsController,
|
||||
upInfo: data,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
_dynamicsController.onTapUp(data);
|
||||
}
|
||||
},
|
||||
dynamicsController: _dynamicsController,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -4,18 +4,22 @@ import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/global_data_cache.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import '../controller.dart';
|
||||
import '../up_dynamic/route_panel.dart';
|
||||
|
||||
class UpPanel extends StatefulWidget {
|
||||
final FollowUpModel upData;
|
||||
final Function? onClickUpCb;
|
||||
final DynamicsController dynamicsController;
|
||||
|
||||
const UpPanel({
|
||||
super.key,
|
||||
required this.upData,
|
||||
this.onClickUpCb,
|
||||
required this.dynamicsController,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -39,7 +43,15 @@ class _UpPanelState extends State<UpPanel> {
|
||||
|
||||
void onClickUp(data, i) {
|
||||
currentMid.value = data.mid;
|
||||
widget.onClickUpCb?.call(data);
|
||||
Navigator.push(
|
||||
context,
|
||||
PlPopupRoute(
|
||||
child: OverlayPanel(
|
||||
ctr: widget.dynamicsController,
|
||||
upInfo: data,
|
||||
),
|
||||
),
|
||||
).then((value) => {currentMid.value = -1});
|
||||
}
|
||||
|
||||
void onClickUpAni(data, i) {
|
||||
@ -49,7 +61,7 @@ class _UpPanelState extends State<UpPanel> {
|
||||
final upLen = upList.length;
|
||||
|
||||
currentMid.value = data.mid;
|
||||
widget.onClickUpCb?.call(data);
|
||||
widget.dynamicsController.onTapUp(data);
|
||||
|
||||
double moveDistance = 0.0;
|
||||
final totalItemsWidth = itemWidth * (upLen + liveLen);
|
||||
|
||||
@ -6,7 +6,7 @@ import 'package:pilipala/models/fans/result.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class FansController extends GetxController {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
int pn = 1;
|
||||
int ps = 20;
|
||||
int total = 0;
|
||||
|
||||
@ -11,7 +11,7 @@ class FavController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
RxList<FavFolderItemData> favFolderList = <FavFolderItemData>[].obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
UserInfoData? userInfo;
|
||||
int currentPage = 1;
|
||||
int pageSize = 60;
|
||||
|
||||
@ -11,7 +11,7 @@ import 'package:pilipala/utils/storage.dart';
|
||||
/// 查看自己的关注时,可以查看分类
|
||||
/// 查看其他人的关注时,只可以看全部
|
||||
class FollowController extends GetxController with GetTickerProviderStateMixin {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
int pn = 1;
|
||||
int ps = 20;
|
||||
int total = 0;
|
||||
|
||||
@ -12,11 +12,11 @@ class HistoryController extends GetxController {
|
||||
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||
RxBool isLoadingMore = false.obs;
|
||||
RxBool pauseStatus = false.obs;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box localCache = GStorage.localCache;
|
||||
RxBool isLoading = false.obs;
|
||||
RxBool enableMultiple = false.obs;
|
||||
RxInt checkedCount = 0.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
UserInfoData? userInfo;
|
||||
|
||||
@override
|
||||
|
||||
@ -14,12 +14,12 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
late List tabsCtrList;
|
||||
late List<Widget> tabsPageList;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box settingStorage = GStrorage.setting;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
Box settingStorage = GStorage.setting;
|
||||
RxBool userLogin = false.obs;
|
||||
RxString userFace = ''.obs;
|
||||
var userInfo;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late final StreamController<bool> searchBarStream =
|
||||
StreamController<bool>.broadcast();
|
||||
late bool hideSearchBar;
|
||||
|
||||
@ -5,7 +5,7 @@ import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
|
||||
class HomeAppBar extends StatelessWidget {
|
||||
const HomeAppBar({super.key});
|
||||
|
||||
@ -25,7 +25,7 @@ class HtmlRenderController extends GetxController {
|
||||
ReplySortType _sortType = ReplySortType.time;
|
||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
|
||||
@ -13,7 +13,7 @@ class LaterController extends GetxController {
|
||||
RxList<HotVideoItemModel> laterList = <HotVideoItemModel>[].obs;
|
||||
int count = 0;
|
||||
RxBool isLoading = false.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
UserInfoData? userInfo;
|
||||
|
||||
@override
|
||||
|
||||
@ -17,7 +17,7 @@ class LiveController extends GetxController {
|
||||
RxInt liveFollowingCount = 0.obs;
|
||||
bool flag = false;
|
||||
OverlayEntry? popupDialog;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
|
||||
@ -7,7 +7,7 @@ import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class LiveFollowController extends GetxController {
|
||||
RxInt crossAxisCount = 2.obs;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
int _currentPage = 1;
|
||||
RxInt liveFollowingCount = 0.obs;
|
||||
RxList<LiveFollowingItemModel> liveFollowingList =
|
||||
|
||||
@ -33,7 +33,7 @@ class LiveRoomController extends GetxController {
|
||||
int? tempCurrentQn;
|
||||
late List<Map<String, dynamic>> acceptQnList;
|
||||
RxString currentQnDesc = ''.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
int userId = 0;
|
||||
PlSocket? plSocket;
|
||||
List<String> danmuHostList = [];
|
||||
|
||||
@ -35,7 +35,7 @@ class _BottomControlState extends State<BottomControl> {
|
||||
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
||||
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box localCache = GStorage.localCache;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
||||
@ -20,12 +20,12 @@ class MainController extends GetxController {
|
||||
late List<int> navBarSort;
|
||||
final StreamController<bool> bottomBarStream =
|
||||
StreamController<bool>.broadcast();
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
DateTime? _lastPressedAt;
|
||||
late bool hideTabBar;
|
||||
late PageController pageController;
|
||||
int selectedIndex = 0;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
dynamic userInfo;
|
||||
RxBool userLogin = false.obs;
|
||||
late Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs;
|
||||
|
||||
@ -30,7 +30,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
late MineController _mineController;
|
||||
|
||||
int? _lastSelectTime; //上次点击时间
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late bool enableMYBar;
|
||||
|
||||
@override
|
||||
@ -113,14 +113,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
await GStrorage.close();
|
||||
await GStorage.close();
|
||||
EventBus().off(EventName.loginEvent);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box localCache = GStorage.localCache;
|
||||
double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||
double sheetHeight = MediaQuery.sizeOf(context).height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
|
||||
@ -18,7 +18,7 @@ class MemberController extends GetxController {
|
||||
late Map userStat;
|
||||
RxString face = ''.obs;
|
||||
String? heroTag;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
late int ownerMid;
|
||||
// 投稿列表
|
||||
RxList<VListItemModel>? archiveList = <VListItemModel>[].obs;
|
||||
|
||||
@ -41,6 +41,7 @@ class MemberSeasonsPanel extends StatelessWidget {
|
||||
'category': '1',
|
||||
'mid': item.meta!.mid.toString(),
|
||||
'seriesId': item.meta!.seriesId.toString(),
|
||||
'seasonId': item.meta!.seasonId.toString(),
|
||||
'seasonName': item.meta!.name!,
|
||||
};
|
||||
}
|
||||
|
||||
@ -24,7 +24,8 @@ class MemberSeasonsController extends GetxController {
|
||||
seasonId = int.parse(Get.parameters['seasonId']!);
|
||||
}
|
||||
if (category == '1') {
|
||||
seriesId = int.parse(Get.parameters['seriesId']!);
|
||||
seriesId = int.tryParse(Get.parameters['seriesId']!);
|
||||
seasonId = int.tryParse(Get.parameters['seasonId']!);
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +74,27 @@ class MemberSeasonsController extends GetxController {
|
||||
getSeasonDetail('onLoad');
|
||||
}
|
||||
if (category == '1') {
|
||||
getSeriesDetail('onLoad');
|
||||
if (seasonId != null) {
|
||||
getSeasonDetail('onLoad');
|
||||
}
|
||||
if (seriesId != null) {
|
||||
getSeriesDetail('onLoad');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future onRefresh() async {
|
||||
if (category == '0') {
|
||||
return getSeasonDetail('onRefresh');
|
||||
}
|
||||
if (category == '1') {
|
||||
if (seasonId != null) {
|
||||
return getSeasonDetail('onRefresh');
|
||||
}
|
||||
if (seriesId != null) {
|
||||
return getSeriesDetail('onRefresh');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,9 +23,7 @@ class _MemberSeasonsPageState extends State<MemberSeasonsPage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
category = Get.parameters['category']!;
|
||||
_futureBuilderFuture = category == '0'
|
||||
? _memberSeasonsController.getSeasonDetail('onRefresh')
|
||||
: _memberSeasonsController.getSeriesDetail('onRefresh');
|
||||
_futureBuilderFuture = _memberSeasonsController.onRefresh();
|
||||
scrollController = _memberSeasonsController.scrollController;
|
||||
scrollController.addListener(
|
||||
() {
|
||||
|
||||
@ -136,6 +136,7 @@ class MessageUtils {
|
||||
.replaceAll('}', '');
|
||||
result[linkText] = match.group(0)!;
|
||||
}
|
||||
print('str: $str');
|
||||
message += str;
|
||||
}
|
||||
} else {
|
||||
@ -144,6 +145,10 @@ class MessageUtils {
|
||||
}
|
||||
lastMatchEnd = end;
|
||||
}
|
||||
// 处理剩余的未匹配部分
|
||||
if (lastMatchEnd < text.length) {
|
||||
message += text.substring(lastMatchEnd + 1);
|
||||
}
|
||||
result['message'] = message;
|
||||
} else {
|
||||
result['message'] = text;
|
||||
|
||||
@ -17,8 +17,8 @@ class MineController extends GetxController {
|
||||
Rx<UserInfoData> userInfo = UserInfoData().obs;
|
||||
Rx<ThemeType> themeType = ThemeType.system.obs;
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
Box setting = GStrorage.setting;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box setting = GStorage.setting;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
List menuList = [
|
||||
{
|
||||
'icon': Icons.history,
|
||||
|
||||
@ -6,7 +6,7 @@ import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class MineEditController extends GetxController {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final TextEditingController unameCtr = TextEditingController();
|
||||
final TextEditingController useridCtr = TextEditingController();
|
||||
|
||||
@ -14,7 +14,7 @@ class RankController extends GetxController with GetTickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
late List tabsCtrList;
|
||||
late List<Widget> tabsPageList;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late final StreamController<bool> searchBarStream =
|
||||
StreamController<bool>.broadcast();
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ class RcmdController extends GetxController {
|
||||
// RxList<RecVideoItemModel> webVideoList = <RecVideoItemModel>[].obs;
|
||||
bool isLoadingMore = true;
|
||||
OverlayEntry? popupDialog;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
RxInt crossAxisCount = 2.obs;
|
||||
late bool enableSaveLastData;
|
||||
late String defaultRcmdType = 'web';
|
||||
|
||||
@ -15,7 +15,7 @@ class SSearchController extends GetxController {
|
||||
RxString searchKeyWord = ''.obs;
|
||||
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||
RxList<HotSearchItem> hotSearchList = <HotSearchItem>[].obs;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box localCache = GStorage.localCache;
|
||||
List historyCacheList = [];
|
||||
RxList historyList = [].obs;
|
||||
RxList<SearchSuggestItem> searchSuggestList = <SearchSuggestItem>[].obs;
|
||||
@ -23,7 +23,7 @@ class SSearchController extends GetxController {
|
||||
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
|
||||
String hintText = '搜索';
|
||||
RxString defaultSearch = ''.obs;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
bool enableHotKey = true;
|
||||
bool enableSearchSuggest = true;
|
||||
|
||||
|
||||
@ -13,9 +13,9 @@ import '../main/index.dart';
|
||||
import 'widgets/select_dialog.dart';
|
||||
|
||||
class SettingController extends GetxController {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box setting = GStrorage.setting;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
Box setting = GStorage.setting;
|
||||
Box localCache = GStorage.localCache;
|
||||
|
||||
RxBool userLogin = false.obs;
|
||||
RxBool feedBackEnable = false.obs;
|
||||
|
||||
@ -19,8 +19,8 @@ class ExtraSetting extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ExtraSettingState extends State<ExtraSetting> {
|
||||
Box setting = GStrorage.setting;
|
||||
static Box localCache = GStrorage.localCache;
|
||||
Box setting = GStorage.setting;
|
||||
static Box localCache = GStorage.localCache;
|
||||
late dynamic defaultReplySort;
|
||||
late dynamic defaultDynamicType;
|
||||
late dynamic enableSystemProxy;
|
||||
|
||||
@ -13,7 +13,7 @@ class ActionMenuSetPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ActionMenuSetPageState extends State<ActionMenuSetPage> {
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late List<String> actionTypeSort;
|
||||
late List<Map> allLabels;
|
||||
|
||||
|
||||
@ -142,7 +142,7 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
||||
}
|
||||
|
||||
class ColorSelectController extends GetxController {
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
RxBool dynamicColor = true.obs;
|
||||
RxInt type = 0.obs;
|
||||
late final List<Map<String, dynamic>> colorThemes;
|
||||
|
||||
@ -16,7 +16,7 @@ class _SetDiaplayModeState extends State<SetDiaplayMode> {
|
||||
List<DisplayMode> modes = <DisplayMode>[];
|
||||
DisplayMode? active;
|
||||
DisplayMode? preferred;
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
|
||||
final ValueNotifier<int> page = ValueNotifier<int>(0);
|
||||
late final PageController controller = PageController()
|
||||
|
||||
@ -11,7 +11,7 @@ class FontSizeSelectPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _FontSizeSelectPageState extends State<FontSizeSelectPage> {
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
List<double> list = [0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3];
|
||||
late double minsize;
|
||||
late double maxSize;
|
||||
|
||||
@ -12,7 +12,7 @@ class TabbarSetPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TabbarSetPageState extends State<TabbarSetPage> {
|
||||
Box settingStorage = GStrorage.setting;
|
||||
Box settingStorage = GStorage.setting;
|
||||
late List defaultTabs;
|
||||
late List<String> tabbarSort;
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ class NavigationBarSetPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NavigationbarSetPageState extends State<NavigationBarSetPage> {
|
||||
Box settingStorage = GStrorage.setting;
|
||||
Box settingStorage = GStorage.setting;
|
||||
late List defaultNavTabs;
|
||||
late List<int> navBarSort;
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ class PlayGesturePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlayGesturePageState extends State<PlayGesturePage> {
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late int fullScreenGestureMode;
|
||||
|
||||
@override
|
||||
|
||||
@ -14,8 +14,8 @@ class PlaySpeedPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
Box videoStorage = GStrorage.video;
|
||||
Box settingStorage = GStrorage.setting;
|
||||
Box videoStorage = GStorage.video;
|
||||
Box settingStorage = GStorage.setting;
|
||||
late double playSpeedDefault;
|
||||
late List<double> playSpeedSystem;
|
||||
late double longPressSpeedDefault;
|
||||
|
||||
@ -23,7 +23,7 @@ class PlaySetting extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlaySettingState extends State<PlaySetting> {
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late dynamic defaultVideoQa;
|
||||
late dynamic defaultLiveQa;
|
||||
late dynamic defaultAudioQa;
|
||||
|
||||
@ -14,7 +14,7 @@ class PrivacySetting extends StatefulWidget {
|
||||
|
||||
class _PrivacySettingState extends State<PrivacySetting> {
|
||||
bool userLogin = false;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
var userInfo;
|
||||
|
||||
@override
|
||||
|
||||
@ -17,10 +17,10 @@ class RecommendSetting extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RecommendSettingState extends State<RecommendSetting> {
|
||||
Box setting = GStrorage.setting;
|
||||
static Box localCache = GStrorage.localCache;
|
||||
Box setting = GStorage.setting;
|
||||
static Box localCache = GStorage.localCache;
|
||||
late dynamic defaultRcmdType;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
late dynamic userInfo;
|
||||
bool userLogin = false;
|
||||
late dynamic accessKeyInfo;
|
||||
@ -247,10 +247,9 @@ class _RecommendSettingState extends State<RecommendSetting> {
|
||||
'* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n'
|
||||
'* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n'
|
||||
'* 后续可能会增加更多过滤条件,敬请期待。',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline.withOpacity(0.7)),
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline.withOpacity(0.7)),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
||||
@ -28,7 +28,7 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
final ColorSelectController colorSelectController =
|
||||
Get.put(ColorSelectController());
|
||||
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late int picQuality;
|
||||
late ThemeType _tempThemeValue;
|
||||
late dynamic defaultCustomRows;
|
||||
|
||||
@ -19,7 +19,7 @@ class SetSelectItem extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SetSelectItemState extends State<SetSelectItem> {
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late dynamic currentVal;
|
||||
late int currentIndex;
|
||||
late List menus;
|
||||
|
||||
@ -28,7 +28,7 @@ class SetSwitchItem extends StatefulWidget {
|
||||
|
||||
class _SetSwitchItemState extends State<SetSwitchItem> {
|
||||
// ignore: non_constant_identifier_names
|
||||
Box Setting = GStrorage.setting;
|
||||
Box Setting = GStorage.setting;
|
||||
late bool val;
|
||||
|
||||
@override
|
||||
|
||||
@ -11,7 +11,7 @@ import '../../models/user/sub_folder.dart';
|
||||
class SubController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Rx<SubFolderModelData> subFolderData = SubFolderModelData().obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
UserInfoData? userInfo;
|
||||
int currentPage = 1;
|
||||
int pageSize = 20;
|
||||
|
||||
@ -6,11 +6,14 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||
import 'package:pilipala/http/common.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/models/sponsor_block/segment.dart';
|
||||
import 'package:pilipala/models/sponsor_block/segment_type.dart';
|
||||
import 'package:pilipala/models/video/later.dart';
|
||||
import 'package:pilipala/models/video/play/quality.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
@ -68,9 +71,9 @@ class VideoDetailController extends GetxController
|
||||
RxBool enableHA = false.obs;
|
||||
|
||||
/// 本地存储
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box setting = GStrorage.setting;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
Box localCache = GStorage.localCache;
|
||||
Box setting = GStorage.setting;
|
||||
|
||||
RxInt oid = 0.obs;
|
||||
// 评论id 请求楼中楼评论使用
|
||||
@ -120,6 +123,8 @@ class VideoDetailController extends GetxController
|
||||
RxBool isWatchLaterVisible = false.obs;
|
||||
RxString watchLaterTitle = ''.obs;
|
||||
RxInt watchLaterCount = 0.obs;
|
||||
List<SegmentDataModel> skipSegments = <SegmentDataModel>[];
|
||||
int? lastPosition;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -188,6 +193,11 @@ class VideoDetailController extends GetxController
|
||||
tabCtr.addListener(() {
|
||||
onTabChanged();
|
||||
});
|
||||
|
||||
/// 仅投稿视频skip
|
||||
if (videoType == SearchType.video) {
|
||||
querySkipSegments();
|
||||
}
|
||||
}
|
||||
|
||||
showReplyReplyPanel(oid, fRpid, firstFloor, currentReply, loadMore) {
|
||||
@ -305,6 +315,7 @@ class VideoDetailController extends GetxController
|
||||
plPlayerController.headerControl = headerControl;
|
||||
|
||||
plPlayerController.subtitles.value = subtitles;
|
||||
onPositionChanged();
|
||||
}
|
||||
|
||||
// 视频链接
|
||||
@ -706,6 +717,53 @@ class VideoDetailController extends GetxController
|
||||
isWatchLaterVisible.value = tabCtr.index == 0;
|
||||
}
|
||||
|
||||
// 获取sponsorBlock数据
|
||||
Future querySkipSegments() async {
|
||||
var res = await CommonHttp.querySkipSegments(bvid: bvid);
|
||||
if (res['status']) {
|
||||
/// TODO 根据segmentType过滤数据
|
||||
skipSegments = res['data'] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
// 监听视频进度
|
||||
void onPositionChanged() async {
|
||||
final List<SegmentDataModel> sponsorSkipSegments = skipSegments
|
||||
.where((e) => e.category!.value == SegmentType.sponsor.value)
|
||||
.toList();
|
||||
if (sponsorSkipSegments.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
plPlayerController.videoPlayerController?.stream.position
|
||||
.listen((Duration position) async {
|
||||
final int positionMs = position.inSeconds;
|
||||
|
||||
// 如果当前秒与上次处理的秒相同,则直接返回
|
||||
if (lastPosition != null && lastPosition! == positionMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastPosition = positionMs;
|
||||
for (SegmentDataModel segment in sponsorSkipSegments) {
|
||||
try {
|
||||
final segmentStart = segment.segment!.first.toInt();
|
||||
final segmentEnd = segment.segment!.last.toInt();
|
||||
|
||||
/// 只有顺序播放时才skip,跳转时间点不会skip
|
||||
if (positionMs == segmentStart && !segment.isSkip) {
|
||||
await plPlayerController.videoPlayerController
|
||||
?.seek(Duration(seconds: segmentEnd));
|
||||
segment.isSkip = true;
|
||||
SmartDialog.showToast('已跳过${segment.category!.label}片段');
|
||||
}
|
||||
} catch (err) {
|
||||
SmartDialog.showToast('skipSegments error: $err');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
|
||||
@ -41,7 +41,7 @@ class VideoIntroController extends GetxController {
|
||||
RxBool hasFav = false.obs;
|
||||
// 是否不喜欢
|
||||
RxBool hasDisLike = false.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
bool userLogin = false;
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
List addMediaIdsNew = [];
|
||||
@ -63,6 +63,7 @@ class VideoIntroController extends GetxController {
|
||||
PersistentBottomSheetController? bottomSheetController;
|
||||
late bool enableRelatedVideo;
|
||||
UgcSeason? ugcSeason;
|
||||
RxList<Part> pages = <Part>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -84,18 +85,20 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
|
||||
// 获取视频简介&分p
|
||||
Future queryVideoIntro() async {
|
||||
Future queryVideoIntro({cover}) async {
|
||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||
if (result['status']) {
|
||||
videoDetail.value = result['data']!;
|
||||
ugcSeason = result['data']!.ugcSeason;
|
||||
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
|
||||
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
|
||||
pages.value = result['data']!.pages!;
|
||||
lastPlayCid.value = videoDetail.value.cid!;
|
||||
if (pages.isNotEmpty) {
|
||||
lastPlayCid.value = pages.first.cid!;
|
||||
}
|
||||
final VideoDetailController videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: heroTag);
|
||||
videoDetailCtr.tabs.value = ['简介', '评论 ${result['data']?.stat?.reply}'];
|
||||
videoDetailCtr.cover.value = result['data'].pic ?? '';
|
||||
videoDetailCtr.cover.value = cover ?? result['data'].pic ?? '';
|
||||
// 获取到粉丝数再返回
|
||||
await queryUserStat();
|
||||
}
|
||||
@ -470,8 +473,7 @@ class VideoIntroController extends GetxController {
|
||||
videoReplyCtr.queryReplyList(type: 'init');
|
||||
} catch (_) {}
|
||||
this.bvid = bvid;
|
||||
lastPlayCid.value = cid;
|
||||
await queryVideoIntro();
|
||||
await queryVideoIntro(cover: cover);
|
||||
}
|
||||
|
||||
void startTimer() {
|
||||
@ -521,9 +523,8 @@ class VideoIntroController extends GetxController {
|
||||
final List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||
episodes.addAll(episodesList);
|
||||
}
|
||||
} else if (videoDetail.value.pages != null) {
|
||||
} else if (pages.isNotEmpty) {
|
||||
isPages = true;
|
||||
final List<Part> pages = videoDetail.value.pages!;
|
||||
episodes.addAll(pages);
|
||||
}
|
||||
|
||||
@ -621,10 +622,9 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (videoDetail.value.pages != null &&
|
||||
videoDetail.value.pages!.length > 1) {
|
||||
if (pages.length > 1) {
|
||||
dataType = VideoEpidoesType.videoPart;
|
||||
episodes = videoDetail.value.pages!;
|
||||
episodes = pages;
|
||||
}
|
||||
|
||||
DrawerUtils.showRightDialog(
|
||||
|
||||
@ -137,8 +137,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
late String heroTag;
|
||||
late final VideoIntroController videoIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
final Box<dynamic> localCache = GStrorage.localCache;
|
||||
final Box<dynamic> setting = GStrorage.setting;
|
||||
final Box<dynamic> localCache = GStorage.localCache;
|
||||
final Box<dynamic> setting = GStorage.setting;
|
||||
late double sheetHeight;
|
||||
late final dynamic owner;
|
||||
late int mid;
|
||||
@ -404,27 +404,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
Obx(
|
||||
() => SeasonPanel(
|
||||
ugcSeason: widget.videoDetail!.ugcSeason!,
|
||||
cid: videoIntroController.lastPlayCid.value != 0
|
||||
? videoIntroController.lastPlayCid.value
|
||||
: widget.videoDetail!.pages!.first.cid,
|
||||
cid: videoIntroController.lastPlayCid.value,
|
||||
sheetHeight: videoDetailCtr.sheetHeight.value,
|
||||
changeFuc: (bvid, cid, aid, cover) =>
|
||||
videoIntroController.changeSeasonOrbangu(
|
||||
bvid,
|
||||
cid,
|
||||
aid,
|
||||
cover,
|
||||
),
|
||||
changeFuc: videoIntroController.changeSeasonOrbangu,
|
||||
videoIntroCtr: videoIntroController,
|
||||
),
|
||||
)
|
||||
],
|
||||
// 合集 videoEpisode
|
||||
if (widget.videoDetail!.pages != null &&
|
||||
widget.videoDetail!.pages!.length > 1) ...[
|
||||
if (videoIntroController.pages.length > 1) ...[
|
||||
Obx(
|
||||
() => PagesPanel(
|
||||
pages: widget.videoDetail!.pages!,
|
||||
pages: videoIntroController.pages,
|
||||
cid: videoIntroController.lastPlayCid.value,
|
||||
sheetHeight: videoDetailCtr.sheetHeight.value,
|
||||
changeFuc: (cid, cover) =>
|
||||
|
||||
@ -16,7 +16,7 @@ class FavPanel extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _FavPanelState extends State<FavPanel> {
|
||||
final Box<dynamic> localCache = GStrorage.localCache;
|
||||
final Box<dynamic> localCache = GStorage.localCache;
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
|
||||
@ -18,7 +18,7 @@ class GroupPanel extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GroupPanelState extends State<GroupPanel> {
|
||||
final Box<dynamic> localCache = GStrorage.localCache;
|
||||
final Box<dynamic> localCache = GStorage.localCache;
|
||||
late Future _futureBuilderFuture;
|
||||
late List<MemberTagItemModel> tagsList;
|
||||
bool showDefault = true;
|
||||
|
||||
@ -3,7 +3,6 @@ import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
||||
import '../../../../../common/pages_bottom_sheet.dart';
|
||||
import '../../../../../models/common/video_episode_type.dart';
|
||||
@ -32,25 +31,26 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
late int cid;
|
||||
late RxInt currentIndex = (-1).obs;
|
||||
final String heroTag = Get.arguments['heroTag'];
|
||||
late VideoDetailController _videoDetailController;
|
||||
final ScrollController listViewScrollCtr = ScrollController();
|
||||
late PersistentBottomSheetController? _bottomSheetController;
|
||||
PersistentBottomSheetController? _bottomSheetController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
cid = widget.cid;
|
||||
episodes = widget.pages;
|
||||
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
||||
currentIndex.value = episodes.indexWhere((Part e) => e.cid == cid);
|
||||
scrollToIndex();
|
||||
_videoDetailController.cid.listen((int p0) {
|
||||
updateCurrentIndexAndScroll();
|
||||
widget.videoIntroCtr.lastPlayCid.listen((int p0) {
|
||||
cid = p0;
|
||||
currentIndex.value = episodes.indexWhere((Part e) => e.cid == cid);
|
||||
scrollToIndex();
|
||||
updateCurrentIndexAndScroll();
|
||||
});
|
||||
}
|
||||
|
||||
void updateCurrentIndexAndScroll() {
|
||||
currentIndex.value = widget.pages.indexWhere((Part e) => e.cid == cid);
|
||||
scrollToIndex();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
listViewScrollCtr.dispose();
|
||||
@ -60,7 +60,10 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
void changeFucCall(item, i) async {
|
||||
widget.changeFuc?.call(item.cid, item.cover);
|
||||
currentIndex.value = i;
|
||||
_bottomSheetController?.close();
|
||||
cid = item.cid;
|
||||
if (_bottomSheetController != null) {
|
||||
_bottomSheetController?.close();
|
||||
}
|
||||
scrollToIndex();
|
||||
}
|
||||
|
||||
@ -112,7 +115,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
widget.videoIntroCtr.bottomSheetController =
|
||||
_bottomSheetController = EpisodeBottomSheet(
|
||||
currentCid: cid,
|
||||
episodes: episodes,
|
||||
episodes: widget.pages,
|
||||
changeFucCall: changeFucCall,
|
||||
sheetHeight: widget.sheetHeight,
|
||||
dataType: VideoEpidoesType.videoPart,
|
||||
|
||||
@ -33,6 +33,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
||||
final String heroTag = Get.arguments['heroTag'];
|
||||
late VideoDetailController _videoDetailController;
|
||||
late PersistentBottomSheetController? _bottomSheetController;
|
||||
int currentEpisodeIndex = -1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -41,13 +42,12 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
||||
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
||||
|
||||
/// 根据 cid 找到对应集,找到对应 episodes
|
||||
/// 有多个episodes时,只显示其中一个
|
||||
/// TODO 同时显示多个合集
|
||||
final List<SectionItem> sections = widget.ugcSeason.sections!;
|
||||
for (int i = 0; i < sections.length; i++) {
|
||||
final List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||
for (int j = 0; j < episodesList.length; j++) {
|
||||
if (episodesList[j].cid == cid) {
|
||||
currentEpisodeIndex = i;
|
||||
episodes = episodesList;
|
||||
continue;
|
||||
}
|
||||
@ -55,10 +55,10 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
||||
}
|
||||
|
||||
/// 取对应 season_id 的 episodes
|
||||
currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid);
|
||||
getCurrentIndex();
|
||||
_videoDetailController.cid.listen((int p0) {
|
||||
cid = p0;
|
||||
currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid);
|
||||
getCurrentIndex();
|
||||
});
|
||||
}
|
||||
|
||||
@ -73,6 +73,23 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
||||
_bottomSheetController?.close();
|
||||
}
|
||||
|
||||
// 获取currentIndex
|
||||
void getCurrentIndex() {
|
||||
currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid);
|
||||
final List<SectionItem> sections = widget.ugcSeason.sections!;
|
||||
if (sections.length == 1 && sections.first.type == 1) {
|
||||
final List<EpisodeItem> episodesList = sections.first.episodes!;
|
||||
for (int i = 0; i < episodesList.length; i++) {
|
||||
for (int j = 0; j < episodesList[i].pages!.length; j++) {
|
||||
if (episodesList[i].pages![j].cid == cid) {
|
||||
currentIndex.value = i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildEpisodeListItem(
|
||||
EpisodeItem episode,
|
||||
int index,
|
||||
@ -125,6 +142,8 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
||||
sheetHeight: widget.sheetHeight,
|
||||
dataType: VideoEpidoesType.videoEpisode,
|
||||
ugcSeason: widget.ugcSeason,
|
||||
currentEpisodeIndex: currentEpisodeIndex,
|
||||
currentIndex: currentIndex.value,
|
||||
).show(context);
|
||||
},
|
||||
child: Padding(
|
||||
|
||||
@ -32,20 +32,20 @@ class VideoReplyController extends GetxController {
|
||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
||||
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
RxInt replyReqCode = 200.obs;
|
||||
bool isEnd = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
int deaultReplySortIndex =
|
||||
int defaultReplySortIndex =
|
||||
setting.get(SettingBoxKey.replySortType, defaultValue: 0) as int;
|
||||
if (deaultReplySortIndex == 2) {
|
||||
if (defaultReplySortIndex == 2) {
|
||||
setting.put(SettingBoxKey.replySortType, 0);
|
||||
deaultReplySortIndex = 0;
|
||||
defaultReplySortIndex = 0;
|
||||
}
|
||||
_sortType = ReplySortType.values[deaultReplySortIndex];
|
||||
_sortType = ReplySortType.values[defaultReplySortIndex];
|
||||
sortTypeTitle.value = _sortType.titles;
|
||||
sortTypeLabel.value = _sortType.labels;
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ import 'package:pilipala/utils/utils.dart';
|
||||
import 'reply_save.dart';
|
||||
import 'zan.dart';
|
||||
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
|
||||
class ReplyItem extends StatelessWidget {
|
||||
const ReplyItem({
|
||||
@ -235,32 +235,33 @@ class ReplyItem extends StatelessWidget {
|
||||
// title
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4),
|
||||
child: !replySave
|
||||
? LayoutBuilder(builder:
|
||||
(BuildContext context, BoxConstraints boxConstraints) {
|
||||
String text = replyItem?.content?.message ?? '';
|
||||
bool didExceedMaxLines = false;
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
TextPainter? textPainter;
|
||||
final int maxLines =
|
||||
replyItem!.content!.isText! && replyLevel == '1'
|
||||
? 6
|
||||
: 999;
|
||||
try {
|
||||
textPainter = TextPainter(
|
||||
text: TextSpan(text: text),
|
||||
maxLines: maxLines,
|
||||
textDirection: Directionality.of(context),
|
||||
);
|
||||
textPainter.layout(maxWidth: maxWidth);
|
||||
didExceedMaxLines = textPainter.didExceedMaxLines;
|
||||
} catch (e) {
|
||||
debugPrint('Error while measuring text: $e');
|
||||
didExceedMaxLines = false;
|
||||
}
|
||||
return replyContent(context, didExceedMaxLines, textPainter);
|
||||
})
|
||||
: replyContent(context, false, null),
|
||||
child: Text.rich(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines:
|
||||
replyLevel == '1' && replyItem!.content!.isText! ? 5 : 999,
|
||||
style: const TextStyle(height: 1.75),
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replyItem!.isTop!)
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: PBadge(
|
||||
text: 'TOP',
|
||||
size: 'small',
|
||||
stack: 'normal',
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
),
|
||||
),
|
||||
buildContent(
|
||||
context,
|
||||
replyItem!,
|
||||
replyReply,
|
||||
null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 操作区域
|
||||
bottonAction(context, replyItem!.replyControl, replySave),
|
||||
@ -281,36 +282,6 @@ class ReplyItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget replyContent(
|
||||
BuildContext context, bool? didExceedMaxLines, TextPainter? textPainter) {
|
||||
return Text.rich(
|
||||
style: const TextStyle(height: 1.75),
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replyItem!.isTop!)
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: PBadge(
|
||||
text: 'TOP',
|
||||
size: 'small',
|
||||
stack: 'normal',
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
),
|
||||
),
|
||||
buildContent(
|
||||
context,
|
||||
replyItem!,
|
||||
replyReply,
|
||||
null,
|
||||
didExceedMaxLines ?? false,
|
||||
textPainter,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 感谢、回复、复制
|
||||
Widget bottonAction(BuildContext context, replyControl, replySave) {
|
||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
@ -493,8 +464,12 @@ class ReplyItemRow extends StatelessWidget {
|
||||
fs: 9,
|
||||
),
|
||||
),
|
||||
buildContent(context, replies![i], replyReply,
|
||||
replyItem, false, null),
|
||||
buildContent(
|
||||
context,
|
||||
replies![i],
|
||||
replyReply,
|
||||
replyItem,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -540,8 +515,6 @@ InlineSpan buildContent(
|
||||
replyItem,
|
||||
replyReply,
|
||||
fReplyItem,
|
||||
bool didExceedMaxLines,
|
||||
TextPainter? textPainter,
|
||||
) {
|
||||
final String routePath = Get.currentRoute;
|
||||
bool isVideoPage = routePath.startsWith('/video');
|
||||
@ -553,25 +526,6 @@ InlineSpan buildContent(
|
||||
final content = replyItem.content;
|
||||
final List<InlineSpan> spanChilds = <InlineSpan>[];
|
||||
|
||||
if (didExceedMaxLines && content.message != '') {
|
||||
final textSize = textPainter!.size;
|
||||
var position = textPainter.getPositionForOffset(
|
||||
Offset(
|
||||
textSize.width,
|
||||
textSize.height,
|
||||
),
|
||||
);
|
||||
final endOffset = textPainter.getOffsetBefore(position.offset);
|
||||
|
||||
if (endOffset != null && endOffset > 0) {
|
||||
content.message = content.message.substring(0, endOffset);
|
||||
} else {
|
||||
content.message = content.message.substring(0, position.offset);
|
||||
}
|
||||
} else {
|
||||
content.message = content.message2;
|
||||
}
|
||||
|
||||
// 投票
|
||||
if (content.vote.isNotEmpty) {
|
||||
content.message.splitMapJoin(RegExp(r"\{vote:.*?\}"),
|
||||
@ -921,17 +875,6 @@ InlineSpan buildContent(
|
||||
}
|
||||
}
|
||||
|
||||
if (didExceedMaxLines) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '\n查看更多',
|
||||
style: TextStyle(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 图片渲染
|
||||
if (content.pictures.isNotEmpty) {
|
||||
final List<String> picList = <String>[];
|
||||
|
||||
@ -42,7 +42,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
|
||||
class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
late VideoReplyReplyController _videoReplyReplyController;
|
||||
late AnimationController replyAnimationCtl;
|
||||
final Box<dynamic> localCache = GStrorage.localCache;
|
||||
final Box<dynamic> localCache = GStorage.localCache;
|
||||
Future? _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
|
||||
|
||||
@ -54,8 +54,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
Rx<PlayerStatus> playerStatus = PlayerStatus.playing.obs;
|
||||
double doubleOffset = 0;
|
||||
|
||||
final Box<dynamic> localCache = GStrorage.localCache;
|
||||
final Box<dynamic> setting = GStrorage.setting;
|
||||
final Box<dynamic> localCache = GStorage.localCache;
|
||||
final Box<dynamic> setting = GStorage.setting;
|
||||
late double statusBarHeight;
|
||||
final double videoHeight = Get.size.width * 9 / 16;
|
||||
late Future _futureBuilderFuture;
|
||||
@ -101,7 +101,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
|
||||
videoSourceInit();
|
||||
appbarStreamListen();
|
||||
fullScreenStatusListener();
|
||||
if (autoPlayEnable) {
|
||||
fullScreenStatusListener();
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
floating = vdCtr.floating!;
|
||||
}
|
||||
@ -137,7 +139,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
autoEnterPip(status: status);
|
||||
if (status == PlayerStatus.completed) {
|
||||
// 结束播放退出全屏
|
||||
if (autoExitFullcreen) {
|
||||
if (autoExitFullcreen && plPlayerController!.isFullScreen.value) {
|
||||
plPlayerController!.triggerFullScreen(status: false);
|
||||
}
|
||||
shutdownTimerService.handleWaitingFinished();
|
||||
@ -184,6 +186,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
await vdCtr.playerInit(autoplay: true);
|
||||
plPlayerController = vdCtr.plPlayerController;
|
||||
plPlayerController!.addStatusLister(playerListener);
|
||||
fullScreenStatusListener();
|
||||
vdCtr.autoPlay.value = true;
|
||||
vdCtr.isShowCover.value = false;
|
||||
isShowing.value = true;
|
||||
|
||||
@ -52,8 +52,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
static const TextStyle subTitleStyle = TextStyle(fontSize: 12);
|
||||
static const TextStyle titleStyle = TextStyle(fontSize: 14);
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
final Box<dynamic> localCache = GStrorage.localCache;
|
||||
final Box<dynamic> videoStorage = GStrorage.video;
|
||||
final Box<dynamic> localCache = GStorage.localCache;
|
||||
final Box<dynamic> videoStorage = GStorage.video;
|
||||
late List<double> speedsList;
|
||||
double buttonSpace = 8;
|
||||
RxBool isFullScreen = false.obs;
|
||||
|
||||
@ -20,7 +20,7 @@ class WhisperDetailController extends GetxController {
|
||||
//表情转换图片规则
|
||||
RxList<dynamic> eInfos = [].obs;
|
||||
final TextEditingController replyContentController = TextEditingController();
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
List emoteList = [];
|
||||
List<String> picList = [];
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
late double emoteHeight = 230.0;
|
||||
double keyboardHeight = 0.0; // 键盘高度
|
||||
RxString toolbarType = ''.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
||||
@ -56,8 +56,7 @@ class ChatItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isOwner =
|
||||
item.senderUid == GStrorage.userInfo.get('userInfoCache').mid;
|
||||
bool isOwner = item.senderUid == GStorage.userInfo.get('userInfoCache').mid;
|
||||
|
||||
bool isPic = item.msgType == MsgType.pic.value; // 图片
|
||||
bool isText = item.msgType == MsgType.text.value; // 文本
|
||||
|
||||
@ -27,9 +27,9 @@ import '../../models/video/subTitile/content.dart';
|
||||
import '../../models/video/subTitile/result.dart';
|
||||
// import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
Box videoStorage = GStrorage.video;
|
||||
Box setting = GStrorage.setting;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box videoStorage = GStorage.video;
|
||||
Box setting = GStorage.setting;
|
||||
Box localCache = GStorage.localCache;
|
||||
|
||||
class PlPlayerController {
|
||||
Player? _videoPlayerController;
|
||||
@ -1033,6 +1033,8 @@ class PlPlayerController {
|
||||
if (progress >= content['from']! && progress <= content['to']!) {
|
||||
subtitleContent.value = content['content']!;
|
||||
return;
|
||||
} else {
|
||||
subtitleContent.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
final RxDouble _distance = 0.0.obs;
|
||||
final RxBool _volumeInterceptEventStream = false.obs;
|
||||
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
late FullScreenMode mode;
|
||||
late int defaultBtmProgressBehavior;
|
||||
late bool enableQuickDouble;
|
||||
|
||||
@ -66,7 +66,7 @@ import '../pages/whisper/index.dart';
|
||||
import '../pages/whisper_detail/index.dart';
|
||||
import '../utils/storage.dart';
|
||||
|
||||
Box<dynamic> setting = GStrorage.setting;
|
||||
Box<dynamic> setting = GStorage.setting;
|
||||
|
||||
class Routes {
|
||||
static final List<GetPage<dynamic>> getPages = [
|
||||
|
||||
@ -24,7 +24,7 @@ Future<VideoPlayerServiceHandler> initAudioService() async {
|
||||
|
||||
class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler {
|
||||
static final List<MediaItem> _item = [];
|
||||
Box setting = GStrorage.setting;
|
||||
Box setting = GStorage.setting;
|
||||
bool enableBackgroundPlay = false;
|
||||
PlPlayerController player = PlPlayerController();
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ void DisableBatteryOpt() async {
|
||||
}
|
||||
// 本地缓存中读取 是否禁用了电池优化 默认未禁用
|
||||
bool isDisableBatteryOptLocal =
|
||||
GStrorage.localCache.get('isDisableBatteryOptLocal', defaultValue: false);
|
||||
GStorage.localCache.get('isDisableBatteryOptLocal', defaultValue: false);
|
||||
if (!isDisableBatteryOptLocal) {
|
||||
final isBatteryOptimizationDisabled =
|
||||
await DisableBatteryOptimization.isBatteryOptimizationDisabled;
|
||||
@ -17,11 +17,11 @@ void DisableBatteryOpt() async {
|
||||
final hasDisabled = await DisableBatteryOptimization
|
||||
.showDisableBatteryOptimizationSettings();
|
||||
// 设置为已禁用
|
||||
GStrorage.localCache.put('isDisableBatteryOptLocal', hasDisabled == true);
|
||||
GStorage.localCache.put('isDisableBatteryOptLocal', hasDisabled == true);
|
||||
}
|
||||
}
|
||||
|
||||
bool isManufacturerBatteryOptimizationDisabled = GStrorage.localCache
|
||||
bool isManufacturerBatteryOptimizationDisabled = GStorage.localCache
|
||||
.get('isManufacturerBatteryOptimizationDisabled', defaultValue: false);
|
||||
if (!isManufacturerBatteryOptimizationDisabled) {
|
||||
final isManBatteryOptimizationDisabled = await DisableBatteryOptimization
|
||||
@ -33,7 +33,7 @@ void DisableBatteryOpt() async {
|
||||
"按照步骤操作以禁用电池优化,以保证应用在后台正常运行",
|
||||
);
|
||||
// 设置为已禁用
|
||||
GStrorage.localCache.put(
|
||||
GStorage.localCache.put(
|
||||
'isManufacturerBatteryOptimizationDisabled', hasDisabled == true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ class Data {
|
||||
}
|
||||
|
||||
static Future historyStatus() async {
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box localCache = GStorage.localCache;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
if (userInfoCache.get('userInfoCache') == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'storage.dart';
|
||||
|
||||
Box<dynamic> setting = GStrorage.setting;
|
||||
Box<dynamic> setting = GStorage.setting;
|
||||
void feedBack() {
|
||||
// 设置中是否开启
|
||||
final bool enable =
|
||||
|
||||
@ -5,10 +5,10 @@ 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;
|
||||
Box setting = GStorage.setting;
|
||||
Box localCache = GStorage.localCache;
|
||||
Box videoStorage = GStorage.video;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
|
||||
class GlobalDataCache {
|
||||
late int imgQuality;
|
||||
|
||||
@ -18,9 +18,6 @@ import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class LoginUtils {
|
||||
static Box userInfoCache = GStrorage.userInfo;
|
||||
static Box localCache = GStrorage.localCache;
|
||||
|
||||
static Future refreshLoginStatus(bool status) async {
|
||||
try {
|
||||
// 更改我的页面登录状态
|
||||
@ -76,7 +73,7 @@ class LoginUtils {
|
||||
if (result['status'] && result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
try {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
if (!userInfoCache.isOpen) {
|
||||
userInfoCache = await Hive.openBox('userInfo');
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ class RecommendFilter {
|
||||
}
|
||||
|
||||
static void update() {
|
||||
var setting = GStrorage.setting;
|
||||
var setting = GStorage.setting;
|
||||
// filterUnfollowedRatio =
|
||||
// setting.get(SettingBoxKey.filterUnfollowedRatio, defaultValue: 0);
|
||||
minDurationForRcmd =
|
||||
|
||||
@ -3,7 +3,7 @@ import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:pilipala/models/user/info.dart';
|
||||
|
||||
class GStrorage {
|
||||
class GStorage {
|
||||
static late final Box<dynamic> userInfo;
|
||||
static late final Box<dynamic> localCache;
|
||||
static late final Box<dynamic> setting;
|
||||
|
||||
@ -9,7 +9,7 @@ import '../http/index.dart';
|
||||
import 'storage.dart';
|
||||
|
||||
class WbiSign {
|
||||
static Box<dynamic> localCache = GStrorage.localCache;
|
||||
static Box<dynamic> localCache = GStorage.localCache;
|
||||
final List<int> mixinKeyEncTab = <int>[
|
||||
46,
|
||||
47,
|
||||
|
||||
20
pubspec.lock
20
pubspec.lock
@ -533,18 +533,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
||||
sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.2+1"
|
||||
version: "0.9.3"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c
|
||||
sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.4+1"
|
||||
version: "0.9.4+2"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -557,10 +557,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69"
|
||||
sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.3+2"
|
||||
version: "0.9.3+3"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -842,10 +842,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447"
|
||||
sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.8.12"
|
||||
version: "0.8.12+1"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1438,10 +1438,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: scrollview_observer
|
||||
sha256: fa408bcfd41e19da841eb53fc471f8f952d5ef818b854d2505c4bb3f0c876381
|
||||
sha256: "8537ba32e5a15ade301e5c77ae858fd8591695defaad1821eca9eeb4ac28a157"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.22.0"
|
||||
version: "1.23.0"
|
||||
sentry:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user