Merge branch 'main' into design
This commit is contained in:
@ -592,4 +592,7 @@ class Api {
|
|||||||
/// 直播间记录
|
/// 直播间记录
|
||||||
static const String liveRoomEntry =
|
static const String liveRoomEntry =
|
||||||
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction';
|
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction';
|
||||||
|
|
||||||
|
/// 删除评论
|
||||||
|
static const String replyDel = '/x/v2/reply/del';
|
||||||
}
|
}
|
||||||
|
@ -229,12 +229,25 @@ class LoginHttp {
|
|||||||
data: formData,
|
data: formData,
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
if (res.data['data']['status'] == 0) {
|
||||||
'status': true,
|
return {
|
||||||
'data': res.data['data'],
|
'status': true,
|
||||||
};
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'code': 1,
|
||||||
|
'data': res.data['data'],
|
||||||
|
'msg': res.data['data']['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,4 +115,25 @@ class ReplyHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future replyDel({
|
||||||
|
required int type, //replyType
|
||||||
|
required int oid,
|
||||||
|
required int rpid,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.replyDel,
|
||||||
|
queryParameters: {
|
||||||
|
'type': type, //type.index
|
||||||
|
'oid': oid,
|
||||||
|
'rpid': rpid,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'msg': '删除成功'};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,56 +79,68 @@ class _FavPageState extends State<FavPage> {
|
|||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: RefreshIndicator(
|
||||||
future: _futureBuilderFuture,
|
onRefresh: () async {
|
||||||
builder: (context, snapshot) {
|
_favController.hasMore.value = true;
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
_favController.currentPage = 1;
|
||||||
Map? data = snapshot.data;
|
setState(() {
|
||||||
if (data != null && data['status']) {
|
_futureBuilderFuture = _favController.queryFavFolder(type: 'init');
|
||||||
return Obx(
|
});
|
||||||
() => ListView.builder(
|
|
||||||
controller: scrollController,
|
|
||||||
itemCount: _favController.favFolderList.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return FavItem(
|
|
||||||
favFolderItem: _favController.favFolderList[index],
|
|
||||||
isOwner: _favController.isOwner.value,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return CustomScrollView(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
slivers: [
|
|
||||||
HttpError(
|
|
||||||
errMsg: data?['msg'] ?? '请求异常',
|
|
||||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
|
||||||
fn: () {
|
|
||||||
if (data?['code'] == -101) {
|
|
||||||
RoutePush.loginRedirectPush();
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
_futureBuilderFuture =
|
|
||||||
_favController.queryFavFolder();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return ListView.builder(
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return const VideoCardHSkeleton();
|
|
||||||
},
|
|
||||||
itemCount: 10,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
child: _buildBody(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildBody() {
|
||||||
|
return FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map? data = snapshot.data;
|
||||||
|
if (data != null && data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: _favController.favFolderList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return FavItem(
|
||||||
|
favFolderItem: _favController.favFolderList[index],
|
||||||
|
isOwner: _favController.isOwner.value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: data?['msg'] ?? '请求异常',
|
||||||
|
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||||
|
fn: () {
|
||||||
|
if (data?['code'] == -101) {
|
||||||
|
RoutePush.loginRedirectPush();
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFuture = _favController.queryFavFolder();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
itemCount: 10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import 'package:pilipala/utils/utils.dart';
|
|||||||
|
|
||||||
class FavDetailController extends GetxController {
|
class FavDetailController extends GetxController {
|
||||||
FavFolderItemData? item;
|
FavFolderItemData? item;
|
||||||
|
RxString title = ''.obs;
|
||||||
|
|
||||||
int? mediaId;
|
int? mediaId;
|
||||||
late String heroTag;
|
late String heroTag;
|
||||||
@ -24,6 +25,7 @@ class FavDetailController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
item = Get.arguments;
|
item = Get.arguments;
|
||||||
|
title.value = item!.title!;
|
||||||
if (Get.parameters.keys.isNotEmpty) {
|
if (Get.parameters.keys.isNotEmpty) {
|
||||||
mediaId = int.parse(Get.parameters['mediaId']!);
|
mediaId = int.parse(Get.parameters['mediaId']!);
|
||||||
heroTag = Get.parameters['heroTag']!;
|
heroTag = Get.parameters['heroTag']!;
|
||||||
@ -117,16 +119,18 @@ class FavDetailController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onEditFavFolder() async {
|
onEditFavFolder() async {
|
||||||
Get.toNamed(
|
var res = await Get.toNamed(
|
||||||
'/favEdit',
|
'/favEdit',
|
||||||
arguments: {
|
arguments: {
|
||||||
'mediaId': mediaId.toString(),
|
'mediaId': mediaId.toString(),
|
||||||
'title': item!.title,
|
'title': item!.title,
|
||||||
'intro': item!.intro,
|
'intro': item!.intro,
|
||||||
'cover': item!.cover,
|
'cover': item!.cover,
|
||||||
'privacy': item!.attr,
|
'privacy': [23, 1].contains(item!.attr) ? 1 : 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
title.value = res['title'];
|
||||||
|
print(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future toViewPlayAll() async {
|
Future toViewPlayAll() async {
|
||||||
|
@ -80,9 +80,11 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Obx(
|
||||||
_favDetailController.item!.title!,
|
() => Text(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
_favDetailController.title.value,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'共${_favDetailController.mediaCount}条视频',
|
'共${_favDetailController.mediaCount}条视频',
|
||||||
@ -156,14 +158,16 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Obx(
|
||||||
_favDetailController.item!.title!,
|
() => Text(
|
||||||
style: TextStyle(
|
_favDetailController.title.value,
|
||||||
fontSize: Theme.of(context)
|
style: TextStyle(
|
||||||
.textTheme
|
fontSize: Theme.of(context)
|
||||||
.titleMedium!
|
.textTheme
|
||||||
.fontSize,
|
.titleMedium!
|
||||||
fontWeight: FontWeight.bold),
|
.fontSize,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
|
@ -56,7 +56,7 @@ class FavEditController extends GetxController {
|
|||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
SmartDialog.showToast('编辑成功');
|
SmartDialog.showToast('编辑成功');
|
||||||
Get.back();
|
Get.back(result: {'title': title});
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(res['msg']);
|
SmartDialog.showToast(res['msg']);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ class LoginPageController extends GetxController {
|
|||||||
RxInt validSeconds = 180.obs;
|
RxInt validSeconds = 180.obs;
|
||||||
Timer? validTimer;
|
Timer? validTimer;
|
||||||
late String qrcodeKey;
|
late String qrcodeKey;
|
||||||
|
RxBool passwordVisible = false.obs;
|
||||||
|
|
||||||
// 监听pageView切换
|
// 监听pageView切换
|
||||||
void onPageChange(int index) {
|
void onPageChange(int index) {
|
||||||
@ -128,7 +129,14 @@ class LoginPageController extends GetxController {
|
|||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
await LoginUtils.confirmLogin('', null);
|
await LoginUtils.confirmLogin('', null);
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(res['msg']);
|
await SmartDialog.showToast(res['msg']);
|
||||||
|
if (res.containsKey('code') && res['code'] == 1) {
|
||||||
|
Get.toNamed('/webview', parameters: {
|
||||||
|
'url': res['data']['data']['url'],
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': '登录验证',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(webKeyRes['msg']);
|
SmartDialog.showToast(webKeyRes['msg']);
|
||||||
|
@ -269,25 +269,46 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.only(top: 38, bottom: 15),
|
margin: const EdgeInsets.only(top: 38, bottom: 15),
|
||||||
child: TextFormField(
|
child: Obx(() => TextFormField(
|
||||||
controller: _loginPageCtr.passwordTextController,
|
controller:
|
||||||
focusNode: _loginPageCtr.passwordTextFieldNode,
|
_loginPageCtr.passwordTextController,
|
||||||
keyboardType: TextInputType.visiblePassword,
|
focusNode:
|
||||||
decoration: InputDecoration(
|
_loginPageCtr.passwordTextFieldNode,
|
||||||
isDense: true,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
labelText: '输入密码',
|
obscureText:
|
||||||
border: OutlineInputBorder(
|
_loginPageCtr.passwordVisible.value,
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
decoration: InputDecoration(
|
||||||
),
|
isDense: true,
|
||||||
),
|
labelText: '输入密码',
|
||||||
// 校验用户名
|
border: OutlineInputBorder(
|
||||||
validator: (v) {
|
borderRadius: BorderRadius.circular(6.0),
|
||||||
return v!.trim().isNotEmpty ? null : "密码不能为空";
|
),
|
||||||
},
|
suffixIcon: IconButton(
|
||||||
onSaved: (val) {
|
icon: Icon(
|
||||||
print(val);
|
_loginPageCtr.passwordVisible.value
|
||||||
},
|
? Icons.visibility
|
||||||
),
|
: Icons.visibility_off,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_loginPageCtr.passwordVisible.value =
|
||||||
|
!_loginPageCtr
|
||||||
|
.passwordVisible.value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 校验用户名
|
||||||
|
validator: (v) {
|
||||||
|
return v!.trim().isNotEmpty
|
||||||
|
? null
|
||||||
|
: "密码不能为空";
|
||||||
|
},
|
||||||
|
onSaved: (val) {
|
||||||
|
print(val);
|
||||||
|
},
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Row(
|
Row(
|
||||||
|
@ -157,7 +157,7 @@ class _OpusPageState extends State<OpusPage> {
|
|||||||
Container(
|
Container(
|
||||||
alignment: TextHelper.getAlignment(paragraph.align),
|
alignment: TextHelper.getAlignment(paragraph.align),
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
child: Text.rich(
|
child: SelectableText.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: paragraph.text?.nodes?.map((node) {
|
children: paragraph.text?.nodes?.map((node) {
|
||||||
return TextHelper.buildTextSpan(
|
return TextHelper.buildTextSpan(
|
||||||
|
@ -20,7 +20,7 @@ class ReadPageController extends GetxController {
|
|||||||
super.onInit();
|
super.onInit();
|
||||||
title.value = Get.parameters['title'] ?? '';
|
title.value = Get.parameters['title'] ?? '';
|
||||||
id = Get.parameters['id']!;
|
id = Get.parameters['id']!;
|
||||||
articleType = Get.parameters['articleType']!;
|
articleType = Get.parameters['articleType'] ?? 'read';
|
||||||
url = 'https://www.bilibili.com/read/cv$id';
|
url = 'https://www.bilibili.com/read/cv$id';
|
||||||
scrollController.addListener(_scrollListener);
|
scrollController.addListener(_scrollListener);
|
||||||
fetchViewInfo();
|
fetchViewInfo();
|
||||||
|
@ -126,7 +126,6 @@ class _ReadPageState extends State<ReadPage> {
|
|||||||
Widget _buildContent(ReadDataModel cvData) {
|
Widget _buildContent(ReadDataModel cvData) {
|
||||||
final List<String> picList = _extractPicList(cvData);
|
final List<String> picList = _extractPicList(cvData);
|
||||||
final List<String> imgList = extractDataSrc(cvData.readInfo!.content!);
|
final List<String> imgList = extractDataSrc(cvData.readInfo!.content!);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
16, 0, 16, MediaQuery.of(context).padding.bottom + 40),
|
16, 0, 16, MediaQuery.of(context).padding.bottom + 40),
|
||||||
@ -163,9 +162,11 @@ class _ReadPageState extends State<ReadPage> {
|
|||||||
padding: const EdgeInsets.only(bottom: 20),
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
child: _buildAuthorWidget(cvData),
|
child: _buildAuthorWidget(cvData),
|
||||||
),
|
),
|
||||||
HtmlRender(
|
SelectionArea(
|
||||||
htmlContent: cvData.readInfo!.content!,
|
child: HtmlRender(
|
||||||
imgList: imgList,
|
htmlContent: cvData.readInfo!.content!,
|
||||||
|
imgList: imgList,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -206,7 +207,7 @@ class _ReadPageState extends State<ReadPage> {
|
|||||||
return Container(
|
return Container(
|
||||||
alignment: TextHelper.getAlignment(paragraph.align),
|
alignment: TextHelper.getAlignment(paragraph.align),
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
child: Text.rich(
|
child: SelectableText.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: paragraph.text?.nodes?.map((node) {
|
children: paragraph.text?.nodes?.map((node) {
|
||||||
return TextHelper.buildTextSpan(node, paragraph.align, context);
|
return TextHelper.buildTextSpan(node, paragraph.align, context);
|
||||||
|
@ -3,7 +3,6 @@ import 'dart:async';
|
|||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
import 'package:bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/constants.dart';
|
import 'package:pilipala/http/constants.dart';
|
||||||
@ -154,11 +153,10 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
if (hasLike.value && hasCoin.value && hasFav.value) {
|
if (hasLike.value && hasCoin.value && hasFav.value) {
|
||||||
// 已点赞、投币、收藏
|
// 已点赞、投币、收藏
|
||||||
SmartDialog.showToast('🙏 UP已经收到了~');
|
SmartDialog.showToast('UP已经收到了~');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var result = await VideoHttp.oneThree(bvid: bvid);
|
var result = await VideoHttp.oneThree(bvid: bvid);
|
||||||
print('🤣🦴:${result["data"]}');
|
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
hasLike.value = result["data"]["like"];
|
hasLike.value = result["data"]["like"];
|
||||||
hasCoin.value = result["data"]["coin"];
|
hasCoin.value = result["data"]["coin"];
|
||||||
@ -604,4 +602,34 @@ class VideoIntroController extends GetxController {
|
|||||||
).buildShowContent(Get.context!),
|
).buildShowContent(Get.context!),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
oneThreeDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('是否一键三连'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => navigator!.pop(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(Get.context!).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
actionOneThree();
|
||||||
|
navigator!.pop();
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
import 'package:bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -151,11 +148,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
RxBool isExpand = false.obs;
|
RxBool isExpand = false.obs;
|
||||||
late ExpandableController _expandableCtr;
|
late ExpandableController _expandableCtr;
|
||||||
|
|
||||||
// 一键三连动画
|
|
||||||
late AnimationController _controller;
|
|
||||||
late Animation<double> _scaleTransition;
|
|
||||||
final RxDouble _progress = 0.0.obs;
|
|
||||||
|
|
||||||
void Function()? handleState(Future<dynamic> Function() action) {
|
void Function()? handleState(Future<dynamic> Function() action) {
|
||||||
return isProcessing
|
return isProcessing
|
||||||
? null
|
? null
|
||||||
@ -178,26 +170,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
owner = widget.videoDetail!.owner;
|
owner = widget.videoDetail!.owner;
|
||||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||||
_expandableCtr = ExpandableController(initialExpanded: false);
|
_expandableCtr = ExpandableController(initialExpanded: false);
|
||||||
|
|
||||||
/// 一键三连动画
|
|
||||||
_controller = AnimationController(
|
|
||||||
duration: const Duration(milliseconds: 1500),
|
|
||||||
reverseDuration: const Duration(milliseconds: 300),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_scaleTransition = Tween<double>(begin: 0.5, end: 1.5).animate(_controller)
|
|
||||||
..addListener(() async {
|
|
||||||
_progress.value =
|
|
||||||
double.parse((_scaleTransition.value - 0.5).toStringAsFixed(3));
|
|
||||||
if (_progress.value == 1) {
|
|
||||||
if (_controller.status == AnimationStatus.completed) {
|
|
||||||
await videoIntroController.actionOneThree();
|
|
||||||
}
|
|
||||||
_progress.value = 0;
|
|
||||||
_scaleTransition.removeListener(() {});
|
|
||||||
_controller.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -279,8 +251,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_expandableCtr.dispose();
|
_expandableCtr.dispose();
|
||||||
_controller.dispose();
|
|
||||||
_scaleTransition.removeListener(() {});
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -573,131 +543,34 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
Widget actionGrid(BuildContext context, videoIntroController) {
|
Widget actionGrid(BuildContext context, videoIntroController) {
|
||||||
final actionTypeSort = GlobalDataCache().actionTypeSort;
|
final actionTypeSort = GlobalDataCache().actionTypeSort;
|
||||||
|
|
||||||
Widget progressWidget(progress) {
|
|
||||||
return SizedBox(
|
|
||||||
width: const IconThemeData.fallback().size! + 5,
|
|
||||||
height: const IconThemeData.fallback().size! + 5,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
value: progress.value,
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Widget> menuListWidgets = {
|
Map<String, Widget> menuListWidgets = {
|
||||||
'like': Obx(
|
'like': Obx(
|
||||||
() {
|
() => ActionItem(
|
||||||
bool likeStatus = videoIntroController.hasLike.value;
|
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||||
return Stack(
|
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||||
children: [
|
onLongPress: () => videoIntroController.oneThreeDialog(),
|
||||||
Positioned(
|
selectStatus: videoIntroController.hasLike.value,
|
||||||
top: ((Get.size.width - 24) / 5) / 2 -
|
text: widget.videoDetail!.stat!.like!.toString(),
|
||||||
(const IconThemeData.fallback().size!),
|
),
|
||||||
left: ((Get.size.width - 24) / 5) / 2 -
|
|
||||||
(const IconThemeData.fallback().size! + 5) / 2,
|
|
||||||
child: progressWidget(_progress)),
|
|
||||||
InkWell(
|
|
||||||
onTapDown: (details) {
|
|
||||||
feedBack();
|
|
||||||
if (videoIntroController.userInfo == null) {
|
|
||||||
SmartDialog.showToast('账号未登录');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_controller.forward();
|
|
||||||
},
|
|
||||||
onTapUp: (TapUpDetails details) {
|
|
||||||
if (_progress.value == 0) {
|
|
||||||
feedBack();
|
|
||||||
EasyThrottle.throttle(
|
|
||||||
'my-throttler', const Duration(milliseconds: 200), () {
|
|
||||||
videoIntroController.actionLikeVideo();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_controller.reverse();
|
|
||||||
},
|
|
||||||
onTapCancel: () {
|
|
||||||
_controller.reverse();
|
|
||||||
},
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
child: SizedBox(
|
|
||||||
width: (Get.size.width - 24) / 5,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
transitionBuilder:
|
|
||||||
(Widget child, Animation<double> animation) {
|
|
||||||
return ScaleTransition(
|
|
||||||
scale: animation, child: child);
|
|
||||||
},
|
|
||||||
child: Icon(
|
|
||||||
key: ValueKey<bool>(likeStatus),
|
|
||||||
likeStatus
|
|
||||||
? FontAwesomeIcons.solidThumbsUp
|
|
||||||
: FontAwesomeIcons.thumbsUp,
|
|
||||||
color: likeStatus
|
|
||||||
? colorScheme.primary
|
|
||||||
: colorScheme.outline,
|
|
||||||
size: 21,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(
|
|
||||||
widget.videoDetail!.stat!.like!.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: likeStatus ? colorScheme.primary : null,
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelSmall!.fontSize,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
'coin': Obx(
|
'coin': Obx(
|
||||||
() => Stack(
|
() => ActionItem(
|
||||||
children: [
|
icon: const Icon(FontAwesomeIcons.b),
|
||||||
Positioned(
|
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||||
top: ((Get.size.width - 24) / 5) / 2 -
|
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||||
(const IconThemeData.fallback().size!),
|
selectStatus: videoIntroController.hasCoin.value,
|
||||||
left: ((Get.size.width - 24) / 5) / 2 -
|
text: widget.videoDetail!.stat!.coin!.toString(),
|
||||||
(const IconThemeData.fallback().size! + 5) / 2,
|
|
||||||
child: progressWidget(_progress)),
|
|
||||||
ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.b),
|
|
||||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
|
||||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
|
||||||
selectStatus: videoIntroController.hasCoin.value,
|
|
||||||
text: widget.videoDetail!.stat!.coin!.toString(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'collect': Obx(
|
'collect': Obx(
|
||||||
() => Stack(
|
() => ActionItem(
|
||||||
children: [
|
icon: const Icon(FontAwesomeIcons.star),
|
||||||
Positioned(
|
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||||
top: ((Get.size.width - 24) / 5) / 2 -
|
onTap: () => showFavBottomSheet(),
|
||||||
(const IconThemeData.fallback().size!),
|
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||||
left: ((Get.size.width - 24) / 5) / 2 -
|
selectStatus: videoIntroController.hasFav.value,
|
||||||
(const IconThemeData.fallback().size! + 5) / 2,
|
text: widget.videoDetail!.stat!.favorite!.toString(),
|
||||||
child: progressWidget(_progress)),
|
|
||||||
ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.star),
|
|
||||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
|
||||||
onTap: () => showFavBottomSheet(),
|
|
||||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
|
||||||
selectStatus: videoIntroController.hasFav.value,
|
|
||||||
text: widget.videoDetail!.stat!.favorite!.toString(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'watchLater': ActionItem(
|
'watchLater': ActionItem(
|
||||||
|
@ -9,6 +9,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/reply.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
import 'package:pilipala/pages/main/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
@ -18,6 +19,7 @@ import 'package:pilipala/plugin/pl_gallery/index.dart';
|
|||||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||||
import 'package:pilipala/utils/app_scheme.dart';
|
import 'package:pilipala/utils/app_scheme.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/global_data_cache.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/url_utils.dart';
|
import 'package:pilipala/utils/url_utils.dart';
|
||||||
@ -48,6 +50,8 @@ class ReplyItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final bool isOwner = int.parse(replyItem!.member!.mid!) ==
|
||||||
|
(GlobalDataCache().userInfo?.mid ?? -1);
|
||||||
return Material(
|
return Material(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
// 点击整个评论区 评论详情/回复
|
// 点击整个评论区 评论详情/回复
|
||||||
@ -73,6 +77,7 @@ class ReplyItem extends StatelessWidget {
|
|||||||
return MorePanel(
|
return MorePanel(
|
||||||
item: replyItem,
|
item: replyItem,
|
||||||
mainFloor: true,
|
mainFloor: true,
|
||||||
|
isOwner: isOwner,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -698,14 +703,11 @@ InlineSpan buildContent(
|
|||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
} else if (RegExp(r'^cv\d+$').hasMatch(matchStr)) {
|
} else if (RegExp(r'^cv\d+$').hasMatch(matchStr)) {
|
||||||
Get.toNamed(
|
Get.toNamed('/read', parameters: {
|
||||||
'/webview',
|
'title': title,
|
||||||
parameters: {
|
'id': Utils.matchNum(matchStr).first.toString(),
|
||||||
'url': 'https://www.bilibili.com/read/$matchStr',
|
'articleType': 'read',
|
||||||
'type': 'url',
|
});
|
||||||
'pageTitle': title
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
|
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
|
||||||
SchemeEntity scheme = SchemeEntity(
|
SchemeEntity scheme = SchemeEntity(
|
||||||
@ -1004,10 +1006,12 @@ InlineSpan buildContent(
|
|||||||
class MorePanel extends StatelessWidget {
|
class MorePanel extends StatelessWidget {
|
||||||
final dynamic item;
|
final dynamic item;
|
||||||
final bool mainFloor;
|
final bool mainFloor;
|
||||||
|
final bool isOwner;
|
||||||
const MorePanel({
|
const MorePanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.mainFloor = false,
|
this.mainFloor = false,
|
||||||
|
this.isOwner = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<dynamic> menuActionHandler(String type) async {
|
Future<dynamic> menuActionHandler(String type) async {
|
||||||
@ -1043,9 +1047,43 @@ class MorePanel extends StatelessWidget {
|
|||||||
// case 'report':
|
// case 'report':
|
||||||
// SmartDialog.showToast('举报');
|
// SmartDialog.showToast('举报');
|
||||||
// break;
|
// break;
|
||||||
// case 'delete':
|
case 'delete':
|
||||||
// SmartDialog.showToast('删除');
|
// 删除评论提示
|
||||||
// break;
|
await showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('删除评论'),
|
||||||
|
content: const Text('删除评论后,评论下所有回复将被删除,确定删除吗?'),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: Text('取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline)),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Get.back();
|
||||||
|
var result = await ReplyHttp.replyDel(
|
||||||
|
type: item.type!,
|
||||||
|
oid: item.oid!,
|
||||||
|
rpid: item.rpid!,
|
||||||
|
);
|
||||||
|
if (result['status']) {
|
||||||
|
SmartDialog.showToast('评论删除成功,需手动刷新');
|
||||||
|
Get.back();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1054,6 +1092,7 @@ class MorePanel extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
TextTheme textTheme = Theme.of(context).textTheme;
|
TextTheme textTheme = Theme.of(context).textTheme;
|
||||||
|
Color errorColor = colorScheme.error;
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -1106,12 +1145,14 @@ class MorePanel extends StatelessWidget {
|
|||||||
// leading: Icon(Icons.report_outlined, color: errorColor),
|
// leading: Icon(Icons.report_outlined, color: errorColor),
|
||||||
// title: Text('举报', style: TextStyle(color: errorColor)),
|
// title: Text('举报', style: TextStyle(color: errorColor)),
|
||||||
// ),
|
// ),
|
||||||
// ListTile(
|
if (isOwner)
|
||||||
// onTap: () async => await menuActionHandler('del'),
|
ListTile(
|
||||||
// minLeadingWidth: 0,
|
onTap: () async => await menuActionHandler('delete'),
|
||||||
// leading: Icon(Icons.delete_outline, color: errorColor),
|
minLeadingWidth: 0,
|
||||||
// title: Text('删除', style: TextStyle(color: errorColor)),
|
leading: Icon(Icons.delete_outline, color: errorColor),
|
||||||
// ),
|
title: Text('删除评论',
|
||||||
|
style: textTheme.titleSmall!.copyWith(color: errorColor)),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -68,10 +68,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
late final AppLifecycleListener _lifecycleListener;
|
late final AppLifecycleListener _lifecycleListener;
|
||||||
late double statusHeight;
|
late double statusHeight;
|
||||||
|
|
||||||
// 稍后再看控制器
|
|
||||||
// late AnimationController _laterCtr;
|
|
||||||
// late Animation<Offset> _laterOffsetAni;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -108,7 +104,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
}
|
}
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
lifecycleListener();
|
lifecycleListener();
|
||||||
// watchLaterControllerInit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取视频资源,初始化播放器
|
// 获取视频资源,初始化播放器
|
||||||
@ -242,8 +237,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
appbarStream.close();
|
appbarStream.close();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_lifecycleListener.dispose();
|
_lifecycleListener.dispose();
|
||||||
// _laterCtr.dispose();
|
|
||||||
// _laterOffsetAni.removeListener(() {});
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,6 +290,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
plPlayerController?.play();
|
plPlayerController?.play();
|
||||||
}
|
}
|
||||||
plPlayerController?.addStatusLister(playerListener);
|
plPlayerController?.addStatusLister(playerListener);
|
||||||
|
appbarStream.add(0);
|
||||||
super.didPopNext();
|
super.didPopNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,21 +484,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 稍后再看控制器初始化
|
|
||||||
// void watchLaterControllerInit() {
|
|
||||||
// _laterCtr = AnimationController(
|
|
||||||
// duration: const Duration(milliseconds: 300),
|
|
||||||
// vsync: this,
|
|
||||||
// );
|
|
||||||
// _laterOffsetAni = Tween<Offset>(
|
|
||||||
// begin: const Offset(0.0, 1.0),
|
|
||||||
// end: Offset.zero,
|
|
||||||
// ).animate(CurvedAnimation(
|
|
||||||
// parent: _laterCtr,
|
|
||||||
// curve: Curves.easeInOut,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sizeContext = MediaQuery.sizeOf(context);
|
final sizeContext = MediaQuery.sizeOf(context);
|
||||||
|
Reference in New Issue
Block a user