mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-30 23:58:13 +08:00
feat: reply with pics
This commit is contained in:
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] 带图评论
|
||||||
- [x] 视频TAG
|
- [x] 视频TAG
|
||||||
- [x] 筛选搜索
|
- [x] 筛选搜索
|
||||||
- [x] 转发动态
|
- [x] 转发动态
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'package:PiliPalaX/http/loading_state.dart';
|
import 'package:PiliPalaX/http/loading_state.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
@@ -515,18 +516,29 @@ class VideoHttp {
|
|||||||
required String message,
|
required String message,
|
||||||
int? root,
|
int? root,
|
||||||
int? parent,
|
int? parent,
|
||||||
|
List? pictures,
|
||||||
}) async {
|
}) async {
|
||||||
if (message == '') {
|
if (message == '') {
|
||||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||||
}
|
}
|
||||||
var res = await Request().post(Api.replyAdd, queryParameters: {
|
Map<String, dynamic> data = {
|
||||||
'type': type.index,
|
'type': type.index,
|
||||||
'oid': oid,
|
'oid': oid,
|
||||||
'root': root == null || root == 0 ? '' : root,
|
'root': root == null || root == 0 ? '' : root,
|
||||||
'parent': parent == null || parent == 0 ? '' : parent,
|
'parent': parent == null || parent == 0 ? '' : parent,
|
||||||
'message': message,
|
'message': message,
|
||||||
|
if (pictures != null) 'pictures': jsonEncode(pictures),
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
};
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.replyAdd,
|
||||||
|
data: FormData.fromMap(data),
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Content-Type': Headers.formUrlEncodedContentType,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
log(res.toString());
|
log(res.toString());
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
|||||||
@@ -222,7 +222,6 @@ class _CreatePanelState extends State<CreatePanel> {
|
|||||||
bool _isEnable = false;
|
bool _isEnable = false;
|
||||||
final _isEnableStream = StreamController<bool>();
|
final _isEnableStream = StreamController<bool>();
|
||||||
late final _imagePicker = ImagePicker();
|
late final _imagePicker = ImagePicker();
|
||||||
late final _pics = [];
|
|
||||||
late final _pathList = <String>[];
|
late final _pathList = <String>[];
|
||||||
late final _pathStream = StreamController<List<String>>();
|
late final _pathStream = StreamController<List<String>>();
|
||||||
|
|
||||||
@@ -244,12 +243,13 @@ class _CreatePanelState extends State<CreatePanel> {
|
|||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
final pics = [];
|
||||||
for (int i = 0; i < _pathList.length; i++) {
|
for (int i = 0; i < _pathList.length; i++) {
|
||||||
SmartDialog.showLoading(msg: '正在上传图片: ${i + 1}/${_pathList.length}');
|
SmartDialog.showLoading(msg: '正在上传图片: ${i + 1}/${_pathList.length}');
|
||||||
dynamic result = await MsgHttp.uploadBfs(_pathList[i]);
|
dynamic result = await MsgHttp.uploadBfs(_pathList[i]);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
int imageSize = await File(_pathList[i]).length();
|
int imageSize = await File(_pathList[i]).length();
|
||||||
_pics.add({
|
pics.add({
|
||||||
'img_width': result['data']['image_width'],
|
'img_width': result['data']['image_width'],
|
||||||
'img_height': result['data']['image_height'],
|
'img_height': result['data']['image_height'],
|
||||||
'img_size': imageSize / 1024,
|
'img_size': imageSize / 1024,
|
||||||
@@ -267,7 +267,7 @@ class _CreatePanelState extends State<CreatePanel> {
|
|||||||
dynamic result = await MsgHttp.createDynamic(
|
dynamic result = await MsgHttp.createDynamic(
|
||||||
mid: GStorage.userInfo.get('userInfoCache').mid,
|
mid: GStorage.userInfo.get('userInfoCache').mid,
|
||||||
rawText: _ctr.text,
|
rawText: _ctr.text,
|
||||||
pics: _pics,
|
pics: pics,
|
||||||
);
|
);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -343,7 +343,8 @@ class _CreatePanelState extends State<CreatePanel> {
|
|||||||
maxLines: 8,
|
maxLines: 8,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
bool isEmpty = value.replaceAll('\n', '').isEmpty;
|
bool isEmpty =
|
||||||
|
value.replaceAll('\n', '').isEmpty && _pathList.isEmpty;
|
||||||
if (!isEmpty && !_isEnable) {
|
if (!isEmpty && !_isEnable) {
|
||||||
_isEnable = true;
|
_isEnable = true;
|
||||||
_isEnableStream.add(true);
|
_isEnableStream.add(true);
|
||||||
@@ -372,7 +373,7 @@ class _CreatePanelState extends State<CreatePanel> {
|
|||||||
physics: const AlwaysScrollableScrollPhysics(
|
physics: const AlwaysScrollableScrollPhysics(
|
||||||
parent: BouncingScrollPhysics(),
|
parent: BouncingScrollPhysics(),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
itemCount: _pathList.length == 9 ? 9 : _pathList.length + 1,
|
itemCount: _pathList.length == 9 ? 9 : _pathList.length + 1,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (_pathList.length != 9 && index == _pathList.length) {
|
if (_pathList.length != 9 && index == _pathList.length) {
|
||||||
@@ -418,7 +419,6 @@ class _CreatePanelState extends State<CreatePanel> {
|
|||||||
} else {
|
} else {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_pics.clear();
|
|
||||||
_pathList.removeAt(index);
|
_pathList.removeAt(index);
|
||||||
_pathStream.add(_pathList);
|
_pathStream.add(_pathList);
|
||||||
if (_pathList.isEmpty &&
|
if (_pathList.isEmpty &&
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:PiliPalaX/http/msg.dart';
|
||||||
import 'package:chat_bottom_container/chat_bottom_container.dart';
|
import 'package:chat_bottom_container/chat_bottom_container.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';
|
||||||
@@ -14,6 +16,7 @@ import 'package:PiliPalaX/pages/emote/index.dart';
|
|||||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||||
import 'package:PiliPalaX/pages/emote/view.dart';
|
import 'package:PiliPalaX/pages/emote/view.dart';
|
||||||
import 'package:PiliPalaX/pages/video/detail/reply_new/toolbar_icon_button.dart';
|
import 'package:PiliPalaX/pages/video/detail/reply_new/toolbar_icon_button.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
enum PanelType { none, keyboard, emoji }
|
enum PanelType { none, keyboard, emoji }
|
||||||
|
|
||||||
@@ -55,6 +58,9 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
final _publishStream = StreamController<bool>();
|
final _publishStream = StreamController<bool>();
|
||||||
bool _selectKeyboard = true;
|
bool _selectKeyboard = true;
|
||||||
final _keyboardStream = StreamController<bool>.broadcast();
|
final _keyboardStream = StreamController<bool>.broadcast();
|
||||||
|
late final _imagePicker = ImagePicker();
|
||||||
|
late final _pathStream = StreamController<List<String>>();
|
||||||
|
late final _pathList = <String>[];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -74,6 +80,8 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() async {
|
void dispose() async {
|
||||||
|
_keyboardStream.close();
|
||||||
|
_pathStream.close();
|
||||||
_publishStream.close();
|
_publishStream.close();
|
||||||
_readOnlyStream.close();
|
_readOnlyStream.close();
|
||||||
_enableSend.close();
|
_enableSend.close();
|
||||||
@@ -105,6 +113,7 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildInputView(),
|
_buildInputView(),
|
||||||
|
_buildImagePreview(),
|
||||||
_buildPanelContainer(),
|
_buildPanelContainer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -162,6 +171,45 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildImagePreview() {
|
||||||
|
return StreamBuilder(
|
||||||
|
initialData: const [],
|
||||||
|
stream: _pathStream.stream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
||||||
|
return Container(
|
||||||
|
height: 85,
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
|
child: ListView.separated(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(
|
||||||
|
parent: BouncingScrollPhysics(),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
itemCount: _pathList.length,
|
||||||
|
itemBuilder: (context, index) => GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_pathList.removeAt(index);
|
||||||
|
_pathStream.add(_pathList);
|
||||||
|
},
|
||||||
|
child: Image(
|
||||||
|
height: 75,
|
||||||
|
fit: BoxFit.fitHeight,
|
||||||
|
filterQuality: FilterQuality.low,
|
||||||
|
image: FileImage(File(_pathList[index])),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
separatorBuilder: (_, index) => const SizedBox(width: 10),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildInputView() {
|
Widget _buildInputView() {
|
||||||
return Container(
|
return Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
@@ -200,10 +248,11 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
autofocus: false,
|
autofocus: false,
|
||||||
readOnly: snapshot.data ?? false,
|
readOnly: snapshot.data ?? false,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value.isNotEmpty && !_enablePublish) {
|
bool isEmpty = value.replaceAll('\n', '').isEmpty;
|
||||||
|
if (!isEmpty && !_enablePublish) {
|
||||||
_enablePublish = true;
|
_enablePublish = true;
|
||||||
_publishStream.add(true);
|
_publishStream.add(true);
|
||||||
} else if (value.isEmpty && _enablePublish) {
|
} else if (isEmpty && _enablePublish) {
|
||||||
_enablePublish = false;
|
_enablePublish = false;
|
||||||
_publishStream.add(false);
|
_publishStream.add(false);
|
||||||
}
|
}
|
||||||
@@ -265,6 +314,35 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
selected: !snapshot.data!,
|
selected: !snapshot.data!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
ToolbarIconButton(
|
||||||
|
tooltip: '图片',
|
||||||
|
selected: false,
|
||||||
|
icon: const Icon(Icons.image, size: 22),
|
||||||
|
onPressed: () async {
|
||||||
|
List<XFile> pickedFiles = await _imagePicker.pickMultiImage(
|
||||||
|
limit: 9,
|
||||||
|
imageQuality: 100,
|
||||||
|
);
|
||||||
|
if (pickedFiles.isNotEmpty) {
|
||||||
|
for (int i = 0; i < pickedFiles.length; i++) {
|
||||||
|
if (_pathList.length == 9) {
|
||||||
|
SmartDialog.showToast('最多选择9张图片');
|
||||||
|
if (i != 0) {
|
||||||
|
_pathStream.add(_pathList);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
_pathList.add(pickedFiles[i].path);
|
||||||
|
if (i == pickedFiles.length - 1) {
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
_pathStream.add(_pathList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
initialData: _enablePublish,
|
initialData: _enablePublish,
|
||||||
@@ -350,6 +428,30 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
|
|
||||||
Future submitReplyAdd() async {
|
Future submitReplyAdd() async {
|
||||||
feedBack();
|
feedBack();
|
||||||
|
List? pictures;
|
||||||
|
if (_pathList.isNotEmpty) {
|
||||||
|
pictures = [];
|
||||||
|
for (int i = 0; i < _pathList.length; i++) {
|
||||||
|
SmartDialog.showLoading(msg: '正在上传图片: ${i + 1}/${_pathList.length}');
|
||||||
|
dynamic result = await MsgHttp.uploadBfs(_pathList[i]);
|
||||||
|
if (result['status']) {
|
||||||
|
int imageSize = await File(_pathList[i]).length();
|
||||||
|
pictures.add({
|
||||||
|
'img_width': result['data']['image_width'],
|
||||||
|
'img_height': result['data']['image_height'],
|
||||||
|
'img_size': imageSize / 1024,
|
||||||
|
'img_src': result['data']['image_url'],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i == _pathList.length - 1) {
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
String message = _replyContentController.text;
|
String message = _replyContentController.text;
|
||||||
var result = await VideoHttp.replyAdd(
|
var result = await VideoHttp.replyAdd(
|
||||||
type: widget.replyType ?? ReplyType.video,
|
type: widget.replyType ?? ReplyType.video,
|
||||||
@@ -359,6 +461,7 @@ class _ReplyPageState extends State<ReplyPage>
|
|||||||
message: widget.replyItem != null && widget.replyItem!.root != 0
|
message: widget.replyItem != null && widget.replyItem!.root != 0
|
||||||
? ' 回复 @${widget.replyItem!.member!.uname!} : $message'
|
? ' 回复 @${widget.replyItem!.member!.uname!} : $message'
|
||||||
: message,
|
: message,
|
||||||
|
pictures: pictures,
|
||||||
);
|
);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
SmartDialog.showToast(result['data']['success_toast']);
|
SmartDialog.showToast(result['data']['success_toast']);
|
||||||
|
|||||||
Reference in New Issue
Block a user