feat: edit dyn

feat: set pub setting

feat: set reply interaction

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-01-13 10:16:45 +08:00
parent 4a2679a589
commit cb58822009
16 changed files with 826 additions and 131 deletions

View File

@@ -7,20 +7,28 @@ import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/models/common/publish_panel_type.dart';
import 'package:PiliPlus/models/dynamics/result.dart'
show PicModel, FilePicModel, OpusPicModel;
import 'package:PiliPlus/models_new/dynamic/dyn_mention/item.dart';
import 'package:PiliPlus/models_new/emote/emote.dart' as e;
import 'package:PiliPlus/models_new/live/live_emote/emoticon.dart';
import 'package:PiliPlus/pages/common/publish/common_publish_page.dart';
import 'package:PiliPlus/pages/dynamics_mention/view.dart';
import 'package:PiliPlus/utils/extension/file_ext.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart' show CancelToken;
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
@@ -31,19 +39,21 @@ abstract class CommonRichTextPubPage
const CommonRichTextPubPage({
super.key,
this.items,
this.pics,
super.onSave,
super.autofocus,
super.imageLengthLimit,
});
final List<RichTextItem>? items;
final List<PicModel>? pics;
}
abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
extends CommonPublishPageState<T> {
final key = GlobalKey<RichTextFieldState>();
late final imagePicker = ImagePicker();
late final RxList<String> pathList = <String>[].obs;
late final RxList<PicModel> imageList;
int get limit => widget.imageLengthLimit ?? 9;
@override
@@ -58,13 +68,16 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
if (editController.rawText.trim().isNotEmpty) {
enablePublish.value = true;
}
imageList = RxList<PicModel>(widget.pics ?? <PicModel>[]);
}
@override
void dispose() {
if (PlatformUtils.isMobile) {
for (final i in pathList) {
File(i).tryDel();
for (final img in imageList) {
if (img is FilePicModel) {
File(img.path).tryDel();
}
}
}
super.dispose();
@@ -82,15 +95,18 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
).colorScheme.secondaryContainer.withValues(alpha: 0.5);
void onClear() {
final path = pathList.removeAt(index);
final image = imageList.removeAt(index);
if (PlatformUtils.isMobile) {
File(path).tryDel();
if (image is FilePicModel) {
File(image.path).tryDel();
}
}
if (pathList.isEmpty && editController.rawText.trim().isEmpty) {
if (imageList.isEmpty && editController.rawText.trim().isEmpty) {
enablePublish.value = false;
}
}
final image = imageList[index];
return Stack(
clipBehavior: Clip.none,
children: [
@@ -98,12 +114,18 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
onTap: () async {
controller.keepChatPanel();
await PageUtils.imageView(
imgList: pathList
imgList: imageList
.map(
(path) => SourceModel(
url: path,
sourceType: SourceType.fileImage,
),
(img) => switch (img) {
FilePicModel e => SourceModel(
url: e.path,
sourceType: .fileImage,
),
OpusPicModel e => SourceModel(
url: e.url!,
sourceType: .networkImage,
),
},
)
.toList(),
initialPage: index,
@@ -117,21 +139,35 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
onSecondaryTap: PlatformUtils.isMobile ? null : onClear,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: Image(
height: height,
fit: BoxFit.fitHeight,
filterQuality: FilterQuality.low,
image: FileImage(File(pathList[index])),
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 42),
child: switch (image) {
FilePicModel e => Image.file(
File(e.path),
height: height,
filterQuality: .low,
cacheHeight: height.cacheSize(context),
),
OpusPicModel e => CachedNetworkImage(
imageUrl: ImageUtils.thumbnailUrl(e.url!),
height: height,
filterQuality: .low,
memCacheHeight: height.cacheSize(context),
fadeInDuration: .zero,
fadeOutDuration: .zero,
placeholder: (_, _) => const SizedBox(width: 42),
),
},
),
),
),
if (PlatformUtils.isMobile)
if (kDebugMode || PlatformUtils.isMobile)
Positioned(
top: 34,
right: 5,
child: iconButton(
icon: const Icon(Icons.edit),
onPressed: () => onCropImage(index),
onPressed: () => onCropImage(index, image),
size: 24,
iconSize: 14,
bgColor: color,
@@ -152,11 +188,23 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
);
}
Future<void> onCropImage(int index) async {
Future<void> onCropImage(int index, PicModel image) async {
String? path;
switch (image) {
case FilePicModel e:
path = e.path;
case OpusPicModel e:
SmartDialog.showLoading();
final file = (await DefaultCacheManager().getSingleFile(
e.url.http2https,
));
await SmartDialog.dismiss();
path = file.path;
}
if (!mounted || path.isEmpty) return;
late final colorScheme = ColorScheme.of(context);
final sourcePath = pathList[index];
final croppedFile = await ImageCropper.platform.cropImage(
sourcePath: sourcePath,
sourcePath: path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: '裁剪',
@@ -168,8 +216,10 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
],
);
if (croppedFile != null) {
File(sourcePath).tryDel();
pathList[index] = croppedFile.path;
if (image is FilePicModel) {
File(image.path).tryDel();
}
imageList[index] = FilePicModel(path: croppedFile.path);
}
}
@@ -185,11 +235,11 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
);
if (pickedFiles.isNotEmpty) {
for (int i = 0; i < pickedFiles.length; i++) {
if (pathList.length == limit) {
if (imageList.length == limit) {
SmartDialog.showToast('最多选择$limit张图片');
break;
} else {
pathList.add(pickedFiles[i].path);
imageList.add(FilePicModel(path: pickedFiles[i].path));
}
}
callback?.call();
@@ -465,25 +515,30 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
Future<void> onPublish() async {
feedBack();
List<Map<String, dynamic>>? pictures;
if (pathList.isNotEmpty) {
if (imageList.isNotEmpty) {
SmartDialog.showLoading(msg: '正在上传图片...');
final cancelToken = CancelToken();
try {
pictures = await Future.wait<Map<String, dynamic>>(
pathList.map((path) async {
final result = await MsgHttp.uploadBfs(
path: path,
category: 'daily',
biz: 'new_dyn',
cancelToken: cancelToken,
);
final data = result.data;
return {
'img_width': data.imageWidth,
'img_height': data.imageHeight,
'img_size': data.imgSize,
'img_src': data.imageUrl,
};
imageList.map((img) async {
switch (img) {
case FilePicModel e:
final result = await MsgHttp.uploadBfs(
path: e.path,
category: 'daily',
biz: 'new_dyn',
cancelToken: cancelToken,
);
final data = result.data;
return {
'img_width': data.imageWidth,
'img_height': data.imageHeight,
'img_size': data.imgSize,
'img_src': data.imageUrl,
};
case OpusPicModel e:
return e.toJson();
}
}),
eagerError: true,
);