mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-27 14:00:12 +08:00
27
README.md
27
README.md
@@ -22,22 +22,18 @@
|
|||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 开发环境
|
|
||||||
|
|
||||||
```bash
|
|
||||||
[✓] Flutter (Channel stable, 3.24.0, on Microsoft Windows [版本 10.0.19045.4046], locale zh-CN)
|
|
||||||
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
|
|
||||||
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
|
|
||||||
[✓] Chrome - develop for the web
|
|
||||||
[✓] Android Studio (version 2022.3)
|
|
||||||
[✓] VS Code (version 1.85.1)
|
|
||||||
[✓] Connected device (3 available)
|
|
||||||
[✓] Network resources
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
## 适配平台
|
||||||
|
|
||||||
|
- [x] Android
|
||||||
|
- [x] iOS
|
||||||
|
- [x] Pad
|
||||||
|
- [x] Windows
|
||||||
|
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
## refactor
|
## refactor
|
||||||
|
|
||||||
@@ -136,11 +132,6 @@
|
|||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
目前着重移动端(Android、iOS)和Pad端,暂时没有适配桌面端、手表端等
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
|
|
||||||
- [x] 推荐视频列表(app端)
|
- [x] 推荐视频列表(app端)
|
||||||
- [x] 最热视频列表
|
- [x] 最热视频列表
|
||||||
- [x] 热门直播
|
- [x] 热门直播
|
||||||
|
|||||||
BIN
assets/images/logo/logo_large.png
Normal file
BIN
assets/images/logo/logo_large.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -42,9 +42,11 @@ class _EmotePanelState extends State<EmotePanel>
|
|||||||
ThemeData theme,
|
ThemeData theme,
|
||||||
LoadingState<List<Package>?> loadingState,
|
LoadingState<List<Package>?> loadingState,
|
||||||
) {
|
) {
|
||||||
late final color = Get.currentRoute.startsWith('/whisperDetail')
|
late final color = ElevationOverlay.colorWithOverlay(
|
||||||
? theme.colorScheme.surface
|
theme.colorScheme.surface,
|
||||||
: theme.colorScheme.onInverseSurface;
|
theme.hoverColor,
|
||||||
|
Get.currentRoute.startsWith('/whisperDetail') ? 8 : 2,
|
||||||
|
);
|
||||||
return switch (loadingState) {
|
return switch (loadingState) {
|
||||||
Loading() => loadingWidget,
|
Loading() => loadingWidget,
|
||||||
Success(:var response) =>
|
Success(:var response) =>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class EpisodePanel extends CommonSlidePage {
|
|||||||
final int initialTabIndex;
|
final int initialTabIndex;
|
||||||
final bool? isSupportReverse;
|
final bool? isSupportReverse;
|
||||||
final bool? isReversed;
|
final bool? isReversed;
|
||||||
final ValueChanged<ugc.BaseEpisodeItem> onChangeEpisode;
|
final Future<bool> Function(ugc.BaseEpisodeItem) onChangeEpisode;
|
||||||
final VoidCallback? onReverse;
|
final VoidCallback? onReverse;
|
||||||
final VoidCallback? onClose;
|
final VoidCallback? onClose;
|
||||||
|
|
||||||
@@ -432,19 +432,23 @@ class _EpisodePanelState extends State<EpisodePanel>
|
|||||||
}
|
}
|
||||||
SmartDialog.showToast('切换到:$title');
|
SmartDialog.showToast('切换到:$title');
|
||||||
widget.onClose?.call();
|
widget.onClose?.call();
|
||||||
if (!showTitle) {
|
|
||||||
_currentItemIndex = index;
|
widget.onChangeEpisode(episode).then((res) {
|
||||||
}
|
if (res) {
|
||||||
widget.onChangeEpisode(episode);
|
if (!showTitle) {
|
||||||
if (widget.type == EpisodeType.season) {
|
_currentItemIndex = index;
|
||||||
try {
|
}
|
||||||
Get.find<VideoDetailController>(
|
if (widget.type == EpisodeType.season) {
|
||||||
tag: widget.ugcIntroController!.heroTag,
|
try {
|
||||||
).seasonCid = episode.cid;
|
Get.find<VideoDetailController>(
|
||||||
} catch (_) {
|
tag: widget.ugcIntroController!.heroTag,
|
||||||
if (kDebugMode) rethrow;
|
).seasonCid = episode.cid;
|
||||||
|
} catch (_) {
|
||||||
|
if (kDebugMode) rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (cover?.isNotEmpty == true) {
|
if (cover?.isNotEmpty == true) {
|
||||||
|
|||||||
@@ -259,57 +259,58 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
|||||||
],
|
],
|
||||||
ListTile(
|
ListTile(
|
||||||
tileColor: theme.colorScheme.onInverseSurface,
|
tileColor: theme.colorScheme.onInverseSurface,
|
||||||
leading: Text.rich(
|
title: Row(
|
||||||
style: const TextStyle(
|
children: [
|
||||||
height: 1,
|
SizedBox(
|
||||||
fontSize: 14,
|
width: 55,
|
||||||
),
|
child: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '*',
|
text: '*',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
height: 1,
|
color: theme.colorScheme.error,
|
||||||
color: theme.colorScheme.error,
|
),
|
||||||
|
),
|
||||||
|
const TextSpan(
|
||||||
|
text: '名称',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const TextSpan(
|
),
|
||||||
text: '名称',
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
readOnly: _attr != null && FavUtils.isDefaultFav(_attr!),
|
||||||
|
controller: _titleController,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
height: 1,
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
color: _attr != null && FavUtils.isDefaultFav(_attr!)
|
||||||
|
? theme.colorScheme.outline
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(20),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
hintText: '名称',
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
gapPadding: 0,
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
title: TextField(
|
|
||||||
autofocus: true,
|
|
||||||
readOnly: _attr != null && FavUtils.isDefaultFav(_attr!),
|
|
||||||
controller: _titleController,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: _attr != null && FavUtils.isDefaultFav(_attr!)
|
|
||||||
? theme.colorScheme.outline
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
inputFormatters: [
|
|
||||||
LengthLimitingTextInputFormatter(20),
|
|
||||||
],
|
],
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
hintText: '名称',
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: theme.colorScheme.outline,
|
|
||||||
),
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
gapPadding: 0,
|
|
||||||
),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -319,24 +320,16 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
|||||||
title: Row(
|
title: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text.rich(
|
SizedBox(
|
||||||
TextSpan(
|
width: 55,
|
||||||
children: [
|
child: Text(
|
||||||
TextSpan(
|
'简介',
|
||||||
text: '简介',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 14,
|
||||||
fontSize: 14,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
const TextSpan(
|
|
||||||
text: '*',
|
|
||||||
style: TextStyle(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
minLines: 6,
|
minLines: 6,
|
||||||
|
|||||||
@@ -254,20 +254,25 @@ class _HistoryPageState extends State<HistoryPage>
|
|||||||
padding: const EdgeInsets.only(left: 16, right: 6),
|
padding: const EdgeInsets.only(left: 16, right: 6),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
|
||||||
Icons.info_outline,
|
|
||||||
size: 18,
|
|
||||||
color: theme.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text.rich(
|
||||||
'历史记录功能已关闭',
|
|
||||||
strutStyle: const StrutStyle(height: 1, leading: 0),
|
strutStyle: const StrutStyle(height: 1, leading: 0),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: theme.onSecondaryContainer,
|
color: theme.onSecondaryContainer,
|
||||||
),
|
),
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
child: Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
size: 18,
|
||||||
|
color: theme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const TextSpan(text: ' 历史记录功能已关闭'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
|||||||
|
|
||||||
Widget _buildBody(LoadingState<List<LiveEmoteDatum>?> loadingState) {
|
Widget _buildBody(LoadingState<List<LiveEmoteDatum>?> loadingState) {
|
||||||
late final theme = Theme.of(context);
|
late final theme = Theme.of(context);
|
||||||
late final color = theme.colorScheme.onInverseSurface;
|
late final color = ElevationOverlay.colorWithOverlay(
|
||||||
|
theme.colorScheme.surface,
|
||||||
|
theme.hoverColor,
|
||||||
|
2,
|
||||||
|
);
|
||||||
return switch (loadingState) {
|
return switch (loadingState) {
|
||||||
Loading() => loadingWidget,
|
Loading() => loadingWidget,
|
||||||
Success(:var response) =>
|
Success(:var response) =>
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ class LiveRoomChatPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
padding: const EdgeInsets.fromLTRB(10, 4, 4, 4),
|
padding: const EdgeInsets.fromLTRB(10, 4, 4, 4),
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: Colors.white, height: 1),
|
||||||
strutStyle: const StrutStyle(height: 1, leading: 0),
|
strutStyle: const StrutStyle(height: 1, leading: 0),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -51,8 +51,12 @@ class _MainAppState extends State<MainApp>
|
|||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
|
final brightness = Theme.brightnessOf(context);
|
||||||
NetworkImgLayer.reduce =
|
NetworkImgLayer.reduce =
|
||||||
NetworkImgLayer.reduceLuxColor != null && context.isDarkMode;
|
NetworkImgLayer.reduceLuxColor != null && brightness.isDark;
|
||||||
|
if (Utils.isDesktop) {
|
||||||
|
windowManager.setBrightness(brightness);
|
||||||
|
}
|
||||||
PageUtils.routeObserver.subscribe(
|
PageUtils.routeObserver.subscribe(
|
||||||
this,
|
this,
|
||||||
ModalRoute.of(context) as PageRoute,
|
ModalRoute.of(context) as PageRoute,
|
||||||
@@ -160,6 +164,8 @@ class _MainAppState extends State<MainApp>
|
|||||||
Future<void> _handleTray() async {
|
Future<void> _handleTray() async {
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
await trayManager.setIcon('assets/images/logo/app_icon.ico');
|
await trayManager.setIcon('assets/images/logo/app_icon.ico');
|
||||||
|
} else {
|
||||||
|
await trayManager.setIcon('assets/images/logo/logo_large.png');
|
||||||
}
|
}
|
||||||
if (!Platform.isLinux) {
|
if (!Platform.isLinux) {
|
||||||
await trayManager.setToolTip(Constants.appName);
|
await trayManager.setToolTip(Constants.appName);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:PiliPlus/models_new/video/video_ai_conclusion/model_result.dart';
|
import 'package:PiliPlus/models_new/video/video_ai_conclusion/model_result.dart';
|
||||||
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
|
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
|
||||||
import 'package:PiliPlus/pages/video/controller.dart';
|
import 'package:PiliPlus/pages/video/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/selectable_text.dart';
|
||||||
import 'package:PiliPlus/utils/duration_utils.dart';
|
import 'package:PiliPlus/utils/duration_utils.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -69,7 +70,7 @@ class _AiDetailState extends State<AiConclusionPanel>
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||||
child: SelectableText(
|
child: selectableText(
|
||||||
widget.item.summary!,
|
widget.item.summary!,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
@@ -98,57 +99,59 @@ class _AiDetailState extends State<AiConclusionPanel>
|
|||||||
itemCount: widget.item.outline!.length,
|
itemCount: widget.item.outline!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = widget.item.outline![index];
|
final item = widget.item.outline![index];
|
||||||
return Column(
|
return SelectionArea(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
if (index != 0) const SizedBox(height: 10),
|
children: [
|
||||||
SelectableText(
|
if (index != 0) const SizedBox(height: 10),
|
||||||
item.title!,
|
Text(
|
||||||
style: const TextStyle(
|
item.title!,
|
||||||
fontSize: 14,
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontSize: 14,
|
||||||
height: 1.5,
|
fontWeight: FontWeight.bold,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 6),
|
||||||
const SizedBox(height: 6),
|
...?item.partOutline?.map(
|
||||||
...?item.partOutline?.map(
|
(item) => Wrap(
|
||||||
(item) => Wrap(
|
children: [
|
||||||
children: [
|
Text.rich(
|
||||||
SelectableText.rich(
|
TextSpan(
|
||||||
TextSpan(
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 14,
|
||||||
fontSize: 14,
|
color: theme.colorScheme.onSurface,
|
||||||
color: theme.colorScheme.onSurface,
|
height: 1.5,
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: DurationUtils.formatDuration(
|
|
||||||
item.timestamp,
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () {
|
|
||||||
try {
|
|
||||||
Get.find<VideoDetailController>(
|
|
||||||
tag: Get.arguments['heroTag'],
|
|
||||||
).plPlayerController.seekTo(
|
|
||||||
Duration(seconds: item.timestamp!),
|
|
||||||
);
|
|
||||||
} catch (_) {}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const TextSpan(text: ' '),
|
children: [
|
||||||
TextSpan(text: item.content!),
|
TextSpan(
|
||||||
],
|
text: DurationUtils.formatDuration(
|
||||||
|
item.timestamp,
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
try {
|
||||||
|
Get.find<VideoDetailController>(
|
||||||
|
tag: Get.arguments['heroTag'],
|
||||||
|
).plPlayerController.seekTo(
|
||||||
|
Duration(seconds: item.timestamp!),
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
TextSpan(text: item.content!),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ class PgcIntroController extends CommonIntroController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 修改分P或番剧分集
|
// 修改分P或番剧分集
|
||||||
Future<void> onChangeEpisode(BaseEpisodeItem episode) async {
|
Future<bool> onChangeEpisode(BaseEpisodeItem episode) async {
|
||||||
try {
|
try {
|
||||||
final int epId = episode.epId ?? episode.id!;
|
final int epId = episode.epId ?? episode.id!;
|
||||||
final String bvid = episode.bvid ?? this.bvid;
|
final String bvid = episode.bvid ?? this.bvid;
|
||||||
@@ -285,7 +285,7 @@ class PgcIntroController extends CommonIntroController {
|
|||||||
final int? cid =
|
final int? cid =
|
||||||
episode.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
episode.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||||
if (cid == null) {
|
if (cid == null) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
final String? cover = episode.cover;
|
final String? cover = episode.cover;
|
||||||
|
|
||||||
@@ -323,8 +323,10 @@ class PgcIntroController extends CommonIntroController {
|
|||||||
this.cid.value = cid;
|
this.cid.value = cid;
|
||||||
queryOnlineTotal();
|
queryOnlineTotal();
|
||||||
queryVideoIntro(episode as EpisodeItem);
|
queryVideoIntro(episode as EpisodeItem);
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (kDebugMode) debugPrint('pgc onChangeEpisode: $e');
|
if (kDebugMode) debugPrint('pgc onChangeEpisode: $e');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:PiliPlus/models_new/video/video_tag/data.dart';
|
|||||||
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
|
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
|
||||||
import 'package:PiliPlus/pages/pgc_review/view.dart';
|
import 'package:PiliPlus/pages/pgc_review/view.dart';
|
||||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||||
|
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/selectable_text.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart' hide TabBarView;
|
import 'package:flutter/material.dart' hide TabBarView;
|
||||||
@@ -108,7 +109,7 @@ class _IntroDetailState extends State<PgcIntroPanel>
|
|||||||
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
|
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
SelectableText(
|
selectableText(
|
||||||
widget.item.title!,
|
widget.item.title!,
|
||||||
style: const TextStyle(fontSize: 16),
|
style: const TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
@@ -152,7 +153,7 @@ class _IntroDetailState extends State<PgcIntroPanel>
|
|||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
SelectableText(
|
selectableText(
|
||||||
widget.item.evaluate!,
|
widget.item.evaluate!,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 修改分P或番剧分集
|
// 修改分P或番剧分集
|
||||||
Future<void> onChangeEpisode(
|
Future<bool> onChangeEpisode(
|
||||||
BaseEpisodeItem episode, {
|
BaseEpisodeItem episode, {
|
||||||
bool isStein = false,
|
bool isStein = false,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -473,7 +473,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
|
|||||||
final int? cid =
|
final int? cid =
|
||||||
episode.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
episode.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||||
if (cid == null) {
|
if (cid == null) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
final String? cover = episode.cover;
|
final String? cover = episode.cover;
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
|
|||||||
cid: cid,
|
cid: cid,
|
||||||
cover: cover,
|
cover: cover,
|
||||||
);
|
);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,8 +546,10 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
|
|||||||
|
|
||||||
this.cid.value = cid;
|
this.cid.value = cid;
|
||||||
queryOnlineTotal();
|
queryOnlineTotal();
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (kDebugMode) debugPrint('ugc onChangeEpisode: $e');
|
if (kDebugMode) debugPrint('ugc onChangeEpisode: $e');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart';
|
|||||||
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart';
|
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart';
|
||||||
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/page.dart';
|
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/page.dart';
|
||||||
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/season.dart';
|
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/season.dart';
|
||||||
|
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/selectable_text.dart';
|
||||||
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/triple_state.dart';
|
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/triple_state.dart';
|
||||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||||
import 'package:PiliPlus/utils/date_utils.dart';
|
import 'package:PiliPlus/utils/date_utils.dart';
|
||||||
@@ -327,13 +328,9 @@ class _UgcIntroPanelState extends TripleState<UgcIntroPanel> {
|
|||||||
),
|
),
|
||||||
if (videoDetail.descV2?.isNotEmpty == true) ...[
|
if (videoDetail.descV2?.isNotEmpty == true) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SelectableText.rich(
|
selectableRichText(
|
||||||
style: const TextStyle(height: 1.4),
|
style: const TextStyle(height: 1.4),
|
||||||
TextSpan(
|
buildContent(theme, videoDetail),
|
||||||
children: [
|
|
||||||
buildContent(theme, videoDetail),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Obx(() {
|
Obx(() {
|
||||||
@@ -601,7 +598,7 @@ class _UgcIntroPanelState extends TripleState<UgcIntroPanel> {
|
|||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
InlineSpan buildContent(ThemeData theme, VideoDetailData content) {
|
TextSpan buildContent(ThemeData theme, VideoDetailData content) {
|
||||||
if (content.descV2.isNullOrEmpty) {
|
if (content.descV2.isNullOrEmpty) {
|
||||||
return const TextSpan();
|
return const TextSpan();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
Widget selectableText(
|
||||||
|
String text, {
|
||||||
|
TextStyle? style,
|
||||||
|
}) {
|
||||||
|
if (Utils.isDesktop) {
|
||||||
|
return SelectionArea(
|
||||||
|
child: Text(
|
||||||
|
style: style,
|
||||||
|
text,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SelectableText(
|
||||||
|
style: style,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget selectableRichText(
|
||||||
|
TextSpan textSpan, {
|
||||||
|
TextStyle? style,
|
||||||
|
}) {
|
||||||
|
if (Utils.isDesktop) {
|
||||||
|
return SelectionArea(
|
||||||
|
child: Text.rich(
|
||||||
|
style: style,
|
||||||
|
textSpan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SelectableText.rich(
|
||||||
|
style: style,
|
||||||
|
textSpan,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -44,6 +44,11 @@ class _WhisperDetailPageState
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ThemeData theme = Theme.of(context);
|
final ThemeData theme = Theme.of(context);
|
||||||
final padding = MediaQuery.viewPaddingOf(context);
|
final padding = MediaQuery.viewPaddingOf(context);
|
||||||
|
late final containerColor = ElevationOverlay.colorWithOverlay(
|
||||||
|
theme.colorScheme.surface,
|
||||||
|
theme.hoverColor,
|
||||||
|
1,
|
||||||
|
);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -145,8 +150,11 @@ class _WhisperDetailPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_whisperDetailController.mid != null) ...[
|
if (_whisperDetailController.mid != null) ...[
|
||||||
_buildInputView(theme),
|
_buildInputView(theme, containerColor),
|
||||||
buildPanelContainer(theme, theme.colorScheme.onInverseSurface),
|
buildPanelContainer(
|
||||||
|
theme,
|
||||||
|
containerColor,
|
||||||
|
),
|
||||||
] else
|
] else
|
||||||
SizedBox(height: padding.bottom),
|
SizedBox(height: padding.bottom),
|
||||||
],
|
],
|
||||||
@@ -228,11 +236,11 @@ class _WhisperDetailPageState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInputView(ThemeData theme) {
|
Widget _buildInputView(ThemeData theme, Color containerColor) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.onInverseSurface,
|
color: containerColor,
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
Reference in New Issue
Block a user