mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-29 15:00:14 +08:00
feat: create/edit/del fav folder
This commit is contained in:
@@ -47,6 +47,7 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] 创建/编辑/删除收藏夹
|
||||||
- [x] 评论楼中楼查看对话
|
- [x] 评论楼中楼查看对话
|
||||||
- [x] 评论楼中楼定位点击查看的评论
|
- [x] 评论楼中楼定位点击查看的评论
|
||||||
- [x] 评论楼中楼按热度/时间排序
|
- [x] 评论楼中楼按热度/时间排序
|
||||||
|
|||||||
@@ -169,6 +169,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.yalantis.ucrop.UCropActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -24,9 +24,12 @@ class Constants {
|
|||||||
'11111111111111111111111111111111:1111111111111111:0:0';
|
'11111111111111111111111111111111:1111111111111111:0:0';
|
||||||
static const String userAgent =
|
static const String userAgent =
|
||||||
'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android build/1462100 channel/bili innerVer/1462100 osVer/14 network/2';
|
'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android build/1462100 channel/bili innerVer/1462100 osVer/14 network/2';
|
||||||
static final String statistics = jsonEncode(
|
static const String statistics =
|
||||||
{"appId": 5, "platform": 3, "version": "1.46.2", "abtest": ""});
|
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
|
||||||
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
// jsonEncode(
|
||||||
|
// {"appId": 5, "platform": 3, "version": "1.46.2", "abtest": ""});
|
||||||
|
// Uri.encodeComponent(
|
||||||
|
// '{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
||||||
|
|
||||||
//内容来自 https://passport.bilibili.com/web/generic/country/list
|
//内容来自 https://passport.bilibili.com/web/generic/country/list
|
||||||
static const List<Map<String, dynamic>> internationalDialingPrefix = [
|
static const List<Map<String, dynamic>> internationalDialingPrefix = [
|
||||||
|
|||||||
@@ -172,6 +172,14 @@ class Api {
|
|||||||
// https://api.bilibili.com/x/v3/fav/folder/created/list?pn=1&ps=10&up_mid=17340771
|
// https://api.bilibili.com/x/v3/fav/folder/created/list?pn=1&ps=10&up_mid=17340771
|
||||||
static const String userFavFolder = '/x/v3/fav/folder/created/list';
|
static const String userFavFolder = '/x/v3/fav/folder/created/list';
|
||||||
|
|
||||||
|
static const String folderInfo = '/x/v3/fav/folder/info';
|
||||||
|
|
||||||
|
static const String addFolder = '/x/v3/fav/folder/add';
|
||||||
|
|
||||||
|
static const String editFolder = '/x/v3/fav/folder/edit';
|
||||||
|
|
||||||
|
static const String deleteFolder = '/x/v3/fav/folder/del';
|
||||||
|
|
||||||
/// 收藏夹 详情
|
/// 收藏夹 详情
|
||||||
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
||||||
/// pn int 当前页
|
/// pn int 当前页
|
||||||
@@ -664,6 +672,8 @@ class Api {
|
|||||||
|
|
||||||
static const String uploadBfs = '/x/dynamic/feed/draw/upload_bfs';
|
static const String uploadBfs = '/x/dynamic/feed/draw/upload_bfs';
|
||||||
|
|
||||||
|
static const String uploadImage = '/x/upload/web/image';
|
||||||
|
|
||||||
static const String videoRelation = '/x/web-interface/archive/relation';
|
static const String videoRelation = '/x/web-interface/archive/relation';
|
||||||
|
|
||||||
static const String seasonFav = '/x/v3/fav/season/'; // + fav unfav
|
static const String seasonFav = '/x/v3/fav/season/'; // + fav unfav
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:PiliPalaX/http/constants.dart';
|
import 'package:PiliPalaX/http/constants.dart';
|
||||||
import 'package:PiliPalaX/pages/dynamics/view.dart' show ReplyOption;
|
import 'package:PiliPalaX/pages/dynamics/view.dart' show ReplyOption;
|
||||||
|
import 'package:PiliPalaX/utils/storage.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
import '../models/msg/account.dart';
|
import '../models/msg/account.dart';
|
||||||
@@ -205,6 +206,33 @@ class MsgHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future uploadImage({
|
||||||
|
required dynamic path,
|
||||||
|
required String bucket,
|
||||||
|
required String dir,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.uploadImage,
|
||||||
|
data: FormData.fromMap({
|
||||||
|
'bucket': bucket,
|
||||||
|
'file': await MultipartFile.fromFile(path),
|
||||||
|
'dir': dir,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future uploadBfs(
|
static Future uploadBfs(
|
||||||
dynamic path,
|
dynamic path,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:PiliPalaX/http/loading_state.dart';
|
import 'package:PiliPalaX/http/loading_state.dart';
|
||||||
|
import 'package:PiliPalaX/utils/storage.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import '../common/constants.dart';
|
import '../common/constants.dart';
|
||||||
@@ -65,6 +66,65 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future deleteFolder({
|
||||||
|
required List<dynamic> mediaIds,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(Api.deleteFolder,
|
||||||
|
data: {
|
||||||
|
'media_ids': mediaIds.join(','),
|
||||||
|
'platform': 'web',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
));
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future addOrEditFolder({
|
||||||
|
required bool isAdd,
|
||||||
|
dynamic mediaId,
|
||||||
|
required String title,
|
||||||
|
required int privacy,
|
||||||
|
required String cover,
|
||||||
|
required String intro,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(isAdd ? Api.addFolder : Api.editFolder,
|
||||||
|
data: {
|
||||||
|
'title': title,
|
||||||
|
'intro': intro,
|
||||||
|
'privacy': privacy,
|
||||||
|
'cover': cover.isNotEmpty ? Uri.encodeFull(cover) : cover,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
if (mediaId != null) 'media_id': mediaId,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
));
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future folderInfo({
|
||||||
|
dynamic mediaId,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(Api.folderInfo, data: {
|
||||||
|
'media_id': mediaId,
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future<LoadingState> userFavFolderDetail(
|
static Future<LoadingState> userFavFolderDetail(
|
||||||
{required int mediaId,
|
{required int mediaId,
|
||||||
required int pn,
|
required int pn,
|
||||||
|
|||||||
@@ -174,10 +174,10 @@ class _BangumiInfoState extends State<BangumiInfo>
|
|||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
minChildSize: 0,
|
minChildSize: 0,
|
||||||
maxChildSize: 1,
|
maxChildSize: 1,
|
||||||
initialChildSize: 0.6,
|
initialChildSize: 0.7,
|
||||||
snap: true,
|
snap: true,
|
||||||
expand: false,
|
expand: false,
|
||||||
snapSizes: const [0.6],
|
snapSizes: const [0.7],
|
||||||
builder: (BuildContext context, ScrollController scrollController) {
|
builder: (BuildContext context, ScrollController scrollController) {
|
||||||
return FavPanel(
|
return FavPanel(
|
||||||
ctr: bangumiIntroController,
|
ctr: bangumiIntroController,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:PiliPalaX/http/loading_state.dart';
|
import 'package:PiliPalaX/http/loading_state.dart';
|
||||||
import 'package:PiliPalaX/http/user.dart';
|
import 'package:PiliPalaX/http/user.dart';
|
||||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||||
|
import 'package:PiliPalaX/utils/storage.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPalaX/http/video.dart';
|
import 'package:PiliPalaX/http/video.dart';
|
||||||
@@ -15,6 +16,8 @@ class FavDetailController extends CommonController {
|
|||||||
RxString title = ''.obs;
|
RxString title = ''.obs;
|
||||||
RxString cover = ''.obs;
|
RxString cover = ''.obs;
|
||||||
RxString name = ''.obs;
|
RxString name = ''.obs;
|
||||||
|
late int attr;
|
||||||
|
RxBool isOwner = false.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@@ -49,6 +52,9 @@ class FavDetailController extends CommonController {
|
|||||||
cover.value = response.response.info['cover'];
|
cover.value = response.response.info['cover'];
|
||||||
name.value = response.response.info['upper']['name'];
|
name.value = response.response.info['upper']['name'];
|
||||||
mediaCount = response.response.info['media_count'];
|
mediaCount = response.response.info['media_count'];
|
||||||
|
attr = response.response.info['attr'];
|
||||||
|
isOwner.value = response.response.info['mid'] ==
|
||||||
|
GStorage.userInfo.get('userInfoCache')?.mid;
|
||||||
}
|
}
|
||||||
List currentList = loadingState.value is Success
|
List currentList = loadingState.value is Success
|
||||||
? (loadingState.value as Success).response
|
? (loadingState.value as Success).response
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:PiliPalaX/http/loading_state.dart';
|
import 'package:PiliPalaX/http/loading_state.dart';
|
||||||
|
import 'package:PiliPalaX/http/user.dart';
|
||||||
import 'package:PiliPalaX/pages/fav_search/view.dart' show SearchType;
|
import 'package:PiliPalaX/pages/fav_search/view.dart' show SearchType;
|
||||||
|
import 'package:PiliPalaX/utils/utils.dart';
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
|
import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
|
||||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||||
@@ -113,6 +116,39 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
// onPressed: () {},
|
// onPressed: () {},
|
||||||
// icon: const Icon(Icons.more_vert),
|
// icon: const Icon(Icons.more_vert),
|
||||||
// ),
|
// ),
|
||||||
|
Obx(
|
||||||
|
() => _favDetailController.isOwner.value
|
||||||
|
? PopupMenuButton(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/createFav',
|
||||||
|
parameters: {'mediaId': mediaId},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text('编辑信息'),
|
||||||
|
),
|
||||||
|
if (!Utils.isDefault(_favDetailController.attr))
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
UserHttp.deleteFolder(mediaIds: [mediaId])
|
||||||
|
.then((data) {
|
||||||
|
if (data['status']) {
|
||||||
|
SmartDialog.showToast('删除成功');
|
||||||
|
Get.back();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(data['msg']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text('删除'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
],
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
|||||||
@@ -180,10 +180,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
minChildSize: 0,
|
minChildSize: 0,
|
||||||
maxChildSize: 1,
|
maxChildSize: 1,
|
||||||
initialChildSize: 0.6,
|
initialChildSize: 0.7,
|
||||||
snap: true,
|
snap: true,
|
||||||
expand: false,
|
expand: false,
|
||||||
snapSizes: const [0.6],
|
snapSizes: const [0.7],
|
||||||
builder: (BuildContext context, ScrollController scrollController) {
|
builder: (BuildContext context, ScrollController scrollController) {
|
||||||
return FavPanel(
|
return FavPanel(
|
||||||
ctr: videoIntroController,
|
ctr: videoIntroController,
|
||||||
|
|||||||
384
lib/pages/video/detail/introduction/widgets/create_fav_page.dart
Normal file
384
lib/pages/video/detail/introduction/widgets/create_fav_page.dart
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||||
|
import 'package:PiliPalaX/http/msg.dart';
|
||||||
|
import 'package:PiliPalaX/http/user.dart';
|
||||||
|
import 'package:PiliPalaX/utils/utils.dart';
|
||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_cropper/image_cropper.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
|
class CreateFavPage extends StatefulWidget {
|
||||||
|
const CreateFavPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CreateFavPage> createState() => _CreateFavPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateFavPageState extends State<CreateFavPage> {
|
||||||
|
dynamic _mediaId;
|
||||||
|
late final _titleController = TextEditingController();
|
||||||
|
late final _introController = TextEditingController();
|
||||||
|
String? _cover;
|
||||||
|
bool _isPublic = true;
|
||||||
|
late final _imagePicker = ImagePicker();
|
||||||
|
String? _errMsg;
|
||||||
|
int? _attr;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_mediaId = Get.parameters['mediaId'];
|
||||||
|
if (_mediaId != null) {
|
||||||
|
_getFolderInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _getFolderInfo() {
|
||||||
|
UserHttp.folderInfo(mediaId: _mediaId).then((data) {
|
||||||
|
if (data['status']) {
|
||||||
|
_titleController.text = data['data']['title'];
|
||||||
|
_introController.text = data['data']['intro'];
|
||||||
|
_isPublic = Utils.isPublic(data['data']['attr']);
|
||||||
|
_cover = data['data']['cover'];
|
||||||
|
_attr = data['data']['attr'];
|
||||||
|
} else {
|
||||||
|
_errMsg = data['msg'];
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_titleController.dispose();
|
||||||
|
_introController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(_mediaId != null ? '编辑' : '创建'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (_titleController.text.isEmpty) {
|
||||||
|
SmartDialog.showToast('名称不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UserHttp.addOrEditFolder(
|
||||||
|
isAdd: _mediaId == null,
|
||||||
|
mediaId: _mediaId,
|
||||||
|
title: _titleController.text,
|
||||||
|
privacy: _isPublic ? 0 : 1,
|
||||||
|
cover: _cover ?? '',
|
||||||
|
intro: _introController.text,
|
||||||
|
).then((data) {
|
||||||
|
if (data['status']) {
|
||||||
|
Get.back(result: data['data']);
|
||||||
|
SmartDialog.showToast('${_mediaId != null ? '编辑' : '创建'}成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(data['msg']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text('完成'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _mediaId != null
|
||||||
|
? _titleController.text.isNotEmpty
|
||||||
|
? _buildBody
|
||||||
|
: _errMsg?.isNotEmpty == true
|
||||||
|
? Center(
|
||||||
|
child: CustomScrollView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: _errMsg,
|
||||||
|
fn: _getFolderInfo,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Center(child: CircularProgressIndicator())
|
||||||
|
: _buildBody,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pickImg() async {
|
||||||
|
XFile? pickedFile = await _imagePicker.pickImage(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
imageQuality: 100,
|
||||||
|
);
|
||||||
|
if (pickedFile != null && mounted) {
|
||||||
|
CroppedFile? croppedFile = await ImageCropper().cropImage(
|
||||||
|
sourcePath: pickedFile.path,
|
||||||
|
uiSettings: [
|
||||||
|
AndroidUiSettings(
|
||||||
|
toolbarTitle: '裁剪',
|
||||||
|
toolbarColor: Theme.of(context).colorScheme.primary,
|
||||||
|
toolbarWidgetColor: Colors.white,
|
||||||
|
aspectRatioPresets: [
|
||||||
|
CropAspectRatioPreset.ratio16x9,
|
||||||
|
],
|
||||||
|
lockAspectRatio: true,
|
||||||
|
hideBottomControls: true,
|
||||||
|
initAspectRatio: CropAspectRatioPreset.ratio16x9,
|
||||||
|
),
|
||||||
|
IOSUiSettings(
|
||||||
|
title: '裁剪',
|
||||||
|
aspectRatioPresets: [
|
||||||
|
CropAspectRatioPreset.ratio16x9,
|
||||||
|
],
|
||||||
|
aspectRatioLockEnabled: true,
|
||||||
|
resetAspectRatioEnabled: false,
|
||||||
|
aspectRatioPickerButtonHidden: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (croppedFile != null) {
|
||||||
|
MsgHttp.uploadImage(
|
||||||
|
path: croppedFile.path,
|
||||||
|
bucket: 'medialist',
|
||||||
|
dir: 'cover',
|
||||||
|
).then((data) {
|
||||||
|
if (data['status']) {
|
||||||
|
_cover = data['data']['location'];
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(data['msg']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic leadingStyle = TextStyle(fontSize: 14);
|
||||||
|
|
||||||
|
Widget get _buildBody => SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (_attr == null || !Utils.isDefault(_attr!)) ...[
|
||||||
|
ListTile(
|
||||||
|
tileColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
onTap: () {
|
||||||
|
EasyThrottle.throttle(
|
||||||
|
'imagePicker', const Duration(milliseconds: 500),
|
||||||
|
() async {
|
||||||
|
if (_cover?.isNotEmpty == true) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
_pickImg();
|
||||||
|
},
|
||||||
|
title: const Text(
|
||||||
|
'替换封面',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
_cover = null;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
title: const Text(
|
||||||
|
'移除封面',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_pickImg();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
leading: Text(
|
||||||
|
'封面',
|
||||||
|
style: leadingStyle,
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (_cover?.isNotEmpty == true)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (_, constraints) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
child: Image.network(
|
||||||
|
_cover!,
|
||||||
|
height: constraints.maxHeight,
|
||||||
|
width: constraints.maxHeight * 16 / 9,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.keyboard_arrow_right,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
ListTile(
|
||||||
|
tileColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
leading: Text.rich(
|
||||||
|
style: TextStyle(
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '*',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
height: 1,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '名称',
|
||||||
|
style: TextStyle(
|
||||||
|
height: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: TextField(
|
||||||
|
readOnly: _attr != null && Utils.isDefault(_attr!),
|
||||||
|
controller: _titleController,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: _attr != null && Utils.isDefault(_attr!)
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(21),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
hintText: '名称',
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
gapPadding: 0,
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.all(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (_attr == null || !Utils.isDefault(_attr!)) ...[
|
||||||
|
ListTile(
|
||||||
|
tileColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
title: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '简介',
|
||||||
|
style: leadingStyle,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '*',
|
||||||
|
style: TextStyle(color: Colors.transparent),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
minLines: 6,
|
||||||
|
maxLines: 6,
|
||||||
|
controller: _introController,
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(201),
|
||||||
|
],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
hintText: '简介',
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
gapPadding: 0,
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.all(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPublic = !_isPublic;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tileColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
leading: Text(
|
||||||
|
'公开',
|
||||||
|
style: leadingStyle,
|
||||||
|
),
|
||||||
|
trailing: Transform.scale(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
scale: 0.8,
|
||||||
|
child: Switch(
|
||||||
|
value: _isPublic,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_isPublic = value;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:PiliPalaX/models/user/fav_folder.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||||
@@ -62,7 +63,22 @@ class _FavPanelState extends State<FavPanel> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO
|
Get.toNamed('/createFav')?.then((data) {
|
||||||
|
(widget.ctr?.favFolderData.value as FavFolderData?)
|
||||||
|
?.list
|
||||||
|
?.insert(
|
||||||
|
1,
|
||||||
|
FavFolderItemData(
|
||||||
|
id: data['id'],
|
||||||
|
fid: data['fid'],
|
||||||
|
attr: data['attr'],
|
||||||
|
title: data['title'],
|
||||||
|
favState: data['fav_state'],
|
||||||
|
mediaCount: data['media_count'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
widget.ctr?.favFolderData.refresh();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'package:PiliPalaX/pages/member/new/member_page.dart';
|
import 'package:PiliPalaX/pages/member/new/member_page.dart';
|
||||||
import 'package:PiliPalaX/pages/setting/sponsor_block_page.dart';
|
import 'package:PiliPalaX/pages/setting/sponsor_block_page.dart';
|
||||||
|
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/create_fav_page.dart';
|
||||||
import 'package:PiliPalaX/pages/webview/webview_page.dart';
|
import 'package:PiliPalaX/pages/webview/webview_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -186,6 +187,7 @@ class Routes {
|
|||||||
// 弹幕屏蔽管理
|
// 弹幕屏蔽管理
|
||||||
CustomGetPage(name: '/danmakuBlock', page: () => const DanmakuBlockPage()),
|
CustomGetPage(name: '/danmakuBlock', page: () => const DanmakuBlockPage()),
|
||||||
CustomGetPage(name: '/sponsorBlock', page: () => const SponsorBlockPage()),
|
CustomGetPage(name: '/sponsorBlock', page: () => const SponsorBlockPage()),
|
||||||
|
CustomGetPage(name: '/createFav', page: () => const CreateFavPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:PiliPalaX/common/widgets/pair.dart';
|
import 'package:PiliPalaX/common/widgets/pair.dart';
|
||||||
import 'package:PiliPalaX/main.dart';
|
|
||||||
import 'package:PiliPalaX/models/common/theme_type.dart';
|
import 'package:PiliPalaX/models/common/theme_type.dart';
|
||||||
import 'package:PiliPalaX/pages/video/detail/controller.dart'
|
import 'package:PiliPalaX/pages/video/detail/controller.dart'
|
||||||
show SegmentType, SegmentTypeExt, SkipType;
|
show SegmentType, SegmentTypeExt, SkipType;
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ import '../models/github/latest.dart';
|
|||||||
class Utils {
|
class Utils {
|
||||||
static final Random random = Random();
|
static final Random random = Random();
|
||||||
|
|
||||||
|
static bool isDefault(int attr) {
|
||||||
|
return (attr & 2) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static bool isPublic(int attr) {
|
static bool isPublic(int attr) {
|
||||||
return (attr & 1) == 0;
|
return (attr & 1) == 0;
|
||||||
}
|
}
|
||||||
@@ -119,10 +123,10 @@ class Utils {
|
|||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
minChildSize: 0,
|
minChildSize: 0,
|
||||||
maxChildSize: 1,
|
maxChildSize: 1,
|
||||||
initialChildSize: 0.6,
|
initialChildSize: 0.7,
|
||||||
snap: true,
|
snap: true,
|
||||||
expand: false,
|
expand: false,
|
||||||
snapSizes: const [0.6],
|
snapSizes: const [0.7],
|
||||||
builder: (BuildContext context,
|
builder: (BuildContext context,
|
||||||
ScrollController scrollController) {
|
ScrollController scrollController) {
|
||||||
return GroupPanel(
|
return GroupPanel(
|
||||||
|
|||||||
24
pubspec.lock
24
pubspec.lock
@@ -949,6 +949,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.0"
|
version: "4.3.0"
|
||||||
|
image_cropper:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_cropper
|
||||||
|
sha256: fe37d9a129411486e0d93089b61bd326d05b89e78ad4981de54b560725bf5bd5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.2"
|
||||||
|
image_cropper_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_cropper_for_web
|
||||||
|
sha256: "34256c8fb7fcb233251787c876bb37271744459b593a948a2db73caa323034d0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
|
image_cropper_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_cropper_platform_interface
|
||||||
|
sha256: e8e9d2ca36360387aee39295ce49029362ae4df3071f23e8e71f2b81e40b7531
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ dependencies:
|
|||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
grpc: ^4.0.1
|
grpc: ^4.0.1
|
||||||
flutter_svg: ^2.0.10+1
|
flutter_svg: ^2.0.10+1
|
||||||
|
image_cropper: ^8.0.2
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
screen_brightness: ^2.0.0+2
|
screen_brightness: ^2.0.0+2
|
||||||
|
|||||||
Reference in New Issue
Block a user