feat: reply with pics

This commit is contained in:
bggRGjQaUbCoE
2024-09-30 20:43:54 +08:00
parent 33b9a93620
commit 5b2412ae5b
4 changed files with 126 additions and 10 deletions

View File

@@ -41,6 +41,7 @@
## feat
- [x] 带图评论
- [x] 视频TAG
- [x] 筛选搜索
- [x] 转发动态

View File

@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:dio/dio.dart';
@@ -515,18 +516,29 @@ class VideoHttp {
required String message,
int? root,
int? parent,
List? pictures,
}) async {
if (message == '') {
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
}
var res = await Request().post(Api.replyAdd, queryParameters: {
Map<String, dynamic> data = {
'type': type.index,
'oid': oid,
'root': root == null || root == 0 ? '' : root,
'parent': parent == null || parent == 0 ? '' : parent,
'message': message,
if (pictures != null) 'pictures': jsonEncode(pictures),
'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());
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};

View File

@@ -222,7 +222,6 @@ class _CreatePanelState extends State<CreatePanel> {
bool _isEnable = false;
final _isEnableStream = StreamController<bool>();
late final _imagePicker = ImagePicker();
late final _pics = [];
late final _pathList = <String>[];
late final _pathStream = StreamController<List<String>>();
@@ -244,12 +243,13 @@ class _CreatePanelState extends State<CreatePanel> {
SmartDialog.showToast(result['msg']);
}
} else {
final pics = [];
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();
_pics.add({
pics.add({
'img_width': result['data']['image_width'],
'img_height': result['data']['image_height'],
'img_size': imageSize / 1024,
@@ -267,7 +267,7 @@ class _CreatePanelState extends State<CreatePanel> {
dynamic result = await MsgHttp.createDynamic(
mid: GStorage.userInfo.get('userInfoCache').mid,
rawText: _ctr.text,
pics: _pics,
pics: pics,
);
if (result['status']) {
Get.back();
@@ -343,7 +343,8 @@ class _CreatePanelState extends State<CreatePanel> {
maxLines: 8,
autofocus: true,
onChanged: (value) {
bool isEmpty = value.replaceAll('\n', '').isEmpty;
bool isEmpty =
value.replaceAll('\n', '').isEmpty && _pathList.isEmpty;
if (!isEmpty && !_isEnable) {
_isEnable = true;
_isEnableStream.add(true);
@@ -372,7 +373,7 @@ class _CreatePanelState extends State<CreatePanel> {
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
padding: const EdgeInsets.symmetric(horizontal: 15),
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _pathList.length == 9 ? 9 : _pathList.length + 1,
itemBuilder: (context, index) {
if (_pathList.length != 9 && index == _pathList.length) {
@@ -418,7 +419,6 @@ class _CreatePanelState extends State<CreatePanel> {
} else {
return GestureDetector(
onTap: () {
_pics.clear();
_pathList.removeAt(index);
_pathStream.add(_pathList);
if (_pathList.isEmpty &&

View File

@@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:PiliPalaX/http/msg.dart';
import 'package:chat_bottom_container/chat_bottom_container.dart';
import 'package:flutter/material.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/pages/emote/view.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 }
@@ -55,6 +58,9 @@ class _ReplyPageState extends State<ReplyPage>
final _publishStream = StreamController<bool>();
bool _selectKeyboard = true;
final _keyboardStream = StreamController<bool>.broadcast();
late final _imagePicker = ImagePicker();
late final _pathStream = StreamController<List<String>>();
late final _pathList = <String>[];
@override
void initState() {
@@ -74,6 +80,8 @@ class _ReplyPageState extends State<ReplyPage>
@override
void dispose() async {
_keyboardStream.close();
_pathStream.close();
_publishStream.close();
_readOnlyStream.close();
_enableSend.close();
@@ -105,6 +113,7 @@ class _ReplyPageState extends State<ReplyPage>
),
),
_buildInputView(),
_buildImagePreview(),
_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() {
return Container(
clipBehavior: Clip.hardEdge,
@@ -200,10 +248,11 @@ class _ReplyPageState extends State<ReplyPage>
autofocus: false,
readOnly: snapshot.data ?? false,
onChanged: (value) {
if (value.isNotEmpty && !_enablePublish) {
bool isEmpty = value.replaceAll('\n', '').isEmpty;
if (!isEmpty && !_enablePublish) {
_enablePublish = true;
_publishStream.add(true);
} else if (value.isEmpty && _enablePublish) {
} else if (isEmpty && _enablePublish) {
_enablePublish = false;
_publishStream.add(false);
}
@@ -265,6 +314,35 @@ class _ReplyPageState extends State<ReplyPage>
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(),
StreamBuilder(
initialData: _enablePublish,
@@ -350,6 +428,30 @@ class _ReplyPageState extends State<ReplyPage>
Future submitReplyAdd() async {
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;
var result = await VideoHttp.replyAdd(
type: widget.replyType ?? ReplyType.video,
@@ -359,6 +461,7 @@ class _ReplyPageState extends State<ReplyPage>
message: widget.replyItem != null && widget.replyItem!.root != 0
? ' 回复 @${widget.replyItem!.member!.uname!} : $message'
: message,
pictures: pictures,
);
if (result['status']) {
SmartDialog.showToast(result['data']['success_toast']);