Merge branch 'main' into design
This commit is contained in:
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user