merge main
This commit is contained in:
@ -1,3 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/bangumi.dart';
|
||||
import 'package:pilipala/models/bangumi/list.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class BangumiController extends GetxController {}
|
||||
class BangumiController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
RxList<BangumiListItemModel> bangumiList = [BangumiListItemModel()].obs;
|
||||
RxList<BangumiListItemModel> bangumiFollowList = [BangumiListItemModel()].obs;
|
||||
int _currentPage = 1;
|
||||
bool isLoadingMore = true;
|
||||
Box user = GStrorage.user;
|
||||
RxBool userLogin = false.obs;
|
||||
late int mid;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (user.get(UserBoxKey.userMid) != null) {
|
||||
mid = int.parse(user.get(UserBoxKey.userMid).toString());
|
||||
}
|
||||
userLogin.value = user.get(UserBoxKey.userLogin) != null;
|
||||
}
|
||||
|
||||
Future queryBangumiListFeed({type = 'init'}) async {
|
||||
if (type == 'init') {
|
||||
_currentPage = 1;
|
||||
}
|
||||
var result = await BangumiHttp.bangumiList(page: _currentPage);
|
||||
if (result['status']) {
|
||||
if (type == 'init') {
|
||||
bangumiList.value = result['data'].list;
|
||||
} else {
|
||||
bangumiList.addAll(result['data'].list);
|
||||
}
|
||||
_currentPage += 1;
|
||||
} else {}
|
||||
isLoadingMore = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
queryBangumiListFeed(type: 'onLoad');
|
||||
}
|
||||
|
||||
// 我的订阅
|
||||
Future queryBangumiFollow() async {
|
||||
var result = await BangumiHttp.bangumiFollow(mid: 17340771);
|
||||
if (result['status']) {
|
||||
bangumiFollowList.value = result['data'].list;
|
||||
} else {}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 返回顶部并刷新
|
||||
void animateToTop() async {
|
||||
if (scrollController.offset >=
|
||||
MediaQuery.of(Get.context!).size.height * 5) {
|
||||
scrollController.jumpTo(0);
|
||||
} else {
|
||||
await scrollController.animateTo(0,
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ class BangumiIntroController extends GetxController {
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
// 视频详情 请求返回
|
||||
Rx<VideoDetailData> videoDetail = VideoDetailData().obs;
|
||||
Rx<BangumiInfoModel> bangumiDetail = BangumiInfoModel().obs;
|
||||
|
||||
// 请求返回的信息
|
||||
@ -89,11 +88,6 @@ class BangumiIntroController extends GetxController {
|
||||
|
||||
// 获取番剧简介&选集
|
||||
Future queryBangumiIntro() async {
|
||||
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
|
||||
if (result['status']) {
|
||||
bangumiDetail.value = result['data'];
|
||||
epId = bangumiDetail.value.episodes!.first.id;
|
||||
}
|
||||
if (userLogin) {
|
||||
// 获取点赞状态
|
||||
queryHasLikeVideo();
|
||||
@ -102,6 +96,11 @@ class BangumiIntroController extends GetxController {
|
||||
// 获取收藏状态
|
||||
queryHasFavVideo();
|
||||
}
|
||||
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
|
||||
if (result['status']) {
|
||||
bangumiDetail.value = result['data'];
|
||||
epId = bangumiDetail.value.episodes!.first.id;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -132,15 +131,10 @@ class BangumiIntroController extends GetxController {
|
||||
Future actionLikeVideo() async {
|
||||
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
||||
if (result['status']) {
|
||||
if (!hasLike.value) {
|
||||
SmartDialog.showToast('点赞成功 👍');
|
||||
hasLike.value = true;
|
||||
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
|
||||
} else if (hasLike.value) {
|
||||
SmartDialog.showToast('取消赞');
|
||||
hasLike.value = false;
|
||||
videoDetail.value.stat!.like = videoDetail.value.stat!.like! - 1;
|
||||
}
|
||||
SmartDialog.showToast(!hasLike.value ? '点赞成功 👍' : '取消赞');
|
||||
hasLike.value = !hasLike.value;
|
||||
bangumiDetail.value.stat!['likes'] =
|
||||
bangumiDetail.value.stat!['likes'] + (!hasLike.value ? 1 : -1);
|
||||
hasLike.refresh();
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
@ -193,8 +187,8 @@ class BangumiIntroController extends GetxController {
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功 👏');
|
||||
hasCoin.value = true;
|
||||
videoDetail.value.stat!.coin =
|
||||
videoDetail.value.stat!.coin! + _tempThemeValue;
|
||||
bangumiDetail.value.stat!['coins'] =
|
||||
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
@ -287,4 +281,13 @@ class BangumiIntroController extends GetxController {
|
||||
await VideoHttp.bangumiDel(seasonId: bangumiDetail.value.seasonId);
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
|
||||
Future queryVideoInFolder() async {
|
||||
var result = await VideoHttp.videoInFolder(
|
||||
mid: user.get(UserBoxKey.userMid), rid: IdUtils.bv2av(bvid));
|
||||
if (result['status']) {
|
||||
favFolderData.value = result['data'];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
final BangumiIntroController bangumiIntroController =
|
||||
Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']);
|
||||
BangumiInfoModel? bangumiDetail;
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
// 添加页面缓存
|
||||
@override
|
||||
@ -44,13 +45,14 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
bangumiIntroController.bangumiDetail.listen((value) {
|
||||
bangumiDetail = value;
|
||||
});
|
||||
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FutureBuilder(
|
||||
future: bangumiIntroController.queryBangumiIntro(),
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data['status']) {
|
||||
@ -89,20 +91,19 @@ class BangumiInfo extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BangumiInfoState extends State<BangumiInfo> {
|
||||
late BangumiInfoModel? bangumiItem;
|
||||
final BangumiIntroController bangumiIntroController =
|
||||
Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']);
|
||||
|
||||
late VideoDetailController? videoDetailCtr;
|
||||
String heroTag = Get.arguments['heroTag'];
|
||||
late final BangumiIntroController bangumiIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late final BangumiInfoModel? bangumiItem;
|
||||
late double sheetHeight;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
bangumiItem = bangumiIntroController.bangumiItem;
|
||||
videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
}
|
||||
|
||||
@ -357,10 +358,10 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: () => bangumiIntroController.actionLikeVideo(),
|
||||
selectStatus: bangumiIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||
: '-'),
|
||||
: bangumiItem!.stat!['likes']!.toString()),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
@ -368,10 +369,10 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => bangumiIntroController.actionCoinVideo(),
|
||||
selectStatus: bangumiIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||
: '-'),
|
||||
: bangumiItem!.stat!['coins']!.toString()),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
@ -379,29 +380,29 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: bangumiIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||
: '-'),
|
||||
: bangumiItem!.stat!['favorite']!.toString()),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
selectIcon: const Icon(FontAwesomeIcons.reply),
|
||||
onTap: () => videoDetailCtr!.tabCtr!.animateTo(1),
|
||||
onTap: () => videoDetailCtr.tabCtr!.animateTo(1),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['reply']!.toString()
|
||||
: '-',
|
||||
: bangumiItem!.stat!['reply']!.toString(),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => bangumiIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['share']!.toString()
|
||||
: '-'),
|
||||
: bangumiItem!.stat!['share']!.toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -465,9 +466,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
// text: !widget.loadingStatus
|
||||
// ? widget.videoDetail!.stat!.share!.toString()
|
||||
// : '-',
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
|
@ -1,4 +1,17 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/view.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
import 'widgets/bangumu_card_v.dart';
|
||||
|
||||
class BangumiPage extends StatefulWidget {
|
||||
const BangumiPage({super.key});
|
||||
@ -7,12 +20,187 @@ class BangumiPage extends StatefulWidget {
|
||||
State<BangumiPage> createState() => _BangumiPageState();
|
||||
}
|
||||
|
||||
class _BangumiPageState extends State<BangumiPage> {
|
||||
class _BangumiPageState extends State<BangumiPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final BangumiController _bangumidController = Get.put(BangumiController());
|
||||
late Future? _futureBuilderFuture;
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ScrollController scrollController = _bangumidController.scrollController;
|
||||
StreamController<bool> mainStream =
|
||||
Get.find<MainController>().bottomBarStream;
|
||||
_futureBuilderFuture = _bangumidController.queryBangumiListFeed();
|
||||
scrollController.addListener(
|
||||
() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
if (!_bangumidController.isLoadingMore) {
|
||||
_bangumidController.isLoadingMore = true;
|
||||
await _bangumidController.onLoad();
|
||||
}
|
||||
}
|
||||
|
||||
final ScrollDirection direction =
|
||||
scrollController.position.userScrollDirection;
|
||||
if (direction == ScrollDirection.forward) {
|
||||
mainStream.add(true);
|
||||
} else if (direction == ScrollDirection.reverse) {
|
||||
mainStream.add(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('还在开发中'),
|
||||
super.build(context);
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(StyleString.imgRadius),
|
||||
),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _bangumidController.queryBangumiListFeed(type: 'init');
|
||||
return _bangumidController.queryBangumiFollow();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _bangumidController.scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() => Visibility(
|
||||
visible: _bangumidController.userLogin.value,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10, bottom: 10, left: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'最近追番',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 254,
|
||||
child: FutureBuilder(
|
||||
future: _bangumidController.queryBangumiFollow(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: _bangumidController
|
||||
.bangumiFollowList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
width: Get.size.width / 3,
|
||||
height: 254,
|
||||
margin: EdgeInsets.only(
|
||||
right: index <
|
||||
_bangumidController
|
||||
.bangumiFollowList
|
||||
.length -
|
||||
1
|
||||
? StyleString.safeSpace
|
||||
: 0),
|
||||
child: BangumiCardV(
|
||||
bangumiItem: _bangumidController
|
||||
.bangumiFollowList[index],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox();
|
||||
}
|
||||
} else {
|
||||
return SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 10, left: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'推荐',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.zero,
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(_bangumidController,
|
||||
_bangumidController.bangumiList));
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return contentGrid(_bangumidController, []);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget contentGrid(ctr, bangumiList) {
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace - 2,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 列数
|
||||
crossAxisCount: 3,
|
||||
mainAxisExtent: Get.size.width / 3 / 0.65 + 30,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return bangumiList!.isNotEmpty
|
||||
? BangumiCardV(bangumiItem: bangumiList[index])
|
||||
: const SizedBox();
|
||||
},
|
||||
childCount: bangumiList!.isNotEmpty ? bangumiList!.length : 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -37,10 +37,10 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
child: Row(
|
||||
AppBar(
|
||||
toolbarHeight: 45,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
@ -53,10 +53,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
titleSpacing: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
@ -66,8 +63,15 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
return ListTile(
|
||||
onTap: () => changeFucCall(widget.pages[index], index),
|
||||
dense: false,
|
||||
leading: index == currentIndex
|
||||
? Image.asset(
|
||||
'assets/images/live.gif',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 12,
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
widget.pages[index].longTitle!,
|
||||
'第${index + 1}话 ${widget.pages[index].longTitle!}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: index == currentIndex
|
||||
@ -148,6 +152,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: widget.pages.length,
|
||||
itemExtent: 150,
|
||||
itemBuilder: ((context, i) {
|
||||
return Container(
|
||||
width: 150,
|
||||
|
173
lib/pages/bangumi/widgets/bangumu_card_v.dart
Normal file
173
lib/pages/bangumi/widgets/bangumu_card_v.dart
Normal file
@ -0,0 +1,173 @@
|
||||
import 'package:flutter/material.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/badge.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/bangumi/info.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
|
||||
// 视频卡片 - 垂直布局
|
||||
class BangumiCardV extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
final bangumiItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
|
||||
const BangumiCardV({
|
||||
Key? key,
|
||||
required this.bangumiItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(bangumiItem.mediaId);
|
||||
return Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: StyleString.mdRadius,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
child: GestureDetector(
|
||||
// onLongPress: () {
|
||||
// if (longPress != null) {
|
||||
// longPress!();
|
||||
// }
|
||||
// },
|
||||
// onLongPressEnd: (details) {
|
||||
// if (longPressEnd != null) {
|
||||
// longPressEnd!();
|
||||
// }
|
||||
// },
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
int seasonId = bangumiItem.seasonId;
|
||||
SmartDialog.showLoading(msg: '获取中...');
|
||||
var res = await SearchHttp.bangumiInfo(seasonId: seasonId);
|
||||
SmartDialog.dismiss().then((value) {
|
||||
if (res['status']) {
|
||||
if (res['data'].episodes.isEmpty) {
|
||||
SmartDialog.showToast('资源加载失败');
|
||||
return;
|
||||
}
|
||||
EpisodeItem episode = res['data'].episodes.first;
|
||||
String bvid = episode.bvid!;
|
||||
int cid = episode.cid!;
|
||||
String pic = episode.cover!;
|
||||
String heroTag = Utils.makeHeroTag(cid);
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid&seasonId=$seasonId',
|
||||
arguments: {
|
||||
'pic': pic,
|
||||
'heroTag': heroTag,
|
||||
'videoType': SearchType.media_bangumi,
|
||||
'bangumiItem': res['data'],
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: StyleString.imgRadius,
|
||||
topRight: StyleString.imgRadius,
|
||||
bottomLeft: StyleString.imgRadius,
|
||||
bottomRight: StyleString.imgRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.65,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: bangumiItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
if (bangumiItem.badge != null)
|
||||
pBadge(bangumiItem.badge, context, 6, 6, null, null),
|
||||
if (bangumiItem.order != null)
|
||||
pBadge(bangumiItem.order, context, null, null, 6, 6,
|
||||
type: 'gray'),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
BangumiContent(bangumiItem: bangumiItem)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BangumiContent extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
final bangumiItem;
|
||||
const BangumiContent({Key? key, required this.bangumiItem}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
// 多列
|
||||
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
|
||||
// 单列
|
||||
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
bangumiItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
],
|
||||
),
|
||||
if (bangumiItem.indexShow != null)
|
||||
Text(
|
||||
bangumiItem.indexShow,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (bangumiItem.progress != null)
|
||||
Text(
|
||||
bangumiItem.progress,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -29,7 +29,8 @@ class DynamicsPage extends StatefulWidget {
|
||||
class _DynamicsPageState extends State<DynamicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
Future? _futureBuilderFuture;
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _futureBuilderFutureUp;
|
||||
bool _isLoadingMore = false;
|
||||
Box user = GStrorage.user;
|
||||
|
||||
@ -40,6 +41,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
||||
ScrollController scrollController = _dynamicsController.scrollController;
|
||||
StreamController<bool> mainStream =
|
||||
Get.find<MainController>().bottomBarStream;
|
||||
@ -175,50 +177,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
icon: const Icon(Icons.history, size: 21),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 10,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: user.get(UserBoxKey.userLogin) ?? false
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => const SizedBox(
|
||||
height: 450,
|
||||
child: MinePage(),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
);
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
width: 30,
|
||||
height: 30,
|
||||
src: user.get(UserBoxKey.userFace),
|
||||
),
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => const SizedBox(
|
||||
height: 450,
|
||||
child: MinePage(),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.person, size: 22),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -229,7 +187,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
controller: _dynamicsController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _dynamicsController.queryFollowUp(),
|
||||
future: _futureBuilderFutureUp,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data;
|
||||
|
@ -40,7 +40,7 @@ class _UpPanelState extends State<UpPanel> {
|
||||
1,
|
||||
UpItem(
|
||||
face: user.get(UserBoxKey.userFace),
|
||||
uname: '我的',
|
||||
uname: '我',
|
||||
mid: user.get(UserBoxKey.userMid),
|
||||
),
|
||||
);
|
||||
|
@ -13,6 +13,13 @@ class FavPage extends StatefulWidget {
|
||||
|
||||
class _FavPageState extends State<FavPage> {
|
||||
final FavController _favController = Get.put(FavController());
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _favController.queryFavFolder();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -26,7 +33,7 @@ class _FavPageState extends State<FavPage> {
|
||||
),
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _favController.queryFavFolder(),
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
|
@ -1,77 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/index.dart';
|
||||
import 'package:pilipala/pages/bangumi/index.dart';
|
||||
import 'package:pilipala/pages/hot/index.dart';
|
||||
import 'package:pilipala/pages/live/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/index.dart';
|
||||
import 'package:pilipala/models/common/tab_type.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
bool flag = false;
|
||||
List tabs = [
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '直播',
|
||||
'type': 'live'
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.thumb_up_off_alt_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '推荐',
|
||||
'type': 'rcm'
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.whatshot_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '热门',
|
||||
'type': 'hot'
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.play_circle_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '番剧',
|
||||
'type': 'bangumi'
|
||||
},
|
||||
];
|
||||
late List tabs;
|
||||
int initialIndex = 1;
|
||||
late TabController tabController;
|
||||
List ctrList = [
|
||||
Get.find<LiveController>,
|
||||
Get.find<RcmdController>,
|
||||
Get.find<HotController>,
|
||||
Get.find<BangumiController>,
|
||||
];
|
||||
late List tabsCtrList;
|
||||
late List<Widget> tabsPageList;
|
||||
RxString defaultSearch = '输入关键词搜索'.obs;
|
||||
Box user = GStrorage.user;
|
||||
RxBool userLogin = false.obs;
|
||||
RxString userFace = ''.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
searchDefault();
|
||||
userLogin.value = user.get(UserBoxKey.userLogin) ?? false;
|
||||
userFace.value = user.get(UserBoxKey.userFace) ?? '';
|
||||
|
||||
// 进行tabs配置
|
||||
tabs = tabsConfig;
|
||||
tabsCtrList = tabsConfig.map((e) => e['ctr']).toList();
|
||||
tabsPageList = tabsConfig.map<Widget>((e) => e['page']).toList();
|
||||
|
||||
tabController = TabController(
|
||||
initialIndex: initialIndex,
|
||||
length: tabs.length,
|
||||
vsync: this,
|
||||
);
|
||||
searchDefault();
|
||||
}
|
||||
|
||||
void onRefresh() {
|
||||
int index = tabController.index;
|
||||
var ctr = ctrList[index];
|
||||
var ctr = tabsCtrList[index];
|
||||
ctr().onRefresh();
|
||||
}
|
||||
|
||||
void animateToTop() {
|
||||
int index = tabController.index;
|
||||
var ctr = ctrList[index];
|
||||
var ctr = tabsCtrList[index];
|
||||
ctr().animateToTop();
|
||||
}
|
||||
|
||||
@ -81,4 +55,10 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
defaultSearch.value = res.data['data']['name'];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新登录状态
|
||||
void updateLoginStatus(val) {
|
||||
userLogin.value = val ?? false;
|
||||
userFace.value = user.get(UserBoxKey.userFace) ?? '';
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/pages/bangumi/index.dart';
|
||||
import 'package:pilipala/pages/hot/index.dart';
|
||||
@ -10,7 +8,6 @@ import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/pages/mine/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import './controller.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
@ -29,6 +26,19 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
showUserBottonSheet() {
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => const SizedBox(
|
||||
height: 450,
|
||||
child: MinePage(),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
@ -38,73 +48,68 @@ class _HomePageState extends State<HomePage>
|
||||
appBar: AppBar(toolbarHeight: 0, elevation: 0),
|
||||
body: Column(
|
||||
children: [
|
||||
CustomAppBar(stream: stream, ctr: _homeController),
|
||||
Container(
|
||||
CustomAppBar(
|
||||
stream: stream,
|
||||
ctr: _homeController,
|
||||
callback: showUserBottonSheet,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4),
|
||||
child: Stack(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
|
||||
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: TabBar(
|
||||
controller: _homeController.tabController,
|
||||
tabs: [
|
||||
for (var i in _homeController.tabs)
|
||||
// Tab(text: i['label'])
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 0, vertical: 11),
|
||||
child: Row(
|
||||
children: [
|
||||
i['icon'],
|
||||
const SizedBox(width: 4),
|
||||
Text(i['label'])
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
isScrollable: true,
|
||||
indicatorWeight: 0,
|
||||
indicatorPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 5),
|
||||
indicator: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
.withOpacity(0.8),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
labelColor: Theme.of(context).colorScheme.primary,
|
||||
labelStyle: const TextStyle(fontSize: 13),
|
||||
dividerColor: Colors.transparent,
|
||||
unselectedLabelColor:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
onTap: (value) =>
|
||||
{feedBack(), _homeController.initialIndex = value},
|
||||
),
|
||||
),
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
|
||||
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _homeController.tabController,
|
||||
tabs: [
|
||||
for (var i in _homeController.tabs) Tab(text: i['label'])
|
||||
],
|
||||
isScrollable: true,
|
||||
indicatorWeight: 0,
|
||||
indicatorPadding: const EdgeInsets.only(
|
||||
top: 37, left: 18, right: 18, bottom: 6),
|
||||
indicatorColor: Colors.black,
|
||||
indicator: BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
center: Alignment.centerLeft,
|
||||
radius: 20.00,
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.primary,
|
||||
Theme.of(context).colorScheme.background,
|
||||
],
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
topRight: Radius.circular(2),
|
||||
bottomLeft: Radius.circular(2),
|
||||
bottomRight: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
],
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
labelColor: Theme.of(context).colorScheme.primary,
|
||||
labelStyle:
|
||||
const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
|
||||
dividerColor: Colors.transparent,
|
||||
unselectedLabelStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.outline,
|
||||
onTap: (value) {
|
||||
feedBack();
|
||||
if (_homeController.initialIndex == value) {
|
||||
_homeController.tabsCtrList[value]().animateToTop();
|
||||
}
|
||||
_homeController.initialIndex = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _homeController.tabController,
|
||||
children: const [
|
||||
LivePage(),
|
||||
RcmdPage(),
|
||||
HotPage(),
|
||||
BangumiPage(),
|
||||
],
|
||||
children: _homeController.tabsPageList,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -116,13 +121,15 @@ class _HomePageState extends State<HomePage>
|
||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final double height;
|
||||
final Stream<bool>? stream;
|
||||
final ctr;
|
||||
final HomeController? ctr;
|
||||
final Function? callback;
|
||||
|
||||
const CustomAppBar({
|
||||
super.key,
|
||||
this.height = kToolbarHeight,
|
||||
this.stream,
|
||||
this.ctr,
|
||||
this.callback,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -130,8 +137,6 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Box user = GStrorage.user;
|
||||
|
||||
return StreamBuilder(
|
||||
stream: stream,
|
||||
initialData: true,
|
||||
@ -144,111 +149,100 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
child: AnimatedContainer(
|
||||
curve: Curves.linear,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
height: snapshot.data ? 94 : MediaQuery.of(context).padding.top,
|
||||
height: snapshot.data
|
||||
? MediaQuery.of(context).padding.top + 42
|
||||
: MediaQuery.of(context).padding.top,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: 4,
|
||||
bottom: 0,
|
||||
top: MediaQuery.of(context).padding.top,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
'PLPL',
|
||||
style: TextStyle(
|
||||
height: 2.8,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Jura-Bold',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Get.toNamed('/search', parameters: {
|
||||
'hintText': ctr.defaultSearch.value
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: 250,
|
||||
height: 45,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
padding: const EdgeInsets.only(left: 12, right: 22),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(25)),
|
||||
color:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_outlined,
|
||||
size: 23,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => Text(
|
||||
ctr.defaultSearch.value,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline),
|
||||
),
|
||||
child: Row(children: [
|
||||
Image.asset(
|
||||
'assets/images/logo/logo_android_2.png',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Get.toNamed('/search',
|
||||
parameters: {'hintText': ctr!.defaultSearch.value});
|
||||
},
|
||||
child: Container(
|
||||
width: 250,
|
||||
height: 40,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
padding: const EdgeInsets.only(left: 12, right: 22),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(25)),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_outlined,
|
||||
size: 21,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => Text(
|
||||
ctr!.defaultSearch.value,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (user.get(UserBoxKey.userLogin) ?? false) ...[
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => const SizedBox(
|
||||
height: 450,
|
||||
child: MinePage(),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Obx(
|
||||
() => ctr!.userLogin.value
|
||||
? GestureDetector(
|
||||
onTap: () => callback!(),
|
||||
child: NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
width: 38,
|
||||
height: 38,
|
||||
src: ctr!.userFace.value,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
);
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
width: 34,
|
||||
height: 34,
|
||||
src: user.get(UserBoxKey.userFace),
|
||||
),
|
||||
)
|
||||
] else ...[
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => const SizedBox(
|
||||
height: 450,
|
||||
child: MinePage(),
|
||||
)
|
||||
: SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding:
|
||||
MaterialStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface;
|
||||
}),
|
||||
),
|
||||
onPressed: () => callback!(),
|
||||
icon: Icon(
|
||||
Icons.person_rounded,
|
||||
size: 22,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.person, size: 22),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -22,10 +22,12 @@ class LivePage extends StatefulWidget {
|
||||
|
||||
class _LivePageState extends State<LivePage> {
|
||||
final LiveController _liveController = Get.put(LiveController());
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _liveController.queryLiveList('init');
|
||||
ScrollController scrollController = _liveController.scrollController;
|
||||
StreamController<bool> mainStream =
|
||||
Get.find<MainController>().bottomBarStream;
|
||||
@ -52,47 +54,54 @@ class _LivePageState extends State<LivePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
return await _liveController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _liveController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
|
||||
sliver: FutureBuilder(
|
||||
future: _liveController.queryLiveList('init'),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() =>
|
||||
contentGrid(_liveController, _liveController.liveList));
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(StyleString.imgRadius),
|
||||
),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
return await _liveController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _liveController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding: EdgeInsets.zero,
|
||||
sliver: FutureBuilder(
|
||||
future: _liveController.queryLiveList('init'),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(
|
||||
_liveController, _liveController.liveList));
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
);
|
||||
// 缓存数据
|
||||
if (_liveController.liveList.length > 1) {
|
||||
return contentGrid(
|
||||
_liveController, _liveController.liveList);
|
||||
}
|
||||
// 骨架屏
|
||||
else {
|
||||
return contentGrid(_liveController, []);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 缓存数据
|
||||
if (_liveController.liveList.length > 1) {
|
||||
return contentGrid(
|
||||
_liveController, _liveController.liveList);
|
||||
}
|
||||
// 骨架屏
|
||||
else {
|
||||
return contentGrid(_liveController, []);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
],
|
||||
const LoadingMore()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -31,6 +31,9 @@ class LiveRoomController extends GetxController {
|
||||
liveItem = Get.arguments['liveItem'];
|
||||
heroTag = Get.arguments['heroTag'] ?? '';
|
||||
if (liveItem.pic != null && liveItem.pic != '') {
|
||||
cover = liveItem.pic;
|
||||
}
|
||||
if (liveItem.cover != null && liveItem.cover != '') {
|
||||
cover = liveItem.cover;
|
||||
}
|
||||
}
|
||||
|
@ -112,68 +112,67 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_liveRoomController.liveItem.watchedShow != null)
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1)),
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1)),
|
||||
),
|
||||
),
|
||||
child: Row(children: <Widget>[
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.subtitles_outlined,
|
||||
size: 21,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(children: <Widget>[
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.subtitles_outlined,
|
||||
size: 21,
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.hd_outlined,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.hd_outlined,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () => _liveRoomController
|
||||
.setVolumn(plPlayerController!.volume.value),
|
||||
icon: Obx(() => Icon(
|
||||
_liveRoomController.volumeOff.value
|
||||
? Icons.volume_off_outlined
|
||||
: Icons.volume_up_outlined,
|
||||
size: 21,
|
||||
)),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () => {},
|
||||
// plPlayerController!.goToFullscreen(context),
|
||||
icon: const Icon(
|
||||
Icons.fullscreen,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () => _liveRoomController
|
||||
.setVolumn(plPlayerController!.volume.value),
|
||||
icon: Obx(() => Icon(
|
||||
_liveRoomController.volumeOff.value
|
||||
? Icons.volume_off_outlined
|
||||
: Icons.volume_up_outlined,
|
||||
size: 21,
|
||||
)),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () => {},
|
||||
// plPlayerController!.goToFullscreen(context),
|
||||
icon: const Icon(
|
||||
Icons.fullscreen,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -143,7 +143,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
// type: BottomNavigationBarType.shifting,
|
||||
selectedItemColor: Theme.of(context).colorScheme.primary,
|
||||
unselectedItemColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
Theme.of(context).colorScheme.outline.withOpacity(0.5),
|
||||
selectedFontSize: 12.4,
|
||||
onTap: (value) => setIndex(value),
|
||||
items: [
|
||||
|
@ -14,13 +14,22 @@ class MediaPage extends StatefulWidget {
|
||||
|
||||
class _MediaPageState extends State<MediaPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late MediaController mediaController;
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
mediaController = Get.put(MediaController());
|
||||
_futureBuilderFuture = mediaController.queryFavFolder();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final MediaController mediaController = Get.put(MediaController());
|
||||
Color primary = Theme.of(context).colorScheme.primary;
|
||||
return Scaffold(
|
||||
appBar: AppBar(toolbarHeight: 30),
|
||||
@ -107,7 +116,7 @@ class _MediaPageState extends State<MediaPage>
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
onPressed: () => mediaController.queryFavFolder(),
|
||||
onPressed: () => _futureBuilderFuture,
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
size: 20,
|
||||
@ -119,7 +128,7 @@ class _MediaPageState extends State<MediaPage>
|
||||
width: double.infinity,
|
||||
height: 170,
|
||||
child: FutureBuilder(
|
||||
future: mediaController.queryFavFolder(),
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/common/theme_type.dart';
|
||||
import 'package:pilipala/models/user/info.dart';
|
||||
import 'package:pilipala/models/user/stat.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
@ -10,9 +12,11 @@ class MineController extends GetxController {
|
||||
Rx<UserInfoData> userInfo = UserInfoData().obs;
|
||||
// 用户状态 动态、关注、粉丝
|
||||
Rx<UserStat> userStat = UserStat().obs;
|
||||
Box user = GStrorage.user;
|
||||
RxBool userLogin = false.obs;
|
||||
Box user = GStrorage.user;
|
||||
Box setting = GStrorage.setting;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Rx<ThemeType> themeType = ThemeType.system.obs;
|
||||
|
||||
@override
|
||||
onInit() {
|
||||
@ -21,6 +25,9 @@ class MineController extends GetxController {
|
||||
if (userInfoCache.get('userInfoCache') != null) {
|
||||
userInfo.value = userInfoCache.get('userInfoCache');
|
||||
}
|
||||
|
||||
themeType.value = ThemeType.values[setting.get(SettingBoxKey.themeMode,
|
||||
defaultValue: ThemeType.system.code)];
|
||||
}
|
||||
|
||||
onLogin() async {
|
||||
@ -90,4 +97,31 @@ class MineController extends GetxController {
|
||||
userLogin.value = false;
|
||||
// Get.find<MainController>().resetLast();
|
||||
}
|
||||
|
||||
onChangeTheme() {
|
||||
Brightness currentBrightness =
|
||||
MediaQuery.of(Get.context!).platformBrightness;
|
||||
ThemeType currentTheme = themeType.value;
|
||||
switch (currentTheme) {
|
||||
case ThemeType.dark:
|
||||
setting.put(SettingBoxKey.themeMode, ThemeType.light.code);
|
||||
themeType.value = ThemeType.light;
|
||||
break;
|
||||
case ThemeType.light:
|
||||
setting.put(SettingBoxKey.themeMode, ThemeType.dark.code);
|
||||
themeType.value = ThemeType.dark;
|
||||
break;
|
||||
case ThemeType.system:
|
||||
// 判断当前的颜色模式
|
||||
if (currentBrightness == Brightness.light) {
|
||||
setting.put(SettingBoxKey.themeMode, ThemeType.dark.code);
|
||||
themeType.value = ThemeType.dark;
|
||||
} else {
|
||||
setting.put(SettingBoxKey.themeMode, ThemeType.light.code);
|
||||
themeType.value = ThemeType.light;
|
||||
}
|
||||
break;
|
||||
}
|
||||
Get.forceAppUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/common/theme_type.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class MinePage extends StatelessWidget {
|
||||
@ -21,16 +23,23 @@ class MinePage extends StatelessWidget {
|
||||
elevation: 0,
|
||||
toolbarHeight: kTextTabBarHeight + 20,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: null,
|
||||
centerTitle: false,
|
||||
title: const Text(
|
||||
'PLPL',
|
||||
style: TextStyle(
|
||||
height: 2.8,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Jura-Bold',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Get.changeThemeMode(ThemeMode.dark);
|
||||
},
|
||||
onPressed: () => mineController.onChangeTheme(),
|
||||
icon: Icon(
|
||||
Get.theme == ThemeData.light()
|
||||
? CupertinoIcons.moon
|
||||
: CupertinoIcons.sun_max,
|
||||
mineController.themeType.value == ThemeType.dark
|
||||
? CupertinoIcons.sun_max
|
||||
: CupertinoIcons.moon,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
@ -93,7 +102,7 @@ class MinePage extends StatelessWidget {
|
||||
src: _mineController.userInfo.value.face,
|
||||
width: 85,
|
||||
height: 85)
|
||||
: Image.asset('assets/images/loading.png'),
|
||||
: Image.asset('assets/images/noface.jpeg'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -23,6 +23,7 @@ class RcmdPage extends StatefulWidget {
|
||||
class _RcmdPageState extends State<RcmdPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final RcmdController _rcmdController = Get.put(RcmdController());
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@ -30,6 +31,7 @@ class _RcmdPageState extends State<RcmdPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _rcmdController.queryRcmdFeed('init');
|
||||
ScrollController scrollController = _rcmdController.scrollController;
|
||||
StreamController<bool> mainStream =
|
||||
Get.find<MainController>().bottomBarStream;
|
||||
@ -57,49 +59,56 @@ class _RcmdPageState extends State<RcmdPage>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
return await _rcmdController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _rcmdController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding: _rcmdController.crossAxisCount == 1
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
|
||||
sliver: FutureBuilder(
|
||||
future: _rcmdController.queryRcmdFeed('init'),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(
|
||||
_rcmdController, _rcmdController.videoList));
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(StyleString.imgRadius),
|
||||
),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
return await _rcmdController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _rcmdController.scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding: _rcmdController.crossAxisCount == 1
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||
sliver: FutureBuilder(
|
||||
future: _rcmdController.queryRcmdFeed('init'),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(() => contentGrid(
|
||||
_rcmdController, _rcmdController.videoList));
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
);
|
||||
// 缓存数据
|
||||
if (_rcmdController.videoList.length > 1) {
|
||||
return contentGrid(
|
||||
_rcmdController, _rcmdController.videoList);
|
||||
}
|
||||
// 骨架屏
|
||||
else {
|
||||
return contentGrid(_rcmdController, []);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 缓存数据
|
||||
if (_rcmdController.videoList.length > 1) {
|
||||
return contentGrid(
|
||||
_rcmdController, _rcmdController.videoList);
|
||||
}
|
||||
// 骨架屏
|
||||
else {
|
||||
return contentGrid(_rcmdController, []);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
],
|
||||
const LoadingMore()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ class SSearchController extends GetxController {
|
||||
}
|
||||
|
||||
void onClear() {
|
||||
if (searchKeyWord.value.isNotEmpty) {
|
||||
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||
controller.value.clear();
|
||||
searchKeyWord.value = '';
|
||||
searchSuggestList.value = [];
|
||||
|
@ -211,13 +211,13 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
||||
return Obx(
|
||||
() => Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(10, 25, 4, 0),
|
||||
padding: const EdgeInsets.fromLTRB(10, 25, 6, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_searchController.historyList.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 1, 2),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 0, 2),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class SearchPanelController extends GetxController {
|
||||
SearchPanelController({this.keyword, this.searchType});
|
||||
@ -21,6 +23,7 @@ class SearchPanelController extends GetxController {
|
||||
} else if (type == 'onRefresh') {
|
||||
resultList.value = result['data'].list;
|
||||
}
|
||||
onPushDetail(keyword, resultList);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -40,4 +43,24 @@ class SearchPanelController extends GetxController {
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
|
||||
void onPushDetail(keyword, resultList) async {
|
||||
// 匹配输入内容,如果是AV、BV号且有结果 直接跳转详情页
|
||||
Map matchRes = IdUtils.matchAvorBv(input: keyword);
|
||||
List matchKeys = matchRes.keys.toList();
|
||||
if (matchKeys.isNotEmpty && searchType == SearchType.video) {
|
||||
String bvid = resultList.first.bvid;
|
||||
int aid = resultList.first.aid;
|
||||
String heroTag = Utils.makeHeroTag(bvid);
|
||||
|
||||
int cid = await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
if (matchKeys.first == 'BV' && matchRes[matchKeys.first] == bvid ||
|
||||
matchKeys.first == 'AV' && matchRes[matchKeys.first] == aid) {
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {'videoItem': resultList.first, 'heroTag': heroTag},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class SearchPanel extends StatefulWidget {
|
||||
|
||||
class _SearchPanelState extends State<SearchPanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late SearchPanelController? _searchPanelController;
|
||||
late SearchPanelController _searchPanelController;
|
||||
|
||||
bool _isLoadingMore = false;
|
||||
late Future _futureBuilderFuture;
|
||||
@ -41,10 +41,9 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
keyword: widget.keyword,
|
||||
searchType: widget.searchType,
|
||||
),
|
||||
tag: widget.searchType!.type + widget.tag!,
|
||||
tag: widget.searchType!.type,
|
||||
);
|
||||
ScrollController scrollController =
|
||||
_searchPanelController!.scrollController;
|
||||
ScrollController scrollController = _searchPanelController.scrollController;
|
||||
scrollController.addListener(() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 100) {
|
||||
@ -55,7 +54,7 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
}
|
||||
}
|
||||
});
|
||||
_futureBuilderFuture = _searchPanelController!.onSearch();
|
||||
_futureBuilderFuture = _searchPanelController.onSearch();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -63,7 +62,7 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
super.build(context);
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _searchPanelController!.onRefresh();
|
||||
await _searchPanelController.onRefresh();
|
||||
},
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
@ -71,7 +70,7 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data;
|
||||
var ctr = _searchPanelController;
|
||||
List list = ctr!.resultList;
|
||||
List list = ctr.resultList;
|
||||
if (data['status']) {
|
||||
return Obx(() {
|
||||
switch (widget.searchType) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
@ -32,6 +33,7 @@ class LiveItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(liveItem.roomid);
|
||||
return Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
@ -40,7 +42,10 @@ class LiveItem extends StatelessWidget {
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
Get.toNamed('/liveRoom?roomid=${liveItem.roomid}',
|
||||
arguments: {'liveItem': liveItem, 'heroTag': heroTag});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
@ -58,7 +63,7 @@ class LiveItem extends StatelessWidget {
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: Utils.makeHeroTag(liveItem.roomid),
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: liveItem.cover,
|
||||
type: 'emote',
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/pages/rcmd/index.dart';
|
||||
import 'package:pilipala/pages/searchPanel/index.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
@ -88,6 +89,7 @@ class _SearchResultPageState extends State<SearchResultPage>
|
||||
tag: SearchType.values[index].type)
|
||||
.animateToTop();
|
||||
}
|
||||
|
||||
_searchResultController!.tabIndex = index;
|
||||
},
|
||||
),
|
||||
|
@ -1,16 +1,21 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
import 'package:pilipala/models/common/theme_type.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/mine/controller.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class SettingController extends GetxController {
|
||||
Box user = GStrorage.user;
|
||||
RxBool userLogin = false.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box setting = GStrorage.setting;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
|
||||
RxBool userLogin = false.obs;
|
||||
RxBool feedBackEnable = false.obs;
|
||||
RxInt picQuality = 10.obs;
|
||||
Rx<ThemeType> themeType = ThemeType.system.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -18,6 +23,10 @@ class SettingController extends GetxController {
|
||||
userLogin.value = user.get(UserBoxKey.userLogin) ?? false;
|
||||
feedBackEnable.value =
|
||||
setting.get(SettingBoxKey.feedBackEnable, defaultValue: false);
|
||||
picQuality.value =
|
||||
setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
||||
themeType.value = ThemeType.values[setting.get(SettingBoxKey.themeMode,
|
||||
defaultValue: ThemeType.system.code)];
|
||||
}
|
||||
|
||||
loginOut() async {
|
||||
@ -25,6 +34,8 @@ class SettingController extends GetxController {
|
||||
await Get.find<MineController>().resetUserInfo();
|
||||
userLogin.value = user.get(UserBoxKey.userLogin) ?? false;
|
||||
userInfoCache.put('userInfoCache', null);
|
||||
HomeController homeCtr = Get.find<HomeController>();
|
||||
homeCtr.updateLoginStatus(false);
|
||||
}
|
||||
|
||||
// 开启关闭震动反馈
|
||||
|
142
lib/pages/setting/play_setting.dart
Normal file
142
lib/pages/setting/play_setting.dart
Normal file
@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/video/play/quality.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import 'widgets/switch_item.dart';
|
||||
|
||||
class PlaySetting extends StatefulWidget {
|
||||
const PlaySetting({super.key});
|
||||
|
||||
@override
|
||||
State<PlaySetting> createState() => _PlaySettingState();
|
||||
}
|
||||
|
||||
class _PlaySettingState extends State<PlaySetting> {
|
||||
Box setting = GStrorage.setting;
|
||||
late dynamic defaultVideoQa;
|
||||
late dynamic defaultAudioQa;
|
||||
late dynamic defaultDecode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
defaultVideoQa = setting.get(SettingBoxKey.defaultVideoQa,
|
||||
defaultValue: VideoQuality.values.last.code);
|
||||
defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultValue: AudioQuality.values.last.code);
|
||||
defaultDecode = setting.get(SettingBoxKey.defaultDecode,
|
||||
defaultValue: VideoDecodeFormats.values.last.code);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
|
||||
TextStyle subTitleStyle = Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
'播放设置',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
const SetSwitchItem(
|
||||
title: '自动播放',
|
||||
subTitle: '进入详情页自动播放',
|
||||
setKey: SettingBoxKey.autoPlayEnable,
|
||||
defaultVal: true,
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '开启硬解',
|
||||
subTitle: '以较低功耗播放视频',
|
||||
setKey: SettingBoxKey.enableHA,
|
||||
defaultVal: true,
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认画质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前画质' + VideoQualityCode.fromCode(defaultVideoQa)!.description!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
initialValue: defaultVideoQa,
|
||||
icon: const Icon(Icons.arrow_forward_rounded, size: 22),
|
||||
onSelected: (item) {
|
||||
defaultVideoQa = item;
|
||||
setting.put(SettingBoxKey.defaultVideoQa, item);
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
for (var i in VideoQuality.values.reversed) ...[
|
||||
PopupMenuItem(
|
||||
value: i.code,
|
||||
child: Text(i.description),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认音质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前音质' + AudioQualityCode.fromCode(defaultAudioQa)!.description!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
initialValue: defaultAudioQa,
|
||||
icon: const Icon(Icons.arrow_forward_rounded, size: 22),
|
||||
onSelected: (item) {
|
||||
defaultAudioQa = item;
|
||||
setting.put(SettingBoxKey.defaultAudioQa, item);
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
for (var i in AudioQuality.values.reversed) ...[
|
||||
PopupMenuItem(
|
||||
value: i.code,
|
||||
child: Text(i.description),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认解码格式', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前解码格式' +
|
||||
VideoDecodeFormatsCode.fromCode(defaultDecode)!.description!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
initialValue: defaultDecode,
|
||||
icon: const Icon(Icons.arrow_forward_rounded, size: 22),
|
||||
onSelected: (item) {
|
||||
defaultDecode = item;
|
||||
setting.put(SettingBoxKey.defaultDecode, item);
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
for (var i in VideoDecodeFormats.values) ...[
|
||||
PopupMenuItem(
|
||||
value: i.code,
|
||||
child: Text(i.description),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
191
lib/pages/setting/style_setting.dart
Normal file
191
lib/pages/setting/style_setting.dart
Normal file
@ -0,0 +1,191 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/common/theme_type.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class StyleSetting extends StatefulWidget {
|
||||
const StyleSetting({super.key});
|
||||
|
||||
@override
|
||||
State<StyleSetting> createState() => _StyleSettingState();
|
||||
}
|
||||
|
||||
class _StyleSettingState extends State<StyleSetting> {
|
||||
final SettingController settingController = Get.put(SettingController());
|
||||
Box setting = GStrorage.setting;
|
||||
late int picQuality;
|
||||
late ThemeType _tempThemeValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
||||
_tempThemeValue = settingController.themeType.value;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
|
||||
TextStyle subTitleStyle = Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
'外观设置',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
Obx(
|
||||
() => ListTile(
|
||||
enableFeedback: true,
|
||||
onTap: () => settingController.onOpenFeedBack(),
|
||||
title: const Text('震动反馈'),
|
||||
subtitle: Text('请确定手机设置中已开启震动反馈', style: subTitleStyle),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == MaterialState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null; // All other states will use the default thumbIcon.
|
||||
}),
|
||||
value: settingController.feedBackEnable.value,
|
||||
onChanged: (value) => settingController.onOpenFeedBack()),
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, StateSetter setState) {
|
||||
final SettingController settingController =
|
||||
Get.put(SettingController());
|
||||
return AlertDialog(
|
||||
title: const Text('图片质量'),
|
||||
contentPadding: const EdgeInsets.only(
|
||||
top: 20, left: 8, right: 8, bottom: 8),
|
||||
content: SizedBox(
|
||||
height: 40,
|
||||
child: Slider(
|
||||
value: picQuality.toDouble(),
|
||||
min: 10,
|
||||
max: 100,
|
||||
divisions: 9,
|
||||
label: '$picQuality%',
|
||||
onChanged: (double val) {
|
||||
picQuality = val.toInt();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text('取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline))),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setting.put(
|
||||
SettingBoxKey.defaultPicQa, picQuality);
|
||||
Get.back();
|
||||
settingController.picQuality.value = picQuality;
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
title: Text('图片质量', style: titleStyle),
|
||||
subtitle: Text('选择合适的图片清晰度,上限100%', style: subTitleStyle),
|
||||
trailing: Obx(
|
||||
() => Text(
|
||||
'${settingController.picQuality.value}%',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('主题模式'),
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||
content: StatefulBuilder(
|
||||
builder: (context, StateSetter setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (var i in ThemeType.values) ...[
|
||||
RadioListTile(
|
||||
value: i,
|
||||
title: Text(i.description, style: titleStyle),
|
||||
groupValue: _tempThemeValue,
|
||||
onChanged: (ThemeType? value) {
|
||||
setState(() {
|
||||
_tempThemeValue = i;
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
settingController.themeType.value = _tempThemeValue;
|
||||
setting.put(
|
||||
SettingBoxKey.themeMode, _tempThemeValue.code);
|
||||
Get.forceAppUpdate();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定'))
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
title: Text('主题模式', style: titleStyle),
|
||||
subtitle: Obx(() => Text(
|
||||
'当前模式:${settingController.themeType.value.description}',
|
||||
style: subTitleStyle)),
|
||||
trailing: const Icon(Icons.arrow_right_alt_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -14,32 +14,30 @@ class SettingPage extends StatelessWidget {
|
||||
final SettingController settingController = Get.put(SettingController());
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('设置'),
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
'设置',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Obx(
|
||||
() => ListTile(
|
||||
enableFeedback: true,
|
||||
onTap: () => settingController.onOpenFeedBack(),
|
||||
title: const Text('震动反馈'),
|
||||
subtitle: Text('请确定手机设置中已开启震动反馈', style: subTitleStyle),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == MaterialState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null; // All other states will use the default thumbIcon.
|
||||
}),
|
||||
value: settingController.feedBackEnable.value,
|
||||
onChanged: (value) => settingController.onOpenFeedBack()),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => Get.toNamed('/playSetting'),
|
||||
dense: false,
|
||||
title: const Text('播放设置'),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => Get.toNamed('/styleSetting'),
|
||||
dense: false,
|
||||
title: const Text('外观设置'),
|
||||
),
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// dense: false,
|
||||
// title: const Text('其他设置'),
|
||||
// ),
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: settingController.userLogin.value,
|
||||
|
120
lib/pages/setting/widgets/select_item.dart
Normal file
120
lib/pages/setting/widgets/select_item.dart
Normal file
@ -0,0 +1,120 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/video/play/quality.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class SetSelectItem extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? subTitle;
|
||||
final String? setKey;
|
||||
const SetSelectItem({
|
||||
this.title,
|
||||
this.subTitle,
|
||||
this.setKey,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SetSelectItem> createState() => _SetSelectItemState();
|
||||
}
|
||||
|
||||
class _SetSelectItemState extends State<SetSelectItem> {
|
||||
Box Setting = GStrorage.setting;
|
||||
late var currentVal;
|
||||
late int currentIndex;
|
||||
late List menus;
|
||||
late List<PopupMenuEntry> popMenuItems;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
late String defaultVal;
|
||||
switch (widget.setKey) {
|
||||
case 'defaultVideoQa':
|
||||
defaultVal = VideoQuality.values.last.description;
|
||||
List<VideoQuality> list = menus = VideoQuality.values.reversed.toList();
|
||||
currentVal = Setting.get(widget.setKey, defaultValue: defaultVal);
|
||||
currentIndex =
|
||||
list.firstWhere((i) => i.description == currentVal).index;
|
||||
|
||||
popMenuItems = [
|
||||
for (var i in list) ...[
|
||||
PopupMenuItem(
|
||||
value: i.code,
|
||||
child: Text(i.description),
|
||||
)
|
||||
]
|
||||
];
|
||||
|
||||
break;
|
||||
case 'defaultAudioQa':
|
||||
defaultVal = AudioQuality.values.last.description;
|
||||
List<AudioQuality> list = menus = AudioQuality.values.reversed.toList();
|
||||
currentVal = Setting.get(widget.setKey, defaultValue: defaultVal);
|
||||
currentIndex =
|
||||
list.firstWhere((i) => i.description == currentVal).index;
|
||||
|
||||
popMenuItems = [
|
||||
for (var i in list) ...[
|
||||
PopupMenuItem(
|
||||
value: i.index,
|
||||
child: Text(i.description),
|
||||
),
|
||||
]
|
||||
];
|
||||
break;
|
||||
case 'defaultDecode':
|
||||
defaultVal = VideoDecodeFormats.values[0].description;
|
||||
currentVal = Setting.get(widget.setKey, defaultValue: defaultVal);
|
||||
List<VideoDecodeFormats> list = menus = VideoDecodeFormats.values;
|
||||
|
||||
currentIndex =
|
||||
list.firstWhere((i) => i.description == currentVal).index;
|
||||
|
||||
popMenuItems = [
|
||||
for (var i in list) ...[
|
||||
PopupMenuItem(
|
||||
value: i.index,
|
||||
child: Text(i.description),
|
||||
),
|
||||
]
|
||||
];
|
||||
break;
|
||||
case 'defaultVideoSpeed':
|
||||
defaultVal = '1.0';
|
||||
currentVal = Setting.get(widget.setKey, defaultValue: defaultVal);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextStyle subTitleStyle = Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline);
|
||||
return ListTile(
|
||||
onTap: () {},
|
||||
dense: false,
|
||||
title: Text(widget.title!),
|
||||
subtitle: Text(
|
||||
'当前${widget.title!} $currentVal',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
initialValue: currentIndex,
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward_rounded,
|
||||
size: 22,
|
||||
),
|
||||
onSelected: (item) {
|
||||
currentVal = menus.firstWhere((e) => e.code == item).first;
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry>[...popMenuItems],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
69
lib/pages/setting/widgets/switch_item.dart
Normal file
69
lib/pages/setting/widgets/switch_item.dart
Normal file
@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class SetSwitchItem extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? subTitle;
|
||||
final String? setKey;
|
||||
final bool? defaultVal;
|
||||
|
||||
const SetSwitchItem({
|
||||
this.title,
|
||||
this.subTitle,
|
||||
this.setKey,
|
||||
this.defaultVal,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SetSwitchItem> createState() => _SetSwitchItemState();
|
||||
}
|
||||
|
||||
class _SetSwitchItemState extends State<SetSwitchItem> {
|
||||
// ignore: non_constant_identifier_names
|
||||
Box Setting = GStrorage.setting;
|
||||
late bool val;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
val = Setting.get(widget.setKey, defaultValue: widget.defaultVal ?? false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
|
||||
TextStyle subTitleStyle = Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline);
|
||||
return ListTile(
|
||||
enableFeedback: true,
|
||||
onTap: () {
|
||||
Setting.put(widget.setKey, !val);
|
||||
},
|
||||
title: Text(widget.title!, style: titleStyle),
|
||||
subtitle: widget.subTitle != null
|
||||
? Text(widget.subTitle!, style: subTitleStyle)
|
||||
: null,
|
||||
trailing: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.isNotEmpty && states.first == MaterialState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null; // All other states will use the default thumbIcon.
|
||||
}),
|
||||
value: val,
|
||||
onChanged: (value) {
|
||||
val = value;
|
||||
Setting.put(widget.setKey, value);
|
||||
setState(() {});
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -13,36 +13,47 @@ import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class VideoDetailController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
int tabInitialIndex = 0;
|
||||
TabController? tabCtr;
|
||||
// tabs
|
||||
RxList<String> tabs = <String>['简介', '评论'].obs;
|
||||
|
||||
// 视频aid
|
||||
/// 路由传参
|
||||
String bvid = Get.parameters['bvid']!;
|
||||
int cid = int.parse(Get.parameters['cid']!);
|
||||
// 视频类型 默认投稿视频
|
||||
SearchType videoType = SearchType.video;
|
||||
|
||||
late PlayUrlModel data;
|
||||
// 当前画质
|
||||
late VideoQuality currentVideoQa;
|
||||
// 当前音质
|
||||
late AudioQuality currentAudioQa;
|
||||
|
||||
// 是否预渲染 骨架屏
|
||||
bool preRender = false;
|
||||
|
||||
// 视频详情 上个页面传入
|
||||
String heroTag = Get.arguments['heroTag'];
|
||||
// 视频详情
|
||||
Map videoItem = {};
|
||||
// 视频类型 默认投稿视频
|
||||
SearchType videoType = Get.arguments['videoType'] ?? SearchType.video;
|
||||
|
||||
/// tabs相关配置
|
||||
int tabInitialIndex = 0;
|
||||
late TabController tabCtr;
|
||||
RxList<String> tabs = <String>['简介', '评论'].obs;
|
||||
|
||||
// 请求返回的视频信息
|
||||
late PlayUrlModel data;
|
||||
// 请求状态
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
String heroTag = '';
|
||||
/// 播放器配置 画质 音质 解码格式
|
||||
late VideoQuality currentVideoQa;
|
||||
late AudioQuality currentAudioQa;
|
||||
late VideoDecodeFormats currentDecodeFormats;
|
||||
// PlPlayerController plPlayerController = PlPlayerController();
|
||||
// 是否开始自动播放 存在多p的情况下,第二p需要为true
|
||||
RxBool autoPlay = true.obs;
|
||||
// 视频资源是否有效
|
||||
RxBool isEffective = true.obs;
|
||||
// 封面图的展示
|
||||
RxBool isShowCover = true.obs;
|
||||
// 硬解
|
||||
RxBool enableHA = true.obs;
|
||||
|
||||
/// 本地存储
|
||||
Box user = GStrorage.user;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box setting = GStrorage.setting;
|
||||
|
||||
int oid = 0;
|
||||
// 评论id 请求楼中楼评论使用
|
||||
@ -52,15 +63,7 @@ class VideoDetailController extends GetxController
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
Timer? timer;
|
||||
RxString bgCover = ''.obs;
|
||||
Box user = GStrorage.user;
|
||||
Box localCache = GStrorage.localCache;
|
||||
PlPlayerController plPlayerController = PlPlayerController.getInstance();
|
||||
// 是否开始自动播放 存在多p的情况下,第二p需要为true
|
||||
RxBool autoPlay = true.obs;
|
||||
// 视频资源是否有效
|
||||
RxBool isEffective = true.obs;
|
||||
// 封面图的展示
|
||||
RxBool isShowCover = true.obs;
|
||||
|
||||
late VideoItem firstVideo;
|
||||
late String videoUrl;
|
||||
@ -70,24 +73,23 @@ class VideoDetailController extends GetxController
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (Get.arguments.isNotEmpty) {
|
||||
if (Get.arguments.containsKey('videoItem')) {
|
||||
preRender = true;
|
||||
var args = Get.arguments['videoItem'];
|
||||
Map argMap = Get.arguments;
|
||||
var keys = argMap.keys.toList();
|
||||
if (keys.isNotEmpty) {
|
||||
if (keys.contains('videoItem')) {
|
||||
var args = argMap['videoItem'];
|
||||
if (args.pic != null && args.pic != '') {
|
||||
videoItem['pic'] = args.pic;
|
||||
bgCover.value = args.pic;
|
||||
}
|
||||
}
|
||||
if (Get.arguments.containsKey('pic')) {
|
||||
videoItem['pic'] = Get.arguments['pic'];
|
||||
bgCover.value = Get.arguments['pic'];
|
||||
if (keys.contains('pic')) {
|
||||
videoItem['pic'] = argMap['pic'];
|
||||
}
|
||||
heroTag = Get.arguments['heroTag'];
|
||||
videoType = Get.arguments['videoType'] ?? SearchType.video;
|
||||
}
|
||||
tabCtr = TabController(length: 2, vsync: this);
|
||||
// queryVideoUrl();
|
||||
autoPlay.value =
|
||||
setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true);
|
||||
enableHA.value = setting.get(SettingBoxKey.enableHA, defaultValue: true);
|
||||
}
|
||||
|
||||
showReplyReplyPanel() {
|
||||
@ -120,8 +122,15 @@ class VideoDetailController extends GetxController
|
||||
/// 暂不匹配解码规则
|
||||
|
||||
/// 根据currentVideoQa 重新设置videoUrl
|
||||
firstVideo =
|
||||
data.dash!.video!.firstWhere((i) => i.id == currentVideoQa.code);
|
||||
// firstVideo =
|
||||
// data.dash!.video!.firstWhere((i) => i.id == currentVideoQa.code);
|
||||
// videoUrl = firstVideo.baseUrl!;
|
||||
|
||||
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
|
||||
List<VideoItem> videoList =
|
||||
data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList();
|
||||
firstVideo = videoList
|
||||
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
|
||||
videoUrl = firstVideo.baseUrl!;
|
||||
|
||||
/// 根据currentAudioQa 重新设置audioUrl
|
||||
@ -133,6 +142,7 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
|
||||
Future playerInit({video, audio, seekToTime, duration}) async {
|
||||
print('data.timeLength:${data.timeLength}');
|
||||
await plPlayerController.setDataSource(
|
||||
DataSource(
|
||||
videoSource: video ?? videoUrl,
|
||||
@ -145,7 +155,7 @@ class VideoDetailController extends GetxController
|
||||
},
|
||||
),
|
||||
// 硬解
|
||||
enableHA: true,
|
||||
enableHA: enableHA.value,
|
||||
autoplay: autoPlay.value,
|
||||
seekTo: seekToTime ?? defaultST,
|
||||
duration: duration ?? Duration(milliseconds: data.timeLength ?? 0),
|
||||
@ -170,14 +180,73 @@ class VideoDetailController extends GetxController
|
||||
data = result['data'];
|
||||
|
||||
/// 优先顺序 省流模式 -> 设置中指定质量 -> 当前可选的最高质量
|
||||
firstVideo = data.dash!.video!.first;
|
||||
videoUrl = firstVideo.baseUrl!;
|
||||
//
|
||||
currentVideoQa = VideoQualityCode.fromCode(firstVideo.id!)!;
|
||||
// firstVideo = data.dash!.video!.first;
|
||||
// videoUrl = firstVideo.baseUrl!;
|
||||
// //
|
||||
// currentVideoQa = VideoQualityCode.fromCode(firstVideo.id!)!;
|
||||
|
||||
// /// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
||||
// AudioItem firstAudio =
|
||||
// data.dash!.audio!.isNotEmpty ? data.dash!.audio!.first : AudioItem();
|
||||
// audioUrl = firstAudio.baseUrl ?? '';
|
||||
|
||||
List<VideoItem> allVideosList = data.dash!.video!;
|
||||
|
||||
try {
|
||||
// 当前可播放的最高质量视频
|
||||
int currentHighVideoQa = allVideosList.first.quality!.code;
|
||||
//
|
||||
int cacheVideoQa = setting.get(SettingBoxKey.defaultVideoQa,
|
||||
defaultValue: currentHighVideoQa);
|
||||
int resVideoQa = currentHighVideoQa;
|
||||
if (cacheVideoQa <= currentHighVideoQa) {
|
||||
List<int> numbers = data.acceptQuality!
|
||||
.where((e) => e <= currentHighVideoQa)
|
||||
.toList();
|
||||
resVideoQa = Utils.findClosestNumber(cacheVideoQa, numbers);
|
||||
}
|
||||
currentVideoQa = VideoQualityCode.fromCode(resVideoQa)!;
|
||||
|
||||
/// 取出符合当前画质的videoList
|
||||
List<VideoItem> videosList =
|
||||
allVideosList.where((e) => e.quality!.code == resVideoQa).toList();
|
||||
|
||||
/// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式
|
||||
List<FormatItem> supportFormats = data.supportFormats!;
|
||||
// 根据画质选编码格式
|
||||
List supportDecodeFormats =
|
||||
supportFormats.firstWhere((e) => e.quality == resVideoQa).codecs!;
|
||||
|
||||
try {
|
||||
currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get(
|
||||
SettingBoxKey.defaultDecode,
|
||||
defaultValue: supportDecodeFormats.first))!;
|
||||
} catch (_) {}
|
||||
|
||||
/// 取出符合当前解码格式的videoItem
|
||||
firstVideo = videosList
|
||||
.firstWhere((e) => e.codecs!.startsWith(currentDecodeFormats.code));
|
||||
videoUrl = firstVideo.baseUrl!;
|
||||
} catch (_) {}
|
||||
|
||||
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
||||
AudioItem firstAudio =
|
||||
data.dash!.audio!.isNotEmpty ? data.dash!.audio!.first : AudioItem();
|
||||
late AudioItem firstAudio;
|
||||
List audiosList = data.dash!.audio!;
|
||||
try {
|
||||
if (audiosList.isNotEmpty) {
|
||||
firstAudio = audiosList.first;
|
||||
int resultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultValue: firstAudio.id);
|
||||
// 选择最接近的那个音轨
|
||||
firstAudio = audiosList.firstWhere(
|
||||
(e) => e.id == resultAudioQa,
|
||||
orElse: () => AudioItem(),
|
||||
);
|
||||
} else {
|
||||
firstAudio = AudioItem();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
audioUrl = firstAudio.baseUrl ?? '';
|
||||
//
|
||||
if (firstAudio.id != null) {
|
||||
@ -185,6 +254,13 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
defaultST = Duration(milliseconds: data.lastPlayTime!);
|
||||
await playerInit();
|
||||
|
||||
// await playerInit(
|
||||
// firstVideo,
|
||||
// audioUrl,
|
||||
// defaultST: Duration(milliseconds: data.lastPlayTime!),
|
||||
// duration: data.timeLength ?? 0,
|
||||
// );
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg'].toString());
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ class VideoIntroController extends GetxController {
|
||||
RxMap followStatus = {}.obs;
|
||||
int _tempThemeValue = -1;
|
||||
|
||||
RxInt lastPlayCid = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
@ -76,6 +78,7 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
}
|
||||
userLogin = user.get(UserBoxKey.userLogin) != null;
|
||||
lastPlayCid.value = int.parse(Get.parameters['cid']!);
|
||||
}
|
||||
|
||||
// 获取视频简介&分p
|
||||
@ -83,6 +86,9 @@ class VideoIntroController extends GetxController {
|
||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||
if (result['status']) {
|
||||
videoDetail.value = result['data']!;
|
||||
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
|
||||
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
|
||||
}
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||
.tabs
|
||||
.value = ['简介', '评论 ${result['data']!.stat!.reply}'];
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -20,6 +19,7 @@ import 'widgets/action_item.dart';
|
||||
import 'widgets/action_row_item.dart';
|
||||
import 'widgets/fav_panel.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
import 'widgets/page.dart';
|
||||
import 'widgets/season.dart';
|
||||
|
||||
class VideoIntroPanel extends StatefulWidget {
|
||||
@ -62,7 +62,6 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data['status']) {
|
||||
// 请求成功
|
||||
// return _buildView(context, false, videoDetail);
|
||||
return Obx(
|
||||
() => VideoInfo(
|
||||
loadingStatus: false,
|
||||
@ -95,22 +94,35 @@ class VideoInfo extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
Map videoItem = Get.put(VideoIntroController()).videoItem!;
|
||||
final VideoIntroController videoIntroController =
|
||||
Get.put(VideoIntroController(), tag: Get.arguments['heroTag']);
|
||||
bool isExpand = false;
|
||||
final String heroTag = Get.arguments['heroTag'];
|
||||
late final VideoIntroController videoIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
late final Map<dynamic, dynamic> videoItem;
|
||||
|
||||
late VideoDetailController? videoDetailCtr;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
late final bool loadingStatus; // 加载状态
|
||||
|
||||
late final dynamic owner;
|
||||
late final dynamic follower;
|
||||
late final dynamic followStatus;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
videoItem = videoIntroController.videoItem!;
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
|
||||
loadingStatus = widget.loadingStatus;
|
||||
owner = loadingStatus ? videoItem['owner'] : widget.videoDetail!.owner;
|
||||
follower = loadingStatus
|
||||
? '-'
|
||||
: Utils.numFormat(videoIntroController.userStat['follower']);
|
||||
followStatus = videoIntroController.followStatus;
|
||||
}
|
||||
|
||||
// 收藏
|
||||
@ -141,24 +153,39 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
// 用户主页
|
||||
onPushMember() {
|
||||
feedBack();
|
||||
int mid = !loadingStatus
|
||||
? widget.videoDetail!.owner!.mid
|
||||
: videoItem['owner'].mid;
|
||||
String face = !loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face;
|
||||
Get.toNamed('/member?mid=$mid',
|
||||
arguments: {'face': face, 'heroTag': (mid + 99).toString()});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData t = Theme.of(context);
|
||||
Color outline = t.colorScheme.outline;
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !widget.loadingStatus || videoItem.isNotEmpty
|
||||
child: !loadingStatus || videoItem.isNotEmpty
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InkWell(
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
!loadingStatus
|
||||
? widget.videoDetail!.title
|
||||
: videoItem['title'],
|
||||
style: const TextStyle(
|
||||
@ -182,14 +209,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
return t.highlightColor.withOpacity(0.2);
|
||||
}),
|
||||
),
|
||||
onPressed: () => showIntroDetail(),
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
onPressed: showIntroDetail,
|
||||
icon: Icon(
|
||||
Icons.more_horiz,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Row(
|
||||
children: [
|
||||
@ -237,7 +268,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// 点赞收藏转发 布局样式2
|
||||
// actionGrid(context, videoIntroController),
|
||||
// 合集
|
||||
if (!widget.loadingStatus &&
|
||||
if (!loadingStatus &&
|
||||
widget.videoDetail!.ugcSeason != null) ...[
|
||||
SeasonPanel(
|
||||
ugcSeason: widget.videoDetail!.ugcSeason!,
|
||||
@ -247,97 +278,86 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
.changeSeasonOrbangu(bvid, cid, aid),
|
||||
)
|
||||
],
|
||||
if (!loadingStatus &&
|
||||
widget.videoDetail!.pages != null &&
|
||||
widget.videoDetail!.pages!.length > 1) ...[
|
||||
Obx(() => PagesPanel(
|
||||
pages: widget.videoDetail!.pages!,
|
||||
cid: videoIntroController.lastPlayCid.value,
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (cid) =>
|
||||
videoIntroController.changeSeasonOrbangu(
|
||||
videoIntroController.bvid, cid, null),
|
||||
))
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
feedBack();
|
||||
int mid = !widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.mid
|
||||
: videoItem['owner'].mid;
|
||||
String face = !widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face;
|
||||
Get.toNamed('/member?mid=$mid', arguments: {
|
||||
'face': face,
|
||||
'heroTag': (mid + 99).toString()
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12, bottom: 12, left: 4, right: 4),
|
||||
onTap: onPushMember,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12, horizontal: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: !widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face,
|
||||
src: loadingStatus
|
||||
? owner.face
|
||||
: widget.videoDetail!.owner!.face,
|
||||
width: 34,
|
||||
height: 34,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.name
|
||||
: videoItem['owner'].name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
Text(owner.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
widget.loadingStatus
|
||||
? '-'
|
||||
: Utils.numFormat(
|
||||
videoIntroController.userStat['follower']),
|
||||
follower,
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||
color: t.colorScheme.outline),
|
||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||
color: outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
AnimatedOpacity(
|
||||
opacity: widget.loadingStatus ? 0 : 1,
|
||||
opacity: loadingStatus ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
child: Obx(
|
||||
() => videoIntroController
|
||||
.followStatus.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: () => videoIntroController
|
||||
.actionRelationMod(),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8),
|
||||
foregroundColor:
|
||||
videoIntroController.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? t.colorScheme.outline
|
||||
: t.colorScheme.onPrimary,
|
||||
backgroundColor: videoIntroController
|
||||
.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? t.colorScheme.onInverseSurface
|
||||
: t.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(
|
||||
videoIntroController.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? '已关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme.labelMedium!
|
||||
.fontSize),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: () => videoIntroController
|
||||
.actionRelationMod(),
|
||||
child: const Text('关注'),
|
||||
),
|
||||
() =>
|
||||
videoIntroController.followStatus.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: videoIntroController
|
||||
.actionRelationMod,
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8),
|
||||
foregroundColor:
|
||||
followStatus['attribute'] != 0
|
||||
? outline
|
||||
: t.colorScheme.onPrimary,
|
||||
backgroundColor:
|
||||
followStatus['attribute'] != 0
|
||||
? t.colorScheme
|
||||
.onInverseSurface
|
||||
: t.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(
|
||||
followStatus['attribute'] != 0
|
||||
? '已关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme
|
||||
.labelMedium!.fontSize),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: videoIntroController
|
||||
.actionRelationMod,
|
||||
child: const Text('关注'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -359,66 +379,64 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
|
||||
Widget actionGrid(BuildContext context, videoIntroController) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
return Padding(
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 6, bottom: 10),
|
||||
child: SizedBox(
|
||||
height: constraints.maxWidth / 5 * 0.8,
|
||||
child: GridView.count(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1.25,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-'),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: '稍后再看'),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => videoIntroController.actionCoinVideo(),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-'),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
// onTap: () => videoIntroController.actionFavVideo(),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-'),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.share!.toString()
|
||||
height: constraints.maxWidth / 5 * 0.8,
|
||||
child: GridView.count(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1.25,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-'),
|
||||
],
|
||||
),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
text: '稍后再看'),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => videoIntroController.actionCoinVideo(),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-'),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
// onTap: () => videoIntroController.actionFavVideo(),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-'),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.share!.toString()
|
||||
: '-'),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -431,10 +449,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-',
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@ -443,10 +460,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => videoIntroController.actionCoinVideo(),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-',
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@ -455,8 +471,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.heart),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-',
|
||||
),
|
||||
@ -468,57 +484,20 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.reply!.toString()
|
||||
: '-',
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-',
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.share),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
// text: !widget.loadingStatus
|
||||
loadingStatus: loadingStatus,
|
||||
// text: !loadingStatus
|
||||
// ? widget.videoDetail!.stat!.share!.toString()
|
||||
// : '-',
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
String desc = content.desc;
|
||||
List descV2 = content.descV2;
|
||||
// type
|
||||
// 1 普通文本
|
||||
// 2 @用户
|
||||
List<InlineSpan> spanChilds = [];
|
||||
if (descV2.isNotEmpty) {
|
||||
for (var i = 0; i < descV2.length; i++) {
|
||||
if (descV2[i].type == 1) {
|
||||
spanChilds.add(TextSpan(text: descV2[i].rawText));
|
||||
} else if (descV2[i].type == 2) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '@${descV2[i].rawText}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
String heroTag = Utils.makeHeroTag(descV2[i].bizId);
|
||||
Get.toNamed(
|
||||
'/member?mid=${descV2[i].bizId}',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spanChilds.add(TextSpan(text: desc));
|
||||
}
|
||||
return TextSpan(children: spanChilds);
|
||||
}
|
||||
}
|
||||
|
@ -33,24 +33,13 @@ class _FavPanelState extends State<FavPanel> {
|
||||
child: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
toolbarHeight: 50,
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: false,
|
||||
elevation: 1,
|
||||
title: Text(
|
||||
'选择文件夹',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
feedBack();
|
||||
await widget.ctr!.actionFavVideo();
|
||||
},
|
||||
child: const Text('完成'),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.close_outlined)),
|
||||
title:
|
||||
Text('添加到收藏夹', style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
@ -63,45 +52,33 @@ class _FavPanelState extends State<FavPanel> {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
itemCount:
|
||||
widget.ctr!.favFolderData.value.list!.length + 1,
|
||||
widget.ctr!.favFolderData.value.list!.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return const SizedBox(height: 10);
|
||||
} else {
|
||||
return ListTile(
|
||||
onTap: () => widget.ctr!.onChoose(
|
||||
widget.ctr!.favFolderData.value
|
||||
.list![index - 1].favState !=
|
||||
1,
|
||||
index - 1),
|
||||
dense: true,
|
||||
leading:
|
||||
const Icon(Icons.folder_special_outlined),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(widget.ctr!.favFolderData.value
|
||||
.list![index - 1].title!),
|
||||
subtitle: Text(
|
||||
'${widget.ctr!.favFolderData.value.list![index - 1].mediaCount}个内容',
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize),
|
||||
return ListTile(
|
||||
onTap: () => widget.ctr!.onChoose(
|
||||
widget.ctr!.favFolderData.value.list![index]
|
||||
.favState !=
|
||||
1,
|
||||
index),
|
||||
dense: true,
|
||||
leading: const Icon(Icons.folder_outlined),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(widget.ctr!.favFolderData.value
|
||||
.list![index].title!),
|
||||
subtitle: Text(
|
||||
'${widget.ctr!.favFolderData.value.list![index].mediaCount}个内容',
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: widget.ctr!.favFolderData.value
|
||||
.list![index].favState ==
|
||||
1,
|
||||
onChanged: (bool? checkValue) =>
|
||||
widget.ctr!.onChoose(checkValue!, index),
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: widget.ctr!.favFolderData.value
|
||||
.list![index - 1].favState ==
|
||||
1,
|
||||
onChanged: (bool? checkValue) => widget.ctr!
|
||||
.onChoose(checkValue!, index - 1),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -119,6 +96,46 @@ class _FavPanelState extends State<FavPanel> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).disabledColor.withOpacity(0.08),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 30, right: 30),
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface, // 设置按钮背景色
|
||||
),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
feedBack();
|
||||
await widget.ctr!.actionFavVideo();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 30, right: 30),
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary, // 设置按钮背景色
|
||||
),
|
||||
child: const Text('完成'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -27,19 +27,20 @@ class IntroDetail extends StatelessWidget {
|
||||
height: sheetHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.5),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(3))),
|
||||
InkWell(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(3))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -125,33 +126,29 @@ class IntroDetail extends StatelessWidget {
|
||||
// type
|
||||
// 1 普通文本
|
||||
// 2 @用户
|
||||
List<InlineSpan> spanChilds = [];
|
||||
if (descV2.isNotEmpty) {
|
||||
for (var i = 0; i < descV2.length; i++) {
|
||||
if (descV2[i].type == 1) {
|
||||
spanChilds.add(TextSpan(text: descV2[i].rawText));
|
||||
} else if (descV2[i].type == 2) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '@${descV2[i].rawText}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
String heroTag = Utils.makeHeroTag(descV2[i].bizId);
|
||||
Get.toNamed(
|
||||
'/member?mid=${descV2[i].bizId}',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
),
|
||||
List<TextSpan> spanChilds = List.generate(descV2.length, (index) {
|
||||
final currentDesc = descV2[index];
|
||||
switch (currentDesc.type) {
|
||||
case 1:
|
||||
return TextSpan(text: currentDesc.rawText);
|
||||
case 2:
|
||||
final colorSchemePrimary = Theme.of(context).colorScheme.primary;
|
||||
final heroTag = Utils.makeHeroTag(currentDesc.bizId);
|
||||
return TextSpan(
|
||||
text: '@${currentDesc.rawText}',
|
||||
style: TextStyle(color: colorSchemePrimary),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
Get.toNamed(
|
||||
'/member?mid=${currentDesc.bizId}',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
default:
|
||||
return TextSpan();
|
||||
}
|
||||
} else {
|
||||
spanChilds.add(TextSpan(text: desc));
|
||||
}
|
||||
});
|
||||
return TextSpan(children: spanChilds);
|
||||
}
|
||||
}
|
||||
|
203
lib/pages/video/detail/introduction/widgets/page.dart
Normal file
203
lib/pages/video/detail/introduction/widgets/page.dart
Normal file
@ -0,0 +1,203 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
|
||||
class PagesPanel extends StatefulWidget {
|
||||
final List<Part> pages;
|
||||
final int? cid;
|
||||
final double? sheetHeight;
|
||||
final Function? changeFuc;
|
||||
|
||||
const PagesPanel({
|
||||
super.key,
|
||||
required this.pages,
|
||||
this.cid,
|
||||
this.sheetHeight,
|
||||
this.changeFuc,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PagesPanel> createState() => _PagesPanelState();
|
||||
}
|
||||
|
||||
class _PagesPanelState extends State<PagesPanel> {
|
||||
late List<Part> episodes;
|
||||
late int currentIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
episodes = widget.pages;
|
||||
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
|
||||
}
|
||||
|
||||
void changeFucCall(item, i) async {
|
||||
await widget.changeFuc!(
|
||||
item.cid,
|
||||
);
|
||||
currentIndex = i;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 2),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('视频选集 '),
|
||||
Expanded(
|
||||
child: Text(
|
||||
' 正在播放:${widget.pages[currentIndex].pagePart}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
SizedBox(
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => Container(
|
||||
height: widget.sheetHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding:
|
||||
const EdgeInsets.only(left: 14, right: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'合集(${episodes.length})',
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView.builder(
|
||||
itemCount: episodes.length,
|
||||
itemBuilder: (context, index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
changeFucCall(episodes[index], index);
|
||||
Get.back();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 15,
|
||||
right: 15),
|
||||
child: Text(
|
||||
episodes[index].pagePart!,
|
||||
style: TextStyle(
|
||||
color: index == currentIndex
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'共${widget.pages.length}集',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 35,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: widget.pages.length,
|
||||
itemExtent: 150,
|
||||
itemBuilder: ((context, i) {
|
||||
return Container(
|
||||
width: 150,
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () => changeFucCall(widget.pages[i], i),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
if (i == currentIndex) ...[
|
||||
Image.asset(
|
||||
'assets/images/live.gif',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 12,
|
||||
),
|
||||
const SizedBox(width: 6)
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.pages[i].pagePart!,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: i == currentIndex
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -391,6 +391,7 @@ class ReplyItemRow extends StatelessWidget {
|
||||
),
|
||||
if (replies![i].isUp)
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: UpTag(),
|
||||
),
|
||||
buildContent(
|
||||
|
@ -118,7 +118,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 400,
|
||||
height: 500,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/common/widgets/sliver_header.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/pages/bangumi/introduction/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||
@ -163,8 +165,11 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
scrolledUnderElevation: 0,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
expandedHeight: videoHeight,
|
||||
// backgroundColor: Colors.transparent,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor:
|
||||
MediaQuery.of(Get.context!).platformBrightness ==
|
||||
Brightness.dark
|
||||
? Colors.black
|
||||
: Theme.of(context).colorScheme.background,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Padding(
|
||||
padding: EdgeInsets.only(top: statusBarHeight),
|
||||
@ -233,10 +238,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
backgroundColor:
|
||||
Colors.transparent,
|
||||
actions: [
|
||||
/// TODO
|
||||
IconButton(
|
||||
tooltip: '稍后再看',
|
||||
onPressed: () {},
|
||||
onPressed: () async {
|
||||
var res = await UserHttp
|
||||
.toViewLater(
|
||||
bvid:
|
||||
videoDetailController
|
||||
.bvid);
|
||||
SmartDialog.showToast(
|
||||
res['msg']);
|
||||
},
|
||||
icon: const Icon(Icons
|
||||
.history_outlined))
|
||||
],
|
||||
@ -291,39 +303,20 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0,
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(left: 20),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
@ -28,6 +28,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
late PlayUrlModel videoInfo;
|
||||
List<PlaySpeed> playSpeed = PlaySpeed.values;
|
||||
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
||||
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
|
||||
@override
|
||||
@ -81,7 +82,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
enabled: false,
|
||||
leading:
|
||||
const Icon(Icons.network_cell_outlined, size: 20),
|
||||
title: const Text('省流模式'),
|
||||
title: Text('省流模式', style: titleStyle),
|
||||
subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.75,
|
||||
@ -99,22 +100,22 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
onTap: () => {Get.back(), showSetSpeedSheet()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.speed_outlined, size: 20),
|
||||
title: const Text('播放速度'),
|
||||
subtitle: Text(
|
||||
'当前倍速 x${widget.controller!.playbackSpeed}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
),
|
||||
// Obx(
|
||||
// () => ListTile(
|
||||
// onTap: () => {Get.back(), showSetSpeedSheet()},
|
||||
// dense: true,
|
||||
// leading: const Icon(Icons.speed_outlined, size: 20),
|
||||
// title: Text('播放速度', style: titleStyle),
|
||||
// subtitle: Text(
|
||||
// '当前倍速 x${widget.controller!.playbackSpeed}',
|
||||
// style: subTitleStyle),
|
||||
// ),
|
||||
// ),
|
||||
ListTile(
|
||||
onTap: () => {Get.back(), showSetVideoQa()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
title: const Text('选择画质'),
|
||||
title: Text('选择画质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前画质 ${widget.videoDetailCtr!.currentVideoQa.description}',
|
||||
style: subTitleStyle),
|
||||
@ -123,24 +124,33 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
onTap: () => {Get.back(), showSetAudioQa()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.album_outlined, size: 20),
|
||||
title: const Text('选择音质'),
|
||||
title: Text('选择音质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前音质 ${widget.videoDetailCtr!.currentAudioQa.description}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
onTap: () => {Get.back(), showSetDecodeFormats()},
|
||||
dense: true,
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
title: const Text('播放设置'),
|
||||
leading: const Icon(Icons.av_timer_outlined, size: 20),
|
||||
title: Text('解码格式', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// dense: true,
|
||||
// enabled: false,
|
||||
// leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
// title: Text('播放设置', style: titleStyle),
|
||||
// ),
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
dense: true,
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.subtitles_outlined, size: 20),
|
||||
title: const Text('弹幕设置'),
|
||||
title: Text('弹幕设置', style: titleStyle),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -250,7 +260,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('选择画质'),
|
||||
Text('选择画质', style: titleStyle),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
@ -329,7 +339,9 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 45, child: Center(child: Text('选择音质'))),
|
||||
SizedBox(
|
||||
height: 45,
|
||||
child: Center(child: Text('选择音质', style: titleStyle))),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView(
|
||||
@ -370,6 +382,74 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
);
|
||||
}
|
||||
|
||||
// 选择解码格式
|
||||
void showSetDecodeFormats() {
|
||||
// 当前选中的解码格式
|
||||
VideoDecodeFormats currentDecodeFormats =
|
||||
widget.videoDetailCtr!.currentDecodeFormats;
|
||||
// 当前视频可用的解码格式
|
||||
List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
||||
List list = videoFormat.first.codecs!;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 250,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 45,
|
||||
child: Center(child: Text('选择解码格式', style: titleStyle))),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView(
|
||||
children: [
|
||||
for (var i in list) ...[
|
||||
ListTile(
|
||||
onTap: () {
|
||||
widget.videoDetailCtr!.currentDecodeFormats =
|
||||
VideoDecodeFormatsCode.fromString(i)!;
|
||||
widget.videoDetailCtr!.updatePlayer();
|
||||
Get.back();
|
||||
},
|
||||
dense: true,
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 20, right: 20),
|
||||
title: Text(VideoDecodeFormatsCode.fromString(i)!
|
||||
.description!),
|
||||
subtitle: Text(
|
||||
i!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: i.startsWith(currentDecodeFormats.code)
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _ = widget.controller!;
|
||||
|
@ -7,6 +7,7 @@ import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/pages/dynamics/index.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/mine/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/controller.dart';
|
||||
import 'package:pilipala/utils/cookie.dart';
|
||||
@ -71,17 +72,24 @@ class WebviewController extends GetxController {
|
||||
print('网页登录: $result');
|
||||
if (result['status'] && result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
Box user = GStrorage.user;
|
||||
user.put(UserBoxKey.userLogin, true);
|
||||
user.put(UserBoxKey.userName, result['data'].uname);
|
||||
user.put(UserBoxKey.userFace, result['data'].face);
|
||||
user.put(UserBoxKey.userMid, result['data'].mid);
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
userInfoCache.put('userInfoCache', result['data']);
|
||||
Get.find<MineController>().userInfo.value = result['data'];
|
||||
Get.find<MineController>().onInit();
|
||||
Get.find<RcmdController>().queryRcmdFeed('onRefresh');
|
||||
Get.find<DynamicsController>().queryFollowDynamic();
|
||||
try {
|
||||
Box user = GStrorage.user;
|
||||
user.put(UserBoxKey.userLogin, true);
|
||||
user.put(UserBoxKey.userName, result['data'].uname);
|
||||
user.put(UserBoxKey.userFace, result['data'].face);
|
||||
user.put(UserBoxKey.userMid, result['data'].mid);
|
||||
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
userInfoCache.put('userInfoCache', result['data']);
|
||||
|
||||
Get.find<MineController>().userInfo.value = result['data'];
|
||||
Get.find<MineController>().onInit();
|
||||
Get.find<RcmdController>().queryRcmdFeed('onRefresh');
|
||||
Get.find<DynamicsController>().queryFollowDynamic();
|
||||
|
||||
HomeController homeCtr = Get.find<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
} catch (_) {}
|
||||
Get.back();
|
||||
}
|
||||
} catch (e) {
|
||||
|
Reference in New Issue
Block a user