Merge branch 'design'
This commit is contained in:
@ -1,35 +1,249 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
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/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
import '../models/common/video_episode_type.dart';
|
import '../models/common/video_episode_type.dart';
|
||||||
|
import 'widgets/badge.dart';
|
||||||
|
import 'widgets/stat/danmu.dart';
|
||||||
|
import 'widgets/stat/view.dart';
|
||||||
|
|
||||||
class EpisodeBottomSheet {
|
class EpisodeBottomSheet {
|
||||||
final List<dynamic> episodes;
|
final List<dynamic> episodes;
|
||||||
final int currentCid;
|
final int currentCid;
|
||||||
final dynamic dataType;
|
final dynamic dataType;
|
||||||
final BuildContext context;
|
|
||||||
final Function changeFucCall;
|
final Function changeFucCall;
|
||||||
final int? cid;
|
final int? cid;
|
||||||
final double? sheetHeight;
|
final double? sheetHeight;
|
||||||
bool isFullScreen = false;
|
bool isFullScreen = false;
|
||||||
|
final UgcSeason? ugcSeason;
|
||||||
|
|
||||||
EpisodeBottomSheet({
|
EpisodeBottomSheet({
|
||||||
required this.episodes,
|
required this.episodes,
|
||||||
required this.currentCid,
|
required this.currentCid,
|
||||||
required this.dataType,
|
required this.dataType,
|
||||||
required this.context,
|
|
||||||
required this.changeFucCall,
|
required this.changeFucCall,
|
||||||
this.cid,
|
this.cid,
|
||||||
this.sheetHeight,
|
this.sheetHeight,
|
||||||
this.isFullScreen = false,
|
this.isFullScreen = false,
|
||||||
|
this.ugcSeason,
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget buildEpisodeListItem(
|
Widget buildShowContent() {
|
||||||
dynamic episode,
|
return PagesBottomSheet(
|
||||||
int index,
|
episodes: episodes,
|
||||||
bool isCurrentIndex,
|
currentCid: currentCid,
|
||||||
) {
|
dataType: dataType,
|
||||||
|
changeFucCall: changeFucCall,
|
||||||
|
cid: cid,
|
||||||
|
sheetHeight: sheetHeight,
|
||||||
|
isFullScreen: isFullScreen,
|
||||||
|
ugcSeason: ugcSeason,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistentBottomSheetController show(BuildContext context) {
|
||||||
|
final PersistentBottomSheetController btmSheetCtr = showBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return buildShowContent();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return btmSheetCtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PagesBottomSheet extends StatefulWidget {
|
||||||
|
const PagesBottomSheet({
|
||||||
|
super.key,
|
||||||
|
required this.episodes,
|
||||||
|
required this.currentCid,
|
||||||
|
required this.dataType,
|
||||||
|
required this.changeFucCall,
|
||||||
|
this.cid,
|
||||||
|
this.sheetHeight,
|
||||||
|
this.isFullScreen = false,
|
||||||
|
this.ugcSeason,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<dynamic> episodes;
|
||||||
|
final int currentCid;
|
||||||
|
final dynamic dataType;
|
||||||
|
final Function changeFucCall;
|
||||||
|
final int? cid;
|
||||||
|
final double? sheetHeight;
|
||||||
|
final bool isFullScreen;
|
||||||
|
final UgcSeason? ugcSeason;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PagesBottomSheet> createState() => _PagesBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PagesBottomSheetState extends State<PagesBottomSheet> {
|
||||||
|
final ItemScrollController _itemScrollController = ItemScrollController();
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
late int currentIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
currentIndex =
|
||||||
|
widget.episodes.indexWhere((dynamic e) => e.cid == widget.currentCid);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (widget.dataType == VideoEpidoesType.videoEpisode) {
|
||||||
|
_itemScrollController.jumpTo(index: currentIndex);
|
||||||
|
} else {
|
||||||
|
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() {
|
||||||
|
switch (widget.dataType) {
|
||||||
|
case VideoEpidoesType.videoEpisode:
|
||||||
|
return '选集';
|
||||||
|
case VideoEpidoesType.videoPart:
|
||||||
|
return '分集';
|
||||||
|
case VideoEpidoesType.bangumiEpisode:
|
||||||
|
return '选集';
|
||||||
|
}
|
||||||
|
return '选集';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return SizedBox(
|
||||||
|
height: widget.sheetHeight,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TitleBar(
|
||||||
|
title: '${prefix()}(${widget.episodes.length})',
|
||||||
|
isFullScreen: widget.isFullScreen,
|
||||||
|
),
|
||||||
|
if (widget.ugcSeason != null) ...[
|
||||||
|
UgcSeasonBuild(ugcSeason: widget.ugcSeason!),
|
||||||
|
],
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
child: widget.dataType == VideoEpidoesType.videoEpisode
|
||||||
|
? ScrollablePositionedList.builder(
|
||||||
|
itemScrollController: _itemScrollController,
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0), // 设置左右间距为12
|
||||||
|
child: GridView.count(
|
||||||
|
controller: _scrollController,
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: StyleString.safeSpace,
|
||||||
|
childAspectRatio: 2.6,
|
||||||
|
children: List.generate(
|
||||||
|
widget.episodes.length,
|
||||||
|
(index) {
|
||||||
|
bool isCurrentIndex = currentIndex == index;
|
||||||
|
return EpisodeGridItem(
|
||||||
|
episode: widget.episodes[index],
|
||||||
|
index: index,
|
||||||
|
isCurrentIndex: isCurrentIndex,
|
||||||
|
dataType: widget.dataType,
|
||||||
|
changeFucCall: widget.changeFucCall,
|
||||||
|
isFullScreen: widget.isFullScreen,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TitleBar extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final bool isFullScreen;
|
||||||
|
|
||||||
|
const TitleBar({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.isFullScreen,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
toolbarHeight: 45,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
centerTitle: false,
|
||||||
|
title: Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
actions: !isFullScreen
|
||||||
|
? [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, size: 20),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpisodeListItem extends StatelessWidget {
|
||||||
|
final dynamic episode;
|
||||||
|
final int index;
|
||||||
|
final bool isCurrentIndex;
|
||||||
|
final dynamic dataType;
|
||||||
|
final Function changeFucCall;
|
||||||
|
final bool isFullScreen;
|
||||||
|
|
||||||
|
const EpisodeListItem({
|
||||||
|
Key? key,
|
||||||
|
required this.episode,
|
||||||
|
required this.index,
|
||||||
|
required this.isCurrentIndex,
|
||||||
|
required this.dataType,
|
||||||
|
required this.changeFucCall,
|
||||||
|
required this.isFullScreen,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
Color primary = Theme.of(context).colorScheme.primary;
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
Color onSurface = Theme.of(context).colorScheme.onSurface;
|
Color onSurface = Theme.of(context).colorScheme.onSurface;
|
||||||
|
|
||||||
@ -45,9 +259,19 @@ class EpisodeBottomSheet {
|
|||||||
title = '第${episode.title}话 ${episode.longTitle!}';
|
title = '第${episode.title}话 ${episode.longTitle!}';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isFullScreen || episode?.cover == null || episode?.cover == ''
|
return isFullScreen || episode?.cover == null || episode?.cover == ''
|
||||||
? ListTile(
|
? _buildListTile(context, title, primary, onSurface)
|
||||||
|
: _buildInkWell(context, title, primary, onSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListTile(
|
||||||
|
BuildContext context, String title, Color primary, Color onSurface) {
|
||||||
|
return ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
if (isCurrentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SmartDialog.showToast('切换至「$title」');
|
SmartDialog.showToast('切换至「$title」');
|
||||||
changeFucCall.call(episode, index);
|
changeFucCall.call(episode, index);
|
||||||
},
|
},
|
||||||
@ -59,114 +283,284 @@ class EpisodeBottomSheet {
|
|||||||
height: 12,
|
height: 12,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
title: Text(title,
|
title: Text(
|
||||||
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: isCurrentIndex ? primary : onSurface,
|
color: isCurrentIndex ? primary : onSurface,
|
||||||
)))
|
),
|
||||||
: InkWell(
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInkWell(
|
||||||
|
BuildContext context, String title, Color primary, Color onSurface) {
|
||||||
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
if (isCurrentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SmartDialog.showToast('切换至「$title」');
|
SmartDialog.showToast('切换至「$title」');
|
||||||
changeFucCall.call(episode, index);
|
changeFucCall.call(episode, index);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.fromLTRB(
|
||||||
const EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
|
StyleString.safeSpace, 6, StyleString.safeSpace, 6),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
||||||
|
const double width = 160;
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
|
height: width / StyleString.aspectRatio,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: StyleString.aspectRatio,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context,
|
||||||
|
BoxConstraints boxConstraints) {
|
||||||
|
final double maxWidth = boxConstraints.maxWidth;
|
||||||
|
final double maxHeight = boxConstraints.maxHeight;
|
||||||
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: 130, height: 75, src: episode?.cover ?? ''),
|
src: episode?.cover ?? '',
|
||||||
const SizedBox(width: 10),
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
),
|
||||||
|
if (episode.duration != 0)
|
||||||
|
PBadge(
|
||||||
|
text: Utils.timeFormat(episode.duration!),
|
||||||
|
right: 6.0,
|
||||||
|
bottom: 6.0,
|
||||||
|
type: 'gray',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Padding(
|
||||||
title,
|
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
episode.title as String,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontWeight: FontWeight.w500,
|
||||||
color: isCurrentIndex ? primary : onSurface,
|
color: isCurrentIndex ? primary : onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (dataType != VideoEpidoesType.videoPart) ...[
|
||||||
|
if (episode?.pubdate != null ||
|
||||||
|
episode.pubTime != null)
|
||||||
|
Text(
|
||||||
|
Utils.dateFormat(
|
||||||
|
episode?.pubdate ?? episode.pubTime),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(height: 2),
|
||||||
),
|
if (episode.stat != null)
|
||||||
),
|
Row(
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildTitle() {
|
|
||||||
return AppBar(
|
|
||||||
toolbarHeight: 45,
|
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
centerTitle: false,
|
|
||||||
title: Text(
|
|
||||||
'合集(${episodes.length})',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
actions: !isFullScreen
|
|
||||||
? [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.close, size: 20),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 14),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildShowContent(BuildContext context) {
|
|
||||||
final ItemScrollController itemScrollController = ItemScrollController();
|
|
||||||
int currentIndex = episodes.indexWhere((dynamic e) => e.cid == currentCid);
|
|
||||||
return StatefulBuilder(
|
|
||||||
builder: (BuildContext context, StateSetter setState) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
itemScrollController.jumpTo(index: currentIndex);
|
|
||||||
});
|
|
||||||
return Container(
|
|
||||||
height: sheetHeight,
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
buildTitle(),
|
StatView(view: episode.stat.view),
|
||||||
Expanded(
|
const SizedBox(width: 8),
|
||||||
child: Material(
|
StatDanMu(danmu: episode.stat.danmaku),
|
||||||
child: PageStorage(
|
const Spacer(),
|
||||||
bucket: PageStorageBucket(),
|
],
|
||||||
child: ScrollablePositionedList.builder(
|
),
|
||||||
itemScrollController: itemScrollController,
|
const SizedBox(height: 4),
|
||||||
itemCount: episodes.length + 1,
|
]
|
||||||
itemBuilder: (BuildContext context, int index) {
|
],
|
||||||
bool isLastItem = index == episodes.length;
|
),
|
||||||
bool isCurrentIndex = currentIndex == index;
|
),
|
||||||
return isLastItem
|
|
||||||
? SizedBox(
|
|
||||||
height:
|
|
||||||
MediaQuery.of(context).padding.bottom + 20,
|
|
||||||
)
|
)
|
||||||
: buildEpisodeListItem(
|
|
||||||
episodes[index],
|
|
||||||
index,
|
|
||||||
isCurrentIndex,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The [BuildContext] of the widget that calls the bottom sheet.
|
|
||||||
PersistentBottomSheetController show(BuildContext context) {
|
|
||||||
final PersistentBottomSheetController btmSheetCtr = showBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return buildShowContent(context);
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpisodeGridItem extends StatelessWidget {
|
||||||
|
final dynamic episode;
|
||||||
|
final int index;
|
||||||
|
final bool isCurrentIndex;
|
||||||
|
final dynamic dataType;
|
||||||
|
final Function changeFucCall;
|
||||||
|
final bool isFullScreen;
|
||||||
|
|
||||||
|
const EpisodeGridItem({
|
||||||
|
Key? key,
|
||||||
|
required this.episode,
|
||||||
|
required this.index,
|
||||||
|
required this.isCurrentIndex,
|
||||||
|
required this.dataType,
|
||||||
|
required this.changeFucCall,
|
||||||
|
required this.isFullScreen,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
|
TextStyle textStyle = TextStyle(
|
||||||
|
color: isCurrentIndex ? colorScheme.primary : colorScheme.onSurface,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.only(top: StyleString.safeSpace),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isCurrentIndex
|
||||||
|
? colorScheme.primaryContainer.withOpacity(0.6)
|
||||||
|
: colorScheme.secondaryContainer.withOpacity(0.4),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: isCurrentIndex
|
||||||
|
? colorScheme.primary.withOpacity(0.8)
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () {
|
||||||
|
if (isCurrentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('切换至「${episode.title}」');
|
||||||
|
changeFucCall.call(episode, index);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
dataType == VideoEpidoesType.bangumiEpisode
|
||||||
|
? '第${index + 1}话'
|
||||||
|
: '第${index + 1}p',
|
||||||
|
style: textStyle),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text(
|
||||||
|
episode.title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (dataType == VideoEpidoesType.bangumiEpisode &&
|
||||||
|
episode.badge != '' &&
|
||||||
|
episode.badge != null)
|
||||||
|
Positioned(
|
||||||
|
right: 8,
|
||||||
|
top: 18,
|
||||||
|
child: Text(
|
||||||
|
episode.badge,
|
||||||
|
style: const TextStyle(fontSize: 11, color: Color(0xFFFF6699)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UgcSeasonBuild extends StatelessWidget {
|
||||||
|
final UgcSeason ugcSeason;
|
||||||
|
|
||||||
|
const UgcSeasonBuild({
|
||||||
|
Key? key,
|
||||||
|
required this.ugcSeason,
|
||||||
|
}) : 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(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(ugcSeason.intro ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline)),
|
||||||
|
),
|
||||||
|
// SizedBox(
|
||||||
|
// height: 32,
|
||||||
|
// child: FilledButton.tonal(
|
||||||
|
// onPressed: () {},
|
||||||
|
// style: ButtonStyle(
|
||||||
|
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
// ),
|
||||||
|
// child: const Text('订阅'),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return btmSheetCtr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -144,6 +144,7 @@ class EpisodeItem {
|
|||||||
this.link,
|
this.link,
|
||||||
this.longTitle,
|
this.longTitle,
|
||||||
this.pubTime,
|
this.pubTime,
|
||||||
|
this.pubdate,
|
||||||
this.pv,
|
this.pv,
|
||||||
this.releaseDate,
|
this.releaseDate,
|
||||||
this.rights,
|
this.rights,
|
||||||
@ -155,6 +156,7 @@ class EpisodeItem {
|
|||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.title,
|
this.title,
|
||||||
this.vid,
|
this.vid,
|
||||||
|
this.stat,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? aid;
|
int? aid;
|
||||||
@ -173,6 +175,7 @@ class EpisodeItem {
|
|||||||
String? link;
|
String? link;
|
||||||
String? longTitle;
|
String? longTitle;
|
||||||
int? pubTime;
|
int? pubTime;
|
||||||
|
int? pubdate;
|
||||||
int? pv;
|
int? pv;
|
||||||
String? releaseDate;
|
String? releaseDate;
|
||||||
Map? rights;
|
Map? rights;
|
||||||
@ -184,6 +187,7 @@ class EpisodeItem {
|
|||||||
String? subtitle;
|
String? subtitle;
|
||||||
String? title;
|
String? title;
|
||||||
String? vid;
|
String? vid;
|
||||||
|
String? stat;
|
||||||
|
|
||||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
@ -202,6 +206,7 @@ class EpisodeItem {
|
|||||||
link = json['link'];
|
link = json['link'];
|
||||||
longTitle = json['long_title'];
|
longTitle = json['long_title'];
|
||||||
pubTime = json['pub_time'];
|
pubTime = json['pub_time'];
|
||||||
|
pubdate = json['pub_time'];
|
||||||
pv = json['pv'];
|
pv = json['pv'];
|
||||||
releaseDate = json['release_date'];
|
releaseDate = json['release_date'];
|
||||||
rights = json['rights'];
|
rights = json['rights'];
|
||||||
@ -211,7 +216,7 @@ class EpisodeItem {
|
|||||||
skip = json['skip'];
|
skip = json['skip'];
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
subtitle = json['subtitle'];
|
subtitle = json['subtitle'];
|
||||||
title = json['title'];
|
title = json['long_title'];
|
||||||
vid = json['vid'];
|
vid = json['vid'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,7 @@ class ReplyReplyData {
|
|||||||
this.page,
|
this.page,
|
||||||
this.config,
|
this.config,
|
||||||
this.replies,
|
this.replies,
|
||||||
|
this.root,
|
||||||
this.topReplies,
|
this.topReplies,
|
||||||
this.upper,
|
this.upper,
|
||||||
});
|
});
|
||||||
@ -108,6 +109,7 @@ class ReplyReplyData {
|
|||||||
ReplyPage? page;
|
ReplyPage? page;
|
||||||
ReplyConfig? config;
|
ReplyConfig? config;
|
||||||
late List<ReplyItemModel>? replies;
|
late List<ReplyItemModel>? replies;
|
||||||
|
ReplyItemModel? root;
|
||||||
late List<ReplyItemModel>? topReplies;
|
late List<ReplyItemModel>? topReplies;
|
||||||
ReplyUpper? upper;
|
ReplyUpper? upper;
|
||||||
|
|
||||||
@ -120,6 +122,9 @@ class ReplyReplyData {
|
|||||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||||
.toList()
|
.toList()
|
||||||
: [];
|
: [];
|
||||||
|
root = json['root'] != null
|
||||||
|
? ReplyItemModel.fromJson(json['root'], false)
|
||||||
|
: null;
|
||||||
topReplies = json['top_replies'] != null
|
topReplies = json['top_replies'] != null
|
||||||
? json['top_replies']
|
? json['top_replies']
|
||||||
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
||||||
|
|||||||
@ -377,6 +377,7 @@ class Part {
|
|||||||
int? page;
|
int? page;
|
||||||
String? from;
|
String? from;
|
||||||
String? pagePart;
|
String? pagePart;
|
||||||
|
String? title;
|
||||||
int? duration;
|
int? duration;
|
||||||
String? vid;
|
String? vid;
|
||||||
String? weblink;
|
String? weblink;
|
||||||
@ -389,6 +390,7 @@ class Part {
|
|||||||
this.page,
|
this.page,
|
||||||
this.from,
|
this.from,
|
||||||
this.pagePart,
|
this.pagePart,
|
||||||
|
this.title,
|
||||||
this.duration,
|
this.duration,
|
||||||
this.vid,
|
this.vid,
|
||||||
this.weblink,
|
this.weblink,
|
||||||
@ -406,6 +408,7 @@ class Part {
|
|||||||
page = json["page"];
|
page = json["page"];
|
||||||
from = json["from"];
|
from = json["from"];
|
||||||
pagePart = json["part"];
|
pagePart = json["part"];
|
||||||
|
title = json["part"];
|
||||||
duration = json["duration"];
|
duration = json["duration"];
|
||||||
vid = json["vid"];
|
vid = json["vid"];
|
||||||
weblink = json["weblink"];
|
weblink = json["weblink"];
|
||||||
@ -649,6 +652,9 @@ class EpisodeItem {
|
|||||||
Part? page;
|
Part? page;
|
||||||
String? bvid;
|
String? bvid;
|
||||||
String? cover;
|
String? cover;
|
||||||
|
int? pubdate;
|
||||||
|
int? duration;
|
||||||
|
Stat? stat;
|
||||||
|
|
||||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||||
seasonId = json['season_id'];
|
seasonId = json['season_id'];
|
||||||
@ -661,6 +667,9 @@ class EpisodeItem {
|
|||||||
page = Part.fromJson(json['page']);
|
page = Part.fromJson(json['page']);
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
cover = json['arc']['pic'];
|
cover = json['arc']['pic'];
|
||||||
|
pubdate = json['arc']['pubdate'];
|
||||||
|
duration = json['arc']['duration'];
|
||||||
|
stat = Stat.fromJson(json['arc']['stat']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -302,14 +302,13 @@ class BangumiIntroController extends GetxController {
|
|||||||
episodes: episodes,
|
episodes: episodes,
|
||||||
currentCid: videoDetailCtr.cid.value,
|
currentCid: videoDetailCtr.cid.value,
|
||||||
dataType: dataType,
|
dataType: dataType,
|
||||||
context: Get.context!,
|
|
||||||
sheetHeight: Get.size.height,
|
sheetHeight: Get.size.height,
|
||||||
isFullScreen: true,
|
isFullScreen: true,
|
||||||
changeFucCall: (item, index) {
|
changeFucCall: (item, index) {
|
||||||
changeSeasonOrbangu(item.bvid, item.cid, item.aid, item.cover);
|
changeSeasonOrbangu(item.bvid, item.cid, item.aid, item.cover);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
},
|
},
|
||||||
).buildShowContent(Get.context!),
|
).buildShowContent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -146,17 +146,34 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
showFavBottomSheet() {
|
showFavBottomSheet() async {
|
||||||
if (bangumiIntroController.userInfo.mid == null) {
|
if (bangumiIntroController.userInfo.mid == null) {
|
||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showModalBottomSheet(
|
final mediaQueryData = MediaQuery.of(context);
|
||||||
context: context,
|
final contentHeight = mediaQueryData.size.height - kToolbarHeight;
|
||||||
useRootNavigator: true,
|
final double initialChildSize =
|
||||||
|
(contentHeight - Get.width * 9 / 16) / contentHeight;
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: Get.context!,
|
||||||
|
useSafeArea: true,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return FavPanel(ctr: bangumiIntroController);
|
return DraggableScrollableSheet(
|
||||||
|
initialChildSize: initialChildSize,
|
||||||
|
minChildSize: 0,
|
||||||
|
maxChildSize: 1,
|
||||||
|
snap: true,
|
||||||
|
expand: false,
|
||||||
|
snapSizes: [initialChildSize],
|
||||||
|
builder: (BuildContext context, ScrollController scrollController) {
|
||||||
|
return FavPanel(
|
||||||
|
ctr: bangumiIntroController,
|
||||||
|
scrollController: scrollController,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -151,7 +151,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
changeFucCall: changeFucCall,
|
changeFucCall: changeFucCall,
|
||||||
sheetHeight: widget.sheetHeight,
|
sheetHeight: widget.sheetHeight,
|
||||||
dataType: VideoEpidoesType.bangumiEpisode,
|
dataType: VideoEpidoesType.bangumiEpisode,
|
||||||
context: context,
|
|
||||||
).show(context);
|
).show(context);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:pilipala/common/widgets/no_data.dart';
|
|||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.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/main_stream.dart';
|
||||||
import 'package:pilipala/utils/route_push.dart';
|
import 'package:pilipala/utils/route_push.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -90,8 +91,13 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if (_dynamicsController.mid.value != -1 &&
|
final mid = _dynamicsController.mid.value;
|
||||||
_dynamicsController.upInfo.value.uname != null) {
|
final uname = _dynamicsController.upInfo.value.uname;
|
||||||
|
|
||||||
|
if (mid == -1 || uname == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 36,
|
height: 36,
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
@ -102,20 +108,17 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
scale: animation, child: child);
|
scale: animation, child: child);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_dynamicsController.upInfo.value.uname!}的动态',
|
'$uname的动态',
|
||||||
key: ValueKey<String>(
|
key: ValueKey<String>(uname),
|
||||||
_dynamicsController.upInfo.value.uname!),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelLarge!
|
.labelLarge!
|
||||||
.fontSize,
|
.fontSize,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
Obx(
|
Obx(
|
||||||
() => _dynamicsController.userLogin.value
|
() => _dynamicsController.userLogin.value
|
||||||
@ -207,14 +210,19 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
() => UpPanel(
|
() => UpPanel(
|
||||||
upData: _dynamicsController.upData.value,
|
upData: _dynamicsController.upData.value,
|
||||||
onClickUpCb: (data) {
|
onClickUpCb: (data) {
|
||||||
// _dynamicsController.onTapUp(data);
|
if (GlobalDataCache().enableDynamicSwitch) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
PlPopupRoute(
|
PlPopupRoute(
|
||||||
child: OverlayPanel(
|
child: OverlayPanel(
|
||||||
ctr: _dynamicsController, upInfo: data),
|
ctr: _dynamicsController,
|
||||||
|
upInfo: data,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
_dynamicsController.onTapUp(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -66,7 +66,15 @@ Widget liveRcmdPanel(item, context, {floor = 1}) {
|
|||||||
},
|
},
|
||||||
child: LayoutBuilder(builder: (context, box) {
|
child: LayoutBuilder(builder: (context, box) {
|
||||||
double width = box.maxWidth;
|
double width = box.maxWidth;
|
||||||
return Stack(
|
return Container(
|
||||||
|
margin: floor == 1
|
||||||
|
? const EdgeInsets.only(
|
||||||
|
left: StyleString.safeSpace, right: StyleString.safeSpace)
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(StyleString.imgRadius)),
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Hero(
|
||||||
tag: liveRcmd.roomId.toString(),
|
tag: liveRcmd.roomId.toString(),
|
||||||
@ -79,16 +87,16 @@ Widget liveRcmdPanel(item, context, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
PBadge(
|
PBadge(
|
||||||
text: watchedShow['text_large'],
|
text: watchedShow['text_large'],
|
||||||
top: 6,
|
top: 8.0,
|
||||||
right: 56,
|
right: 62.0,
|
||||||
bottom: null,
|
bottom: null,
|
||||||
left: null,
|
left: null,
|
||||||
type: 'gray',
|
type: 'gray',
|
||||||
),
|
),
|
||||||
PBadge(
|
PBadge(
|
||||||
text: liveStatus == 1 ? '直播中' : '直播结束',
|
text: liveStatus == 1 ? '直播中' : '直播结束',
|
||||||
top: 6,
|
top: 8.0,
|
||||||
right: 6,
|
right: 10.0,
|
||||||
bottom: null,
|
bottom: null,
|
||||||
left: null,
|
left: null,
|
||||||
),
|
),
|
||||||
@ -136,6 +144,7 @@ Widget liveRcmdPanel(item, context, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|||||||
import 'package:pilipala/models/dynamics/up.dart';
|
import 'package:pilipala/models/dynamics/up.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/global_data_cache.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class UpPanel extends StatefulWidget {
|
class UpPanel extends StatefulWidget {
|
||||||
@ -23,7 +24,7 @@ class UpPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _UpPanelState extends State<UpPanel> {
|
class _UpPanelState extends State<UpPanel> {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
int currentMid = -1;
|
RxInt currentMid = (-1).obs;
|
||||||
late double contentWidth = 56;
|
late double contentWidth = 56;
|
||||||
List<UpItem> upList = [];
|
List<UpItem> upList = [];
|
||||||
List<LiveUserItem> liveList = [];
|
List<LiveUserItem> liveList = [];
|
||||||
@ -37,26 +38,36 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onClickUp(data, i) {
|
void onClickUp(data, i) {
|
||||||
currentMid = data.mid;
|
currentMid.value = data.mid;
|
||||||
widget.onClickUpCb?.call(data);
|
widget.onClickUpCb?.call(data);
|
||||||
// int liveLen = liveList.length;
|
}
|
||||||
// int upLen = upList.length;
|
|
||||||
// double itemWidth = contentWidth + itemPadding.horizontal;
|
void onClickUpAni(data, i) {
|
||||||
// double screenWidth = MediaQuery.sizeOf(context).width;
|
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||||
// double moveDistance = 0.0;
|
final itemWidth = contentWidth + itemPadding.horizontal;
|
||||||
// if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
|
final liveLen = liveList.length;
|
||||||
// } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
final upLen = upList.length;
|
||||||
// moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
|
||||||
// } else {
|
currentMid.value = data.mid;
|
||||||
// moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
|
widget.onClickUpCb?.call(data);
|
||||||
// }
|
|
||||||
// data.hasUpdate = false;
|
double moveDistance = 0.0;
|
||||||
// scrollController.animateTo(
|
final totalItemsWidth = itemWidth * (upLen + liveLen);
|
||||||
// moveDistance,
|
|
||||||
// duration: const Duration(milliseconds: 200),
|
if (totalItemsWidth > screenWidth) {
|
||||||
// curve: Curves.linear,
|
if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
||||||
// );
|
moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
||||||
// setState(() {});
|
} else {
|
||||||
|
moveDistance = totalItemsWidth + 46 - screenWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.hasUpdate = false;
|
||||||
|
scrollController.animateTo(
|
||||||
|
moveDistance,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -144,14 +155,17 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget upItemBuild(data, i) {
|
Widget upItemBuild(data, i) {
|
||||||
bool isCurrent = currentMid == data.mid || currentMid == -1;
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
feedBack();
|
feedBack();
|
||||||
if (data.type == 'up') {
|
if (data.type == 'up') {
|
||||||
EasyThrottle.throttle('follow', const Duration(milliseconds: 300),
|
EasyThrottle.throttle('follow', const Duration(milliseconds: 300),
|
||||||
() {
|
() {
|
||||||
|
if (GlobalDataCache().enableDynamicSwitch) {
|
||||||
onClickUp(data, i);
|
onClickUp(data, i);
|
||||||
|
} else {
|
||||||
|
onClickUpAni(data, i);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else if (data.type == 'live') {
|
} else if (data.type == 'live') {
|
||||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||||
@ -177,8 +191,11 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: itemPadding,
|
padding: itemPadding,
|
||||||
child: AnimatedOpacity(
|
child: Obx(
|
||||||
opacity: isCurrent ? 1 : 0.3,
|
() => AnimatedOpacity(
|
||||||
|
opacity: currentMid.value == data.mid || currentMid.value == -1
|
||||||
|
? 1
|
||||||
|
: 0.3,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -222,11 +239,13 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
softWrap: false,
|
softWrap: false,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: currentMid == data.mid
|
color: currentMid.value == data.mid
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Theme.of(context).colorScheme.outline,
|
: Theme.of(context).colorScheme.outline,
|
||||||
fontSize:
|
fontSize: Theme.of(context)
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize),
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -234,6 +253,7 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,7 +78,15 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
],
|
],
|
||||||
LayoutBuilder(builder: (context, box) {
|
LayoutBuilder(builder: (context, box) {
|
||||||
double width = box.maxWidth;
|
double width = box.maxWidth;
|
||||||
return Stack(
|
return Container(
|
||||||
|
margin: floor == 1
|
||||||
|
? const EdgeInsets.only(
|
||||||
|
left: StyleString.safeSpace, right: StyleString.safeSpace)
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(StyleString.imgRadius)),
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
type: floor == 1 ? 'emote' : null,
|
type: floor == 1 ? 'emote' : null,
|
||||||
@ -120,8 +128,10 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
children: [
|
children: [
|
||||||
DefaultTextStyle.merge(
|
DefaultTextStyle.merge(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize: Theme.of(context)
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize,
|
||||||
color: Colors.white),
|
color: Colors.white),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -144,6 +154,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|||||||
@ -96,7 +96,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
[23, 1].contains(favFolderItem.attr) ? '私密' : '公开',
|
[22, 0].contains(favFolderItem.attr) ? '公开' : '私密',
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class FavDetailController extends GetxController {
|
|||||||
RxString loadingText = '加载中...'.obs;
|
RxString loadingText = '加载中...'.obs;
|
||||||
RxInt mediaCount = 0.obs;
|
RxInt mediaCount = 0.obs;
|
||||||
late String isOwner;
|
late String isOwner;
|
||||||
|
late bool hasMore = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -35,7 +36,7 @@ class FavDetailController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
||||||
if (type == 'onLoad' && favList.length >= mediaCount.value) {
|
if (type == 'onLoad' && !hasMore) {
|
||||||
loadingText.value = '没有更多了';
|
loadingText.value = '没有更多了';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -47,17 +48,18 @@ class FavDetailController extends GetxController {
|
|||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
favInfo.value = res['data'].info;
|
favInfo.value = res['data'].info;
|
||||||
|
hasMore = res['data'].hasMore;
|
||||||
if (currentPage == 1 && type == 'init') {
|
if (currentPage == 1 && type == 'init') {
|
||||||
favList.value = res['data'].medias;
|
favList.value = res['data'].medias;
|
||||||
mediaCount.value = res['data'].info['media_count'];
|
mediaCount.value = res['data'].info['media_count'];
|
||||||
} else if (type == 'onLoad') {
|
} else if (type == 'onLoad') {
|
||||||
favList.addAll(res['data'].medias);
|
favList.addAll(res['data'].medias);
|
||||||
}
|
}
|
||||||
if (favList.length >= mediaCount.value) {
|
if (!hasMore) {
|
||||||
loadingText.value = '没有更多了';
|
loadingText.value = '没有更多了';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
currentPage += 1;
|
currentPage += 1;
|
||||||
|
}
|
||||||
isLoadingMore = false;
|
isLoadingMore = false;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -126,7 +128,7 @@ class FavDetailController extends GetxController {
|
|||||||
'title': item!.title,
|
'title': item!.title,
|
||||||
'intro': item!.intro,
|
'intro': item!.intro,
|
||||||
'cover': item!.cover,
|
'cover': item!.cover,
|
||||||
'privacy': [23, 1].contains(item!.attr) ? 1 : 0,
|
'privacy': [22, 0].contains(item!.attr) ? 0 : 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
title.value = res['title'];
|
title.value = res['title'];
|
||||||
|
|||||||
@ -193,7 +193,9 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'共${_favDetailController.mediaCount}条视频',
|
_favDetailController.mediaCount > 0
|
||||||
|
? '共${_favDetailController.mediaCount}条视频'
|
||||||
|
: '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize:
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
@ -215,7 +217,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
List favList = _favDetailController.favList;
|
List favList = _favDetailController.favList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => favList.isEmpty
|
() => favList.isEmpty
|
||||||
? const SliverToBoxAdapter(child: SizedBox())
|
? const NoData()
|
||||||
: SliverList(
|
: SliverList(
|
||||||
delegate:
|
delegate:
|
||||||
SliverChildBuilderDelegate((context, index) {
|
SliverChildBuilderDelegate((context, index) {
|
||||||
@ -247,18 +249,20 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: MediaQuery.of(context).padding.bottom + 60,
|
height: MediaQuery.of(context).padding.bottom + 90,
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: MediaQuery.of(context).padding.bottom),
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Obx(
|
child: Obx(() {
|
||||||
() => Text(
|
final mediaCount = _favDetailController.mediaCount;
|
||||||
_favDetailController.loadingText.value,
|
final loadingText = _favDetailController.loadingText.value;
|
||||||
style: TextStyle(
|
final textColor = Theme.of(context).colorScheme.outline;
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize: 13),
|
return Text(
|
||||||
),
|
mediaCount > 0 ? loadingText : '',
|
||||||
),
|
style: TextStyle(color: textColor, fontSize: 13),
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
@ -48,24 +47,27 @@ class FollowItem extends StatelessWidget {
|
|||||||
height: 34,
|
height: 34,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await showFlexibleBottomSheet(
|
await showModalBottomSheet(
|
||||||
bottomSheetBorderRadius: const BorderRadius.only(
|
context: context,
|
||||||
topLeft: Radius.circular(16),
|
useSafeArea: true,
|
||||||
topRight: Radius.circular(16),
|
isScrollControlled: true,
|
||||||
),
|
builder: (BuildContext context) {
|
||||||
minHeight: 1,
|
return DraggableScrollableSheet(
|
||||||
initHeight: 1,
|
initialChildSize: 0.6,
|
||||||
maxHeight: 1,
|
minChildSize: 0,
|
||||||
context: Get.context!,
|
maxChildSize: 1,
|
||||||
|
snap: true,
|
||||||
|
expand: false,
|
||||||
|
snapSizes: const [0.6],
|
||||||
builder: (BuildContext context,
|
builder: (BuildContext context,
|
||||||
ScrollController scrollController, double offset) {
|
ScrollController scrollController) {
|
||||||
return GroupPanel(
|
return GroupPanel(
|
||||||
mid: item.mid!,
|
mid: item.mid!,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
anchors: [1],
|
);
|
||||||
isSafeArea: true,
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
|
|||||||
@ -129,7 +129,7 @@ class _LaterPageState extends State<LaterPage> {
|
|||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: MediaQuery.of(context).padding.bottom + 10,
|
height: MediaQuery.of(context).padding.bottom + 80,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
|
||||||
import 'package:pilipala/models/msg/like.dart';
|
import 'package:pilipala/models/msg/like.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
import '../utils/index.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
class MessageLikePage extends StatefulWidget {
|
class MessageLikePage extends StatefulWidget {
|
||||||
@ -122,39 +120,13 @@ class LikeItem extends StatelessWidget {
|
|||||||
final nickNameList = item.users!.map((e) => e.nickname).take(2).toList();
|
final nickNameList = item.users!.map((e) => e.nickname).take(2).toList();
|
||||||
int usersLen = item.users!.length > 3 ? 3 : item.users!.length;
|
int usersLen = item.users!.length > 3 ? 3 : item.users!.length;
|
||||||
final Uri uri = Uri.parse(item.item!.uri!);
|
final Uri uri = Uri.parse(item.item!.uri!);
|
||||||
final String path = uri.path;
|
|
||||||
final String bvid = path.split('/').last;
|
|
||||||
|
|
||||||
/// bilibili://
|
/// bilibili://
|
||||||
final Uri nativeUri = Uri.parse(item.item!.nativeUri!);
|
final Uri nativeUri = Uri.parse(item.item!.nativeUri!);
|
||||||
final Map<String, String> queryParameters = nativeUri.queryParameters;
|
|
||||||
final String type = item.item!.type!;
|
final String type = item.item!.type!;
|
||||||
// cid
|
|
||||||
final String? argCid = queryParameters['cid'];
|
|
||||||
// 页码
|
|
||||||
final String? page = queryParameters['page'];
|
|
||||||
// 根评论id
|
|
||||||
final String? commentRootId = queryParameters['comment_root_id'];
|
|
||||||
// 二级评论id
|
|
||||||
final String? commentSecondaryId = queryParameters['comment_secondary_id'];
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
MessageUtils.onClickMessage(context, uri, nativeUri, type);
|
||||||
final int cid = argCid != null
|
|
||||||
? int.parse(argCid)
|
|
||||||
: await SearchHttp.ab2c(bvid: bvid);
|
|
||||||
final String heroTag = Utils.makeHeroTag(bvid);
|
|
||||||
Get.toNamed<dynamic>(
|
|
||||||
'/video?bvid=$bvid&cid=$cid',
|
|
||||||
arguments: <String, String?>{
|
|
||||||
'pic': '',
|
|
||||||
'heroTag': heroTag,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
SmartDialog.showToast('视频可能失效了$e');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
@ -243,6 +215,7 @@ class LikeItem extends StatelessWidget {
|
|||||||
width: 60,
|
width: 60,
|
||||||
height: 60,
|
height: 60,
|
||||||
src: item.item!.image,
|
src: item.item!.image,
|
||||||
|
radius: 6,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -4,10 +4,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
|
||||||
import 'package:pilipala/models/msg/reply.dart';
|
import 'package:pilipala/models/msg/reply.dart';
|
||||||
|
import 'package:pilipala/pages/message/utils/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
class MessageReplyPage extends StatefulWidget {
|
class MessageReplyPage extends StatefulWidget {
|
||||||
@ -112,28 +111,14 @@ class ReplyItem extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Color outline = Theme.of(context).colorScheme.outline;
|
Color outline = Theme.of(context).colorScheme.outline;
|
||||||
final String heroTag = Utils.makeHeroTag(item.user!.mid);
|
final String heroTag = Utils.makeHeroTag(item.user!.mid);
|
||||||
final String bvid = item.item!.uri!.split('/').last;
|
final Uri uri = Uri.parse(item.item!.uri!);
|
||||||
// 页码
|
|
||||||
final String page =
|
|
||||||
item.item!.nativeUri!.split('page=').last.split('&').first;
|
|
||||||
// 根评论id
|
|
||||||
final String commentRootId =
|
|
||||||
item.item!.nativeUri!.split('comment_root_id=').last.split('&').first;
|
|
||||||
// 二级评论id
|
|
||||||
final String commentSecondaryId =
|
|
||||||
item.item!.nativeUri!.split('comment_secondary_id=').last;
|
|
||||||
|
|
||||||
|
/// bilibili://
|
||||||
|
final Uri nativeUri = Uri.parse(item.item!.nativeUri!);
|
||||||
|
final String type = item.item!.type!;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
MessageUtils.onClickMessage(context, uri, nativeUri, type);
|
||||||
final String heroTag = Utils.makeHeroTag(bvid);
|
|
||||||
Get.toNamed<dynamic>(
|
|
||||||
'/video?bvid=$bvid&cid=$cid',
|
|
||||||
arguments: <String, String?>{
|
|
||||||
'pic': '',
|
|
||||||
'heroTag': heroTag,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
@ -217,6 +202,7 @@ class ReplyItem extends StatelessWidget {
|
|||||||
width: 60,
|
width: 60,
|
||||||
height: 60,
|
height: 60,
|
||||||
src: item.item!.image,
|
src: item.item!.image,
|
||||||
|
radius: 6,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/models/msg/system.dart';
|
import 'package:pilipala/models/msg/system.dart';
|
||||||
|
import 'package:pilipala/pages/message/utils/index.dart';
|
||||||
|
import 'package:pilipala/utils/app_scheme.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
class MessageSystemPage extends StatefulWidget {
|
class MessageSystemPage extends StatefulWidget {
|
||||||
@ -97,6 +100,13 @@ class SystemItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// if (item.content is Map) {
|
||||||
|
// var res = MessageUtils().extractLinks(item.content['web']);
|
||||||
|
// print('res: $res');
|
||||||
|
// } else {
|
||||||
|
// var res = MessageUtils().extractLinks(item.content);
|
||||||
|
// print('res: $res');
|
||||||
|
// }
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(14, 14, 14, 12),
|
padding: const EdgeInsets.fromLTRB(14, 14, 14, 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -111,9 +121,73 @@ class SystemItem extends StatelessWidget {
|
|||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(item.content is String ? item.content : item.content!['web']),
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
buildContent(
|
||||||
|
context,
|
||||||
|
item.content is String ? item.content : item.content!['web']!,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// if (item.content is String)
|
||||||
|
// Text(item.content)
|
||||||
|
// else ...[
|
||||||
|
// Text(item.content!['web']!),
|
||||||
|
// ]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InlineSpan buildContent(
|
||||||
|
BuildContext context,
|
||||||
|
String content,
|
||||||
|
) {
|
||||||
|
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
|
final List<InlineSpan> spanChilds = <InlineSpan>[];
|
||||||
|
Map<String, dynamic> contentMap = MessageUtils().extractLinks(content);
|
||||||
|
List<String> keys = contentMap.keys.toList();
|
||||||
|
keys.removeWhere((element) => element == 'message');
|
||||||
|
String patternStr = keys.join('|');
|
||||||
|
RegExp regExp = RegExp(patternStr, caseSensitive: false);
|
||||||
|
|
||||||
|
contentMap['message'].splitMapJoin(
|
||||||
|
regExp,
|
||||||
|
onMatch: (Match match) {
|
||||||
|
if (!match.group(0)!.startsWith('BV')) {
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: Icon(Icons.link, color: colorScheme.primary, size: 16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
spanChilds.add(
|
||||||
|
TextSpan(
|
||||||
|
text: match.group(0),
|
||||||
|
style: TextStyle(
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
PiliSchame.routePush(Uri.parse(contentMap[match.group(0)]));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
onNonMatch: (String text) {
|
||||||
|
spanChilds.add(
|
||||||
|
TextSpan(
|
||||||
|
text: text,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return TextSpan(
|
||||||
|
children: spanChilds,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
141
lib/pages/message/utils/index.dart
Normal file
141
lib/pages/message/utils/index.dart
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/reply_reply/index.dart';
|
||||||
|
import 'package:pilipala/utils/app_scheme.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
class MessageUtils {
|
||||||
|
// 回复我的、收到的赞点击
|
||||||
|
static void onClickMessage(
|
||||||
|
BuildContext context, Uri uri, Uri nativeUri, String type) async {
|
||||||
|
final String path = uri.path;
|
||||||
|
final String bvid = path.split('/').last;
|
||||||
|
final String nativePath = nativeUri.path;
|
||||||
|
final String oid = nativePath.split('/').last;
|
||||||
|
final Map<String, String> queryParameters = nativeUri.queryParameters;
|
||||||
|
final String? argCid = queryParameters['cid'];
|
||||||
|
// final String? page = queryParameters['page'];
|
||||||
|
final String? commentRootId = queryParameters['comment_root_id'];
|
||||||
|
// final String? commentSecondaryId = queryParameters['comment_secondary_id'];
|
||||||
|
switch (type) {
|
||||||
|
case 'video':
|
||||||
|
case 'danmu':
|
||||||
|
try {
|
||||||
|
final int cid = argCid != null
|
||||||
|
? int.parse(argCid)
|
||||||
|
: await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
|
Get.toNamed<dynamic>(
|
||||||
|
'/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: <String, String?>{
|
||||||
|
'pic': '',
|
||||||
|
'heroTag': heroTag,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast('视频可能失效了$e');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'reply':
|
||||||
|
debugPrint('commentRootId: $oid, $commentRootId');
|
||||||
|
navigateToComment(
|
||||||
|
context, oid, commentRootId!, ReplyType.video, nativeUri);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转查看评论
|
||||||
|
static void navigateToComment(
|
||||||
|
BuildContext context,
|
||||||
|
String oid,
|
||||||
|
String rpid,
|
||||||
|
ReplyType replyType,
|
||||||
|
Uri nativeUri,
|
||||||
|
) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('评论详情'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
tooltip: '查看原内容',
|
||||||
|
onPressed: () {
|
||||||
|
PiliSchame.routePush(nativeUri);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.open_in_new_outlined),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: VideoReplyReplyPanel(
|
||||||
|
oid: int.tryParse(oid),
|
||||||
|
rpid: int.tryParse(rpid),
|
||||||
|
source: 'routePush',
|
||||||
|
replyType: replyType,
|
||||||
|
firstFloor: null,
|
||||||
|
showRoot: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配链接
|
||||||
|
Map<String, String> extractLinks(String text) {
|
||||||
|
Map<String, String> result = {};
|
||||||
|
String message = '';
|
||||||
|
// 是否匹配到bv
|
||||||
|
RegExp bvRegex = RegExp(r'bv1[\d\w]{9}', caseSensitive: false);
|
||||||
|
final Iterable<RegExpMatch> bvMatches = bvRegex.allMatches(text);
|
||||||
|
for (var match in bvMatches) {
|
||||||
|
result[match.group(0)!] =
|
||||||
|
'https://www.bilibili.com/video/${match.group(0)!}';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义正则表达式
|
||||||
|
RegExp regex = RegExp(
|
||||||
|
r'(?:(?:(?:http:\/\/|https:\/\/)(?:[a-zA-Z0-9_.-]+\.)*(?:bilibili|biligame)\.com(?:\/[/.$*?~=#!%@&\-\w]*)?)|(?:(?:http:\/\/|https:\/\/)(?:[a-zA-Z0-9_.-]+\.)*(?:acg|b23)\.tv(?:\/[/.$*?~=#!%@&\-\w]*)?)|(?:(?:http:\/\/|https:\/\/)dl\.(?:hdslb)\.com(?:\/[/.$*?~=#!%@&\-\w]*)?))');
|
||||||
|
// 链接文字
|
||||||
|
RegExp linkTextRegex = RegExp(r"#\{(.*?)\}");
|
||||||
|
final Iterable<RegExpMatch> matches = regex.allMatches(text);
|
||||||
|
int lastMatchEnd = 0;
|
||||||
|
if (matches.isNotEmpty) {
|
||||||
|
for (var match in matches) {
|
||||||
|
final int start = match.start;
|
||||||
|
final int end = match.end;
|
||||||
|
String str = text.substring(lastMatchEnd, start);
|
||||||
|
final Iterable<RegExpMatch> linkTextMatches =
|
||||||
|
linkTextRegex.allMatches(str);
|
||||||
|
|
||||||
|
if (linkTextMatches.isNotEmpty) {
|
||||||
|
for (var linkTextMatch in linkTextMatches) {
|
||||||
|
if (linkTextMatch.group(1) != null) {
|
||||||
|
String linkText = linkTextMatch.group(1)!;
|
||||||
|
str = str
|
||||||
|
.replaceAll(linkTextMatch.group(0)!, linkText)
|
||||||
|
.replaceAll('{', '')
|
||||||
|
.replaceAll('}', '');
|
||||||
|
result[linkText] = match.group(0)!;
|
||||||
|
}
|
||||||
|
message += str;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message += '$str查看详情';
|
||||||
|
result['查看详情'] = match.group(0)!;
|
||||||
|
}
|
||||||
|
lastMatchEnd = end;
|
||||||
|
}
|
||||||
|
result['message'] = message;
|
||||||
|
} else {
|
||||||
|
result['message'] = text;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -183,6 +183,14 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
|||||||
setKey: SettingBoxKey.enableAi,
|
setKey: SettingBoxKey.enableAi,
|
||||||
defaultVal: true,
|
defaultVal: true,
|
||||||
),
|
),
|
||||||
|
SetSwitchItem(
|
||||||
|
title: '视频简介默认展开',
|
||||||
|
setKey: SettingBoxKey.enableAutoExpand,
|
||||||
|
defaultVal: false,
|
||||||
|
callFn: (val) {
|
||||||
|
GlobalDataCache().enableAutoExpand = val;
|
||||||
|
},
|
||||||
|
),
|
||||||
const SetSwitchItem(
|
const SetSwitchItem(
|
||||||
title: '相关视频推荐',
|
title: '相关视频推荐',
|
||||||
subTitle: '视频详情页推荐相关视频',
|
subTitle: '视频详情页推荐相关视频',
|
||||||
|
|||||||
@ -108,6 +108,12 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
defaultVal: true,
|
defaultVal: true,
|
||||||
needReboot: true,
|
needReboot: true,
|
||||||
),
|
),
|
||||||
|
const SetSwitchItem(
|
||||||
|
title: '动态页滑动切换up',
|
||||||
|
setKey: SettingBoxKey.enableDynamicSwitch,
|
||||||
|
defaultVal: true,
|
||||||
|
needReboot: true,
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
int? result = await showDialog(
|
int? result = await showDialog(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
// import 'package:bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -62,6 +62,7 @@ class VideoIntroController extends GetxController {
|
|||||||
late ModelResult modelResult;
|
late ModelResult modelResult;
|
||||||
PersistentBottomSheetController? bottomSheetController;
|
PersistentBottomSheetController? bottomSheetController;
|
||||||
late bool enableRelatedVideo;
|
late bool enableRelatedVideo;
|
||||||
|
UgcSeason? ugcSeason;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -87,6 +88,7 @@ class VideoIntroController extends GetxController {
|
|||||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
videoDetail.value = result['data']!;
|
videoDetail.value = result['data']!;
|
||||||
|
ugcSeason = result['data']!.ugcSeason;
|
||||||
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
|
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
|
||||||
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
|
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
|
||||||
}
|
}
|
||||||
@ -531,25 +533,31 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置关注分组
|
// 设置关注分组
|
||||||
void setFollowGroup() {
|
void setFollowGroup() async {
|
||||||
showFlexibleBottomSheet(
|
final mediaQueryData = MediaQuery.of(Get.context!);
|
||||||
bottomSheetBorderRadius: const BorderRadius.only(
|
final contentHeight = mediaQueryData.size.height - kToolbarHeight;
|
||||||
topLeft: Radius.circular(16),
|
final double initialChildSize =
|
||||||
topRight: Radius.circular(16),
|
(contentHeight - Get.width * 9 / 16) / contentHeight;
|
||||||
),
|
await showModalBottomSheet(
|
||||||
minHeight: 0.6,
|
|
||||||
initHeight: 0.6,
|
|
||||||
maxHeight: 1,
|
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (BuildContext context, ScrollController scrollController,
|
useSafeArea: true,
|
||||||
double offset) {
|
isScrollControlled: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return DraggableScrollableSheet(
|
||||||
|
initialChildSize: initialChildSize,
|
||||||
|
minChildSize: 0,
|
||||||
|
maxChildSize: 1,
|
||||||
|
snap: true,
|
||||||
|
expand: false,
|
||||||
|
snapSizes: [initialChildSize],
|
||||||
|
builder: (BuildContext context, ScrollController scrollController) {
|
||||||
return GroupPanel(
|
return GroupPanel(
|
||||||
mid: videoDetail.value.owner!.mid!,
|
mid: videoDetail.value.owner!.mid!,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
anchors: [0.6, 1],
|
);
|
||||||
isSafeArea: true,
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,9 +610,9 @@ class VideoIntroController extends GetxController {
|
|||||||
episodes: episodes,
|
episodes: episodes,
|
||||||
currentCid: lastPlayCid.value,
|
currentCid: lastPlayCid.value,
|
||||||
dataType: dataType,
|
dataType: dataType,
|
||||||
context: Get.context!,
|
|
||||||
sheetHeight: Get.size.height,
|
sheetHeight: Get.size.height,
|
||||||
isFullScreen: true,
|
isFullScreen: true,
|
||||||
|
ugcSeason: ugcSeason,
|
||||||
changeFucCall: (item, index) {
|
changeFucCall: (item, index) {
|
||||||
if (dataType == VideoEpidoesType.videoEpisode) {
|
if (dataType == VideoEpidoesType.videoEpisode) {
|
||||||
changeSeasonOrbangu(
|
changeSeasonOrbangu(
|
||||||
@ -615,7 +623,7 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
},
|
},
|
||||||
).buildShowContent(Get.context!),
|
).buildShowContent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
// import 'package:bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -169,7 +169,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
owner = widget.videoDetail!.owner;
|
owner = widget.videoDetail!.owner;
|
||||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||||
_expandableCtr = ExpandableController(initialExpanded: false);
|
_expandableCtr = ExpandableController(
|
||||||
|
initialExpanded: GlobalDataCache().enableAutoExpand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -198,25 +199,35 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFavPanel() {
|
void _showFavPanel() async {
|
||||||
showFlexibleBottomSheet(
|
final mediaQueryData = MediaQuery.of(context);
|
||||||
bottomSheetBorderRadius: const BorderRadius.only(
|
final contentHeight = mediaQueryData.size.height - kToolbarHeight;
|
||||||
topLeft: Radius.circular(16),
|
final double initialChildSize =
|
||||||
topRight: Radius.circular(16),
|
(contentHeight - Get.width * 9 / 16) / contentHeight;
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: Get.context!,
|
||||||
|
useSafeArea: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
transitionAnimationController: AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
vsync: this,
|
||||||
),
|
),
|
||||||
minHeight: 0.6,
|
builder: (BuildContext context) {
|
||||||
initHeight: 0.6,
|
return DraggableScrollableSheet(
|
||||||
maxHeight: 1,
|
initialChildSize: initialChildSize,
|
||||||
context: context,
|
minChildSize: 0,
|
||||||
builder: (BuildContext context, ScrollController scrollController,
|
maxChildSize: 1,
|
||||||
double offset) {
|
snap: true,
|
||||||
|
expand: false,
|
||||||
|
snapSizes: [initialChildSize],
|
||||||
|
builder: (BuildContext context, ScrollController scrollController) {
|
||||||
return FavPanel(
|
return FavPanel(
|
||||||
ctr: videoIntroController,
|
ctr: videoIntroController,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
anchors: [0.6, 1],
|
);
|
||||||
isSafeArea: true,
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -32,8 +31,14 @@ class _FavPanelState extends State<FavPanel> {
|
|||||||
AppBar(
|
AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
automaticallyImplyLeading: false,
|
shape: const RoundedRectangleBorder(
|
||||||
leadingWidth: 0,
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(Icons.close_outlined)),
|
||||||
title: Text(
|
title: Text(
|
||||||
'选择收藏夹',
|
'选择收藏夹',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
@ -61,16 +66,16 @@ class _FavPanelState extends State<FavPanel> {
|
|||||||
onTap: () =>
|
onTap: () =>
|
||||||
widget.ctr!.onChoose(item.favState != 1, index),
|
widget.ctr!.onChoose(item.favState != 1, index),
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: Icon([23, 1].contains(item.attr)
|
leading: Icon([22, 0].contains(item.attr)
|
||||||
? Icons.lock_outline
|
? Icons.lock_outline
|
||||||
: Icons.folder_outlined),
|
: Icons.folder_outlined),
|
||||||
minLeadingWidth: 0,
|
minLeadingWidth: 0,
|
||||||
title: Text(item.title!),
|
title: Text(item.title!),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${item.mediaCount}个内容 - ${[
|
'${item.mediaCount}个内容 - ${[
|
||||||
23,
|
22,
|
||||||
1
|
0
|
||||||
].contains(item.attr) ? '私密' : '公开'}',
|
].contains(item.attr) ? '公开' : '私密'}',
|
||||||
),
|
),
|
||||||
trailing: Transform.scale(
|
trailing: Transform.scale(
|
||||||
scale: 0.9,
|
scale: 0.9,
|
||||||
@ -92,7 +97,7 @@ class _FavPanelState extends State<FavPanel> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
// 骨架屏
|
||||||
return const Text('请求中');
|
return const Center(child: Text('请求中'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -59,10 +59,19 @@ class _GroupPanelState extends State<GroupPanel> {
|
|||||||
AppBar(
|
AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
icon: const Icon(Icons.close_outlined)),
|
icon: const Icon(Icons.close_outlined)),
|
||||||
title: Text('设置关注分组', style: Theme.of(context).textTheme.titleMedium),
|
title: Text('设置关注分组',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium!
|
||||||
|
.copyWith(fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Material(
|
child: Material(
|
||||||
@ -115,7 +124,7 @@ class _GroupPanelState extends State<GroupPanel> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
// 骨架屏
|
||||||
return const Text('请求中');
|
return const Center(child: Text('请求中'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -116,7 +116,6 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
changeFucCall: changeFucCall,
|
changeFucCall: changeFucCall,
|
||||||
sheetHeight: widget.sheetHeight,
|
sheetHeight: widget.sheetHeight,
|
||||||
dataType: VideoEpidoesType.videoPart,
|
dataType: VideoEpidoesType.videoPart,
|
||||||
context: context,
|
|
||||||
).show(context);
|
).show(context);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@ -124,7 +124,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
changeFucCall: changeFucCall,
|
changeFucCall: changeFucCall,
|
||||||
sheetHeight: widget.sheetHeight,
|
sheetHeight: widget.sheetHeight,
|
||||||
dataType: VideoEpidoesType.videoEpisode,
|
dataType: VideoEpidoesType.videoEpisode,
|
||||||
context: context,
|
ugcSeason: widget.ugcSeason,
|
||||||
).show(context);
|
).show(context);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
@ -764,14 +764,14 @@ InlineSpan buildContent(
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
|
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
|
||||||
SchemeEntity scheme = SchemeEntity(
|
Uri scheme = Uri(
|
||||||
scheme: uri.scheme,
|
scheme: uri.scheme,
|
||||||
host: uri.host,
|
host: uri.host,
|
||||||
port: uri.port,
|
port: uri.port,
|
||||||
path: uri.path,
|
path: uri.path,
|
||||||
query: uri.queryParameters,
|
// query: uri.queryParameters,
|
||||||
source: '',
|
// source: '',
|
||||||
dataString: matchStr,
|
// dataString: matchStr,
|
||||||
);
|
);
|
||||||
PiliSchame.httpsScheme(scheme);
|
PiliSchame.httpsScheme(scheme);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,15 @@ import 'package:pilipala/models/common/reply_type.dart';
|
|||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
|
|
||||||
class VideoReplyReplyController extends GetxController {
|
class VideoReplyReplyController extends GetxController {
|
||||||
VideoReplyReplyController(this.aid, this.rpid, this.replyType);
|
VideoReplyReplyController(this.aid, this.rpid, this.replyType, this.showRoot);
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
// 视频aid 请求时使用的oid
|
// 视频aid 请求时使用的oid
|
||||||
int? aid;
|
int? aid;
|
||||||
// rpid 请求楼中楼回复
|
// rpid 请求楼中楼回复
|
||||||
String? rpid;
|
String? rpid;
|
||||||
ReplyType replyType = ReplyType.video;
|
ReplyType replyType = ReplyType.video;
|
||||||
|
bool showRoot = false;
|
||||||
|
ReplyItemModel? rootReply;
|
||||||
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
||||||
// 当前页
|
// 当前页
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
@ -42,6 +44,7 @@ class VideoReplyReplyController extends GetxController {
|
|||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
final List<ReplyItemModel> replies = res['data'].replies;
|
final List<ReplyItemModel> replies = res['data'].replies;
|
||||||
|
ReplyItemModel? root = res['data'].root;
|
||||||
if (replies.isNotEmpty) {
|
if (replies.isNotEmpty) {
|
||||||
noMore.value = '加载中...';
|
noMore.value = '加载中...';
|
||||||
if (replies.length == res['data'].page.count) {
|
if (replies.length == res['data'].page.count) {
|
||||||
@ -60,7 +63,9 @@ class VideoReplyReplyController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
replyList.addAll(replies);
|
replyList.addAll(replies);
|
||||||
// res['data'].replies.addAll(replyList);
|
}
|
||||||
|
if (showRoot && root != null) {
|
||||||
|
rootReply = root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (replyList.isNotEmpty && currentReply != null) {
|
if (replyList.isNotEmpty && currentReply != null) {
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import 'package:pilipala/models/common/reply_type.dart';
|
|||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
class VideoReplyReplyPanel extends StatefulWidget {
|
class VideoReplyReplyPanel extends StatefulWidget {
|
||||||
@ -22,6 +21,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
|
|||||||
this.sheetHeight,
|
this.sheetHeight,
|
||||||
this.currentReply,
|
this.currentReply,
|
||||||
this.loadMore = true,
|
this.loadMore = true,
|
||||||
|
this.showRoot = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final int? oid;
|
final int? oid;
|
||||||
@ -33,6 +33,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
|
|||||||
final double? sheetHeight;
|
final double? sheetHeight;
|
||||||
final dynamic currentReply;
|
final dynamic currentReply;
|
||||||
final bool loadMore;
|
final bool loadMore;
|
||||||
|
final bool showRoot;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VideoReplyReplyPanel> createState() => _VideoReplyReplyPanelState();
|
State<VideoReplyReplyPanel> createState() => _VideoReplyReplyPanelState();
|
||||||
@ -49,7 +50,11 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
_videoReplyReplyController = Get.put(
|
_videoReplyReplyController = Get.put(
|
||||||
VideoReplyReplyController(
|
VideoReplyReplyController(
|
||||||
widget.oid, widget.rpid.toString(), widget.replyType!),
|
widget.oid,
|
||||||
|
widget.rpid.toString(),
|
||||||
|
widget.replyType!,
|
||||||
|
widget.showRoot,
|
||||||
|
),
|
||||||
tag: widget.rpid.toString());
|
tag: widget.rpid.toString());
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
@ -80,15 +85,8 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget _buildAppBar() {
|
||||||
Widget build(BuildContext context) {
|
return AppBar(
|
||||||
return Container(
|
|
||||||
height: widget.source == 'videoDetail' ? widget.sheetHeight : null,
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (widget.source == 'videoDetail')
|
|
||||||
AppBar(
|
|
||||||
toolbarHeight: 45,
|
toolbarHeight: 45,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
@ -101,13 +99,87 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
icon: const Icon(Icons.close, size: 20),
|
icon: const Icon(Icons.close, size: 20),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_videoReplyReplyController.currentPage = 0;
|
_videoReplyReplyController.currentPage = 0;
|
||||||
widget.closePanel?.call;
|
widget.closePanel?.call();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildReplyItem(ReplyItemModel? replyItem, String replyLevel) {
|
||||||
|
return ReplyItem(
|
||||||
|
replyItem: replyItem,
|
||||||
|
replyLevel: replyLevel,
|
||||||
|
showReplyRow: false,
|
||||||
|
addReply: (replyItem) {
|
||||||
|
_videoReplyReplyController.replyList.add(replyItem);
|
||||||
|
},
|
||||||
|
replyType: widget.replyType,
|
||||||
|
replyReply: (replyItem) => replyReply(replyItem),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSliverList() {
|
||||||
|
return Obx(
|
||||||
|
() => SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(BuildContext context, int index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return _videoReplyReplyController.rootReply != null
|
||||||
|
? Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color:
|
||||||
|
Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
|
width: 6,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _buildReplyItem(
|
||||||
|
_videoReplyReplyController.rootReply, '1'),
|
||||||
|
)
|
||||||
|
: const SizedBox();
|
||||||
|
}
|
||||||
|
int adjustedIndex = index - 1;
|
||||||
|
if (adjustedIndex == _videoReplyReplyController.replyList.length) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
height: MediaQuery.of(context).padding.bottom + 100,
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_videoReplyReplyController.noMore.value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return _buildReplyItem(
|
||||||
|
_videoReplyReplyController.replyList[adjustedIndex], '2');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childCount: _videoReplyReplyController.replyList.length + 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: widget.source == 'videoDetail' ? widget.sheetHeight : null,
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (widget.source == 'videoDetail') _buildAppBar(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
@ -120,28 +192,22 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: _videoReplyReplyController.scrollController,
|
controller: _videoReplyReplyController.scrollController,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
if (widget.firstFloor != null) ...[
|
if (widget.firstFloor != null)
|
||||||
// const SliverToBoxAdapter(child: SizedBox(height: 10)),
|
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: ReplyItem(
|
child: Container(
|
||||||
replyItem: widget.firstFloor,
|
decoration: BoxDecoration(
|
||||||
replyLevel: '2',
|
border: Border(
|
||||||
showReplyRow: false,
|
bottom: BorderSide(
|
||||||
addReply: (replyItem) {
|
color: Theme.of(context)
|
||||||
_videoReplyReplyController.replyList.add(replyItem);
|
.dividerColor
|
||||||
},
|
.withOpacity(0.1),
|
||||||
replyType: widget.replyType,
|
width: 6,
|
||||||
replyReply: (replyItem) => replyReply(replyItem),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
),
|
||||||
child: Divider(
|
child: _buildReplyItem(widget.firstFloor, '2'),
|
||||||
height: 20,
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
|
||||||
thickness: 6,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
widget.loadMore
|
widget.loadMore
|
||||||
? FutureBuilder(
|
? FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
@ -150,76 +216,21 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
ConnectionState.done) {
|
ConnectionState.done) {
|
||||||
Map? data = snapshot.data;
|
Map? data = snapshot.data;
|
||||||
if (data != null && data['status']) {
|
if (data != null && data['status']) {
|
||||||
// 请求成功
|
return _buildSliverList();
|
||||||
return Obx(
|
|
||||||
() => SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
if (index ==
|
|
||||||
_videoReplyReplyController
|
|
||||||
.replyList.length) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context)
|
|
||||||
.padding
|
|
||||||
.bottom),
|
|
||||||
height: MediaQuery.of(context)
|
|
||||||
.padding
|
|
||||||
.bottom +
|
|
||||||
100,
|
|
||||||
child: Center(
|
|
||||||
child: Obx(
|
|
||||||
() => Text(
|
|
||||||
_videoReplyReplyController
|
|
||||||
.noMore.value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return ReplyItem(
|
|
||||||
replyItem:
|
|
||||||
_videoReplyReplyController
|
|
||||||
.replyList[index],
|
|
||||||
replyLevel: '2',
|
|
||||||
showReplyRow: false,
|
|
||||||
addReply: (replyItem) {
|
|
||||||
_videoReplyReplyController
|
|
||||||
.replyList
|
|
||||||
.add(replyItem);
|
|
||||||
},
|
|
||||||
replyType: widget.replyType,
|
|
||||||
replyReply: (replyItem) =>
|
|
||||||
replyReply(replyItem),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
childCount: _videoReplyReplyController
|
|
||||||
.replyList.length +
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 请求错误
|
|
||||||
return HttpError(
|
return HttpError(
|
||||||
errMsg: data?['msg'] ?? '请求错误',
|
errMsg: data?['msg'] ?? '请求错误',
|
||||||
fn: () => setState(() {}),
|
fn: () => setState(() {}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
|
||||||
return SliverList(
|
return SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
return const VideoReplySkeleton();
|
return const VideoReplySkeleton();
|
||||||
}, childCount: 8),
|
},
|
||||||
|
childCount: 8,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -237,7 +248,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -24,30 +24,30 @@ class PiliSchame {
|
|||||||
|
|
||||||
appScheme.getInitScheme().then((SchemeEntity? value) {
|
appScheme.getInitScheme().then((SchemeEntity? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
_routePush(value);
|
routePush(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
appScheme.getLatestScheme().then((SchemeEntity? value) {
|
appScheme.getLatestScheme().then((SchemeEntity? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
_routePush(value);
|
routePush(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
appScheme.registerSchemeListener().listen((SchemeEntity? event) {
|
appScheme.registerSchemeListener().listen((SchemeEntity? event) {
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
_routePush(event);
|
routePush(event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 路由跳转
|
/// 路由跳转
|
||||||
static void _routePush(value) async {
|
static void routePush(value) async {
|
||||||
final String scheme = value.scheme;
|
final String scheme = value.scheme;
|
||||||
if (scheme == 'bilibili') {
|
if (scheme == 'bilibili') {
|
||||||
biliScheme(value);
|
biliScheme(value);
|
||||||
}
|
}
|
||||||
if (scheme == 'https') {
|
if (['http', 'https'].contains(scheme)) {
|
||||||
httpsScheme(value);
|
httpsScheme(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,16 +79,16 @@ class PiliSchame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> httpsScheme(SchemeEntity value) async {
|
static Future<void> httpsScheme(Uri value) async {
|
||||||
// https://m.bilibili.com/bangumi/play/ss39708
|
// https://m.bilibili.com/bangumi/play/ss39708
|
||||||
// https | m.bilibili.com | /bangumi/play/ss39708
|
// https | m.bilibili.com | /bangumi/play/ss39708
|
||||||
// final String scheme = value.scheme!;
|
// final String scheme = value.scheme!;
|
||||||
final String host = value.host!;
|
final String host = value.host;
|
||||||
final String? path = value.path;
|
final String path = value.path;
|
||||||
Map<String, String>? query = value.query;
|
Map<String, String>? query = value.queryParameters;
|
||||||
RegExp regExp = RegExp(r'^((www\.)|(m\.))?bilibili\.com$');
|
RegExp regExp = RegExp(r'^((www\.)|(m\.))?bilibili\.com$');
|
||||||
if (regExp.hasMatch(host)) {
|
if (regExp.hasMatch(host)) {
|
||||||
final String lastPathSegment = path!.split('/').last;
|
final String lastPathSegment = path.split('/').last;
|
||||||
if (path.startsWith('/video')) {
|
if (path.startsWith('/video')) {
|
||||||
Map matchRes = IdUtils.matchAvorBv(input: path);
|
Map matchRes = IdUtils.matchAvorBv(input: path);
|
||||||
if (matchRes.containsKey('AV')) {
|
if (matchRes.containsKey('AV')) {
|
||||||
@ -113,13 +113,13 @@ class PiliSchame {
|
|||||||
_videoPush(Utils.matchNum(path.split('?').first).first, null);
|
_videoPush(Utils.matchNum(path.split('?').first).first, null);
|
||||||
}
|
}
|
||||||
} else if (host.contains('live')) {
|
} else if (host.contains('live')) {
|
||||||
int roomId = int.parse(path!.split('/').last);
|
int roomId = int.parse(path.split('/').last);
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/liveRoom?roomid=$roomId',
|
'/liveRoom?roomid=$roomId',
|
||||||
arguments: {'liveItem': null, 'heroTag': roomId.toString()},
|
arguments: {'liveItem': null, 'heroTag': roomId.toString()},
|
||||||
);
|
);
|
||||||
} else if (host.contains('space')) {
|
} else if (host.contains('space')) {
|
||||||
var mid = path!.split('/').last;
|
var mid = path.split('/').last;
|
||||||
Get.toNamed('/member?mid=$mid', arguments: {'face': ''});
|
Get.toNamed('/member?mid=$mid', arguments: {'face': ''});
|
||||||
return;
|
return;
|
||||||
} else if (host == 'b23.tv') {
|
} else if (host == 'b23.tv') {
|
||||||
@ -154,7 +154,7 @@ class PiliSchame {
|
|||||||
parameters: {'url': redirectUrl, 'type': 'url', 'pageTitle': ''},
|
parameters: {'url': redirectUrl, 'type': 'url', 'pageTitle': ''},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (path != null) {
|
} else {
|
||||||
final String area = path.split('/').last;
|
final String area = path.split('/').last;
|
||||||
switch (area) {
|
switch (area) {
|
||||||
case 'bangumi':
|
case 'bangumi':
|
||||||
@ -178,12 +178,12 @@ class PiliSchame {
|
|||||||
break;
|
break;
|
||||||
case 'read':
|
case 'read':
|
||||||
print('专栏');
|
print('专栏');
|
||||||
String id = Utils.matchNum(query!['id']!).first.toString();
|
String id = Utils.matchNum(query['id']!).first.toString();
|
||||||
Get.toNamed('/read', parameters: {
|
Get.toNamed('/read', parameters: {
|
||||||
'url': value.dataString!,
|
'url': value.toString(),
|
||||||
'title': '',
|
'title': '',
|
||||||
'id': id,
|
'id': id,
|
||||||
'articleType': 'read'
|
'articleType': 'read',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'space':
|
case 'space':
|
||||||
@ -201,9 +201,9 @@ class PiliSchame {
|
|||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/webview',
|
'/webview',
|
||||||
parameters: {
|
parameters: {
|
||||||
'url': value.dataString ?? "",
|
'url': value.toString(),
|
||||||
'type': 'url',
|
'type': 'url',
|
||||||
'pageTitle': ''
|
'pageTitle': '',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,10 @@ class GlobalDataCache {
|
|||||||
late List historyCacheList;
|
late List historyCacheList;
|
||||||
//
|
//
|
||||||
late bool enableSearchSuggest = true;
|
late bool enableSearchSuggest = true;
|
||||||
|
// 简介默认展开
|
||||||
|
late bool enableAutoExpand = false;
|
||||||
|
//
|
||||||
|
late bool enableDynamicSwitch = true;
|
||||||
|
|
||||||
// 私有构造函数
|
// 私有构造函数
|
||||||
GlobalDataCache._();
|
GlobalDataCache._();
|
||||||
@ -112,5 +116,9 @@ class GlobalDataCache {
|
|||||||
historyCacheList = localCache.get('cacheList', defaultValue: []);
|
historyCacheList = localCache.get('cacheList', defaultValue: []);
|
||||||
enableSearchSuggest =
|
enableSearchSuggest =
|
||||||
setting.get(SettingBoxKey.enableSearchSuggest, defaultValue: true);
|
setting.get(SettingBoxKey.enableSearchSuggest, defaultValue: true);
|
||||||
|
enableAutoExpand =
|
||||||
|
setting.get(SettingBoxKey.enableAutoExpand, defaultValue: false);
|
||||||
|
enableDynamicSwitch =
|
||||||
|
setting.get(SettingBoxKey.enableDynamicSwitch, defaultValue: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,6 +113,7 @@ class SettingBoxKey {
|
|||||||
enableSearchWord = 'enableSearchWord',
|
enableSearchWord = 'enableSearchWord',
|
||||||
enableSystemProxy = 'enableSystemProxy',
|
enableSystemProxy = 'enableSystemProxy',
|
||||||
enableAi = 'enableAi',
|
enableAi = 'enableAi',
|
||||||
|
enableAutoExpand = 'enableAutoExpand',
|
||||||
defaultHomePage = 'defaultHomePage',
|
defaultHomePage = 'defaultHomePage',
|
||||||
enableRelatedVideo = 'enableRelatedVideo';
|
enableRelatedVideo = 'enableRelatedVideo';
|
||||||
|
|
||||||
@ -130,6 +131,7 @@ class SettingBoxKey {
|
|||||||
tabbarSort = 'tabbarSort', // 首页tabbar
|
tabbarSort = 'tabbarSort', // 首页tabbar
|
||||||
dynamicBadgeMode = 'dynamicBadgeMode',
|
dynamicBadgeMode = 'dynamicBadgeMode',
|
||||||
enableGradientBg = 'enableGradientBg',
|
enableGradientBg = 'enableGradientBg',
|
||||||
|
enableDynamicSwitch = 'enableDynamicSwitch',
|
||||||
navBarSort = 'navBarSort',
|
navBarSort = 'navBarSort',
|
||||||
actionTypeSort = 'actionTypeSort';
|
actionTypeSort = 'actionTypeSort';
|
||||||
}
|
}
|
||||||
|
|||||||
16
pubspec.lock
16
pubspec.lock
@ -145,22 +145,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
bottom_inset_observer:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: bottom_inset_observer
|
|
||||||
sha256: cbfb01e0e07cc4922052701786d5e607765a6f54e1844f41061abf8744519a7d
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.0"
|
|
||||||
bottom_sheet:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: bottom_sheet
|
|
||||||
sha256: efd28f52357d23e1c01eaeb45466b407f1e29318305bd6d10baf814fda18bd7e
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "4.0.4"
|
|
||||||
brotli:
|
brotli:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -147,7 +147,6 @@ dependencies:
|
|||||||
lottie: ^3.1.2
|
lottie: ^3.1.2
|
||||||
# 二维码
|
# 二维码
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
bottom_sheet: ^4.0.4
|
|
||||||
web_socket_channel: ^2.4.5
|
web_socket_channel: ^2.4.5
|
||||||
brotli: ^0.6.0
|
brotli: ^0.6.0
|
||||||
# 文本语法高亮
|
# 文本语法高亮
|
||||||
|
|||||||
Reference in New Issue
Block a user