mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-21 19:28:27 +08:00
@@ -17,7 +17,10 @@ mixin ScrollOrRefreshMixin {
|
||||
if (scrollController.hasClients) {
|
||||
if (scrollController.position.pixels == 0) {
|
||||
EasyThrottle.throttle(
|
||||
'topOrRefresh', const Duration(milliseconds: 500), onRefresh);
|
||||
'topOrRefresh',
|
||||
const Duration(milliseconds: 500),
|
||||
onRefresh,
|
||||
);
|
||||
} else {
|
||||
animateToTop();
|
||||
}
|
||||
|
||||
@@ -71,15 +71,16 @@ abstract class CommonSearchPageState<S extends CommonSearchPage, R, T>
|
||||
Widget _buildBody(LoadingState<List<T>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => const HttpError(),
|
||||
Success(:var response) => response?.isNotEmpty == true
|
||||
? buildList(response!)
|
||||
: HttpError(
|
||||
onReload: controller.onReload,
|
||||
),
|
||||
Success(:var response) =>
|
||||
response?.isNotEmpty == true
|
||||
? buildList(response!)
|
||||
: HttpError(
|
||||
onReload: controller.onReload,
|
||||
),
|
||||
Error(:var errMsg) => HttpError(
|
||||
errMsg: errMsg,
|
||||
onReload: controller.onReload,
|
||||
),
|
||||
errMsg: errMsg,
|
||||
onReload: controller.onReload,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@ abstract class CommonSlidePageState<T extends CommonSlidePage> extends State<T>
|
||||
vsync: this,
|
||||
reverseDuration: const Duration(milliseconds: 500),
|
||||
);
|
||||
_anim = Tween<Offset>(begin: Offset.zero, end: const Offset(0, 1))
|
||||
.animate(_animController!);
|
||||
_anim = Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: const Offset(0, 1),
|
||||
).animate(_animController!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,63 +59,63 @@ abstract class CommonSlidePageState<T extends CommonSlidePage> extends State<T>
|
||||
Widget buildList(ThemeData theme) => throw UnimplementedError();
|
||||
|
||||
Widget slideList(ThemeData theme) => LayoutBuilder(
|
||||
builder: (_, constrains) {
|
||||
final maxWidth = constrains.maxWidth;
|
||||
builder: (_, constrains) {
|
||||
final maxWidth = constrains.maxWidth;
|
||||
|
||||
void onDismiss() {
|
||||
if (isSliding == true) {
|
||||
if (_animController!.value * maxWidth + downPos!.dx >= 100) {
|
||||
Get.back();
|
||||
} else {
|
||||
_animController!.reverse();
|
||||
}
|
||||
}
|
||||
downPos = null;
|
||||
isSliding = null;
|
||||
void onDismiss() {
|
||||
if (isSliding == true) {
|
||||
if (_animController!.value * maxWidth + downPos!.dx >= 100) {
|
||||
Get.back();
|
||||
} else {
|
||||
_animController!.reverse();
|
||||
}
|
||||
}
|
||||
downPos = null;
|
||||
isSliding = null;
|
||||
}
|
||||
|
||||
void onPan(Offset localPosition) {
|
||||
if (isSliding == false) {
|
||||
return;
|
||||
} else if (isSliding == null) {
|
||||
if (downPos != null) {
|
||||
Offset cumulativeDelta = localPosition - downPos!;
|
||||
if (cumulativeDelta.dx.abs() >= cumulativeDelta.dy.abs()) {
|
||||
downPos = localPosition;
|
||||
isSliding = true;
|
||||
} else {
|
||||
isSliding = false;
|
||||
}
|
||||
}
|
||||
} else if (isSliding == true) {
|
||||
if (localPosition.dx < 0) {
|
||||
return;
|
||||
}
|
||||
_animController!.value =
|
||||
max(0, (localPosition.dx - downPos!.dx)) / maxWidth;
|
||||
void onPan(Offset localPosition) {
|
||||
if (isSliding == false) {
|
||||
return;
|
||||
} else if (isSliding == null) {
|
||||
if (downPos != null) {
|
||||
Offset cumulativeDelta = localPosition - downPos!;
|
||||
if (cumulativeDelta.dx.abs() >= cumulativeDelta.dy.abs()) {
|
||||
downPos = localPosition;
|
||||
isSliding = true;
|
||||
} else {
|
||||
isSliding = false;
|
||||
}
|
||||
}
|
||||
} else if (isSliding == true) {
|
||||
if (localPosition.dx < 0) {
|
||||
return;
|
||||
}
|
||||
_animController!.value =
|
||||
max(0, (localPosition.dx - downPos!.dx)) / maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onPanDown: (details) {
|
||||
if (details.localPosition.dx > 30) {
|
||||
isSliding = false;
|
||||
} else {
|
||||
downPos = details.localPosition;
|
||||
}
|
||||
},
|
||||
onPanStart: (details) {
|
||||
onPan(details.localPosition);
|
||||
},
|
||||
onPanUpdate: (details) {
|
||||
onPan(details.localPosition);
|
||||
},
|
||||
onPanCancel: onDismiss,
|
||||
onPanEnd: (_) {
|
||||
onDismiss();
|
||||
},
|
||||
child: buildList(theme),
|
||||
);
|
||||
return GestureDetector(
|
||||
onPanDown: (details) {
|
||||
if (details.localPosition.dx > 30) {
|
||||
isSliding = false;
|
||||
} else {
|
||||
downPos = details.localPosition;
|
||||
}
|
||||
},
|
||||
onPanStart: (details) {
|
||||
onPan(details.localPosition);
|
||||
},
|
||||
onPanUpdate: (details) {
|
||||
onPan(details.localPosition);
|
||||
},
|
||||
onPanCancel: onDismiss,
|
||||
onPanEnd: (_) {
|
||||
onDismiss();
|
||||
},
|
||||
child: buildList(theme),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,11 @@ abstract class CommonWhisperController<R>
|
||||
}
|
||||
|
||||
Future<void> onSetTop(
|
||||
Session item, int index, bool isTop, SessionId sessionId) async {
|
||||
Session item,
|
||||
int index,
|
||||
bool isTop,
|
||||
SessionId sessionId,
|
||||
) async {
|
||||
var res = isTop
|
||||
? await ImGrpc.unpinSession(sessionId: sessionId)
|
||||
: await ImGrpc.pinSession(sessionId: sessionId);
|
||||
|
||||
@@ -29,7 +29,8 @@ abstract class CommonPublishPage<T> extends StatefulWidget {
|
||||
}
|
||||
|
||||
abstract class CommonPublishPageState<T extends CommonPublishPage>
|
||||
extends State<T> with WidgetsBindingObserver {
|
||||
extends State<T>
|
||||
with WidgetsBindingObserver {
|
||||
late final focusNode = FocusNode();
|
||||
late final controller = ChatBottomPanelContainerController<PanelType>();
|
||||
TextEditingController get editController;
|
||||
@@ -218,23 +219,24 @@ abstract class CommonPublishPageState<T extends CommonPublishPage>
|
||||
final cancelToken = CancelToken();
|
||||
try {
|
||||
pictures = await Future.wait<Map<String, dynamic>>(
|
||||
pathList.map((path) async {
|
||||
Map result = await MsgHttp.uploadBfs(
|
||||
path: path,
|
||||
category: 'daily',
|
||||
biz: 'new_dyn',
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
if (!result['status']) throw HttpException(result['msg']);
|
||||
UploadBfsResData data = result['data'];
|
||||
return {
|
||||
'img_width': data.imageWidth,
|
||||
'img_height': data.imageHeight,
|
||||
'img_size': data.imgSize,
|
||||
'img_src': data.imageUrl,
|
||||
};
|
||||
}).toList(),
|
||||
eagerError: true);
|
||||
pathList.map((path) async {
|
||||
Map result = await MsgHttp.uploadBfs(
|
||||
path: path,
|
||||
category: 'daily',
|
||||
biz: 'new_dyn',
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
if (!result['status']) throw HttpException(result['msg']);
|
||||
UploadBfsResData data = result['data'];
|
||||
return {
|
||||
'img_width': data.imageWidth,
|
||||
'img_height': data.imageHeight,
|
||||
'img_size': data.imgSize,
|
||||
'img_src': data.imageUrl,
|
||||
};
|
||||
}).toList(),
|
||||
eagerError: true,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
} on HttpException catch (e) {
|
||||
cancelToken.cancel();
|
||||
|
||||
@@ -40,9 +40,9 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||
@override
|
||||
late final RichTextEditingController editController =
|
||||
RichTextEditingController(
|
||||
items: widget.items,
|
||||
onMention: onMention,
|
||||
);
|
||||
items: widget.items,
|
||||
onMention: onMention,
|
||||
);
|
||||
|
||||
@override
|
||||
void initPubState() {
|
||||
@@ -58,8 +58,9 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||
}
|
||||
|
||||
Widget buildImage(int index, double height) {
|
||||
final color =
|
||||
Theme.of(context).colorScheme.secondaryContainer.withValues(alpha: 0.5);
|
||||
final color = Theme.of(
|
||||
context,
|
||||
).colorScheme.secondaryContainer.withValues(alpha: 0.5);
|
||||
|
||||
void onClear() {
|
||||
pathList.removeAt(index);
|
||||
@@ -76,10 +77,12 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||
controller.keepChatPanel();
|
||||
await context.imageView(
|
||||
imgList: pathList
|
||||
.map((path) => SourceModel(
|
||||
url: path,
|
||||
sourceType: SourceType.fileImage,
|
||||
))
|
||||
.map(
|
||||
(path) => SourceModel(
|
||||
url: path,
|
||||
sourceType: SourceType.fileImage,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
initialPage: index,
|
||||
);
|
||||
@@ -145,28 +148,31 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||
}
|
||||
|
||||
void onPickImage([VoidCallback? callback]) {
|
||||
EasyThrottle.throttle('imagePicker', const Duration(milliseconds: 500),
|
||||
() async {
|
||||
try {
|
||||
List<XFile> pickedFiles = await imagePicker.pickMultiImage(
|
||||
limit: limit,
|
||||
imageQuality: 100,
|
||||
);
|
||||
if (pickedFiles.isNotEmpty) {
|
||||
for (int i = 0; i < pickedFiles.length; i++) {
|
||||
if (pathList.length == limit) {
|
||||
SmartDialog.showToast('最多选择$limit张图片');
|
||||
break;
|
||||
} else {
|
||||
pathList.add(pickedFiles[i].path);
|
||||
EasyThrottle.throttle(
|
||||
'imagePicker',
|
||||
const Duration(milliseconds: 500),
|
||||
() async {
|
||||
try {
|
||||
List<XFile> pickedFiles = await imagePicker.pickMultiImage(
|
||||
limit: limit,
|
||||
imageQuality: 100,
|
||||
);
|
||||
if (pickedFiles.isNotEmpty) {
|
||||
for (int i = 0; i < pickedFiles.length; i++) {
|
||||
if (pathList.length == limit) {
|
||||
SmartDialog.showToast('最多选择$limit张图片');
|
||||
break;
|
||||
} else {
|
||||
pathList.add(pickedFiles[i].path);
|
||||
}
|
||||
}
|
||||
callback?.call();
|
||||
}
|
||||
callback?.call();
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void onChooseEmote(dynamic emote, double? width, double? height) {
|
||||
@@ -294,8 +300,10 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||
delta = RichTextEditingDeltaReplacement(
|
||||
oldText: oldValue.text,
|
||||
replacementText: text,
|
||||
replacedRange:
|
||||
TextRange(start: selection.start - 1, end: selection.end),
|
||||
replacedRange: TextRange(
|
||||
start: selection.start - 1,
|
||||
end: selection.end,
|
||||
),
|
||||
selection: TextSelection.collapsed(
|
||||
offset: selection.start - 1 + text.length,
|
||||
),
|
||||
@@ -376,49 +384,49 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
|
||||
}
|
||||
|
||||
Widget get emojiBtn => Obx(
|
||||
() {
|
||||
final isEmoji = panelType.value == PanelType.emoji;
|
||||
return ToolbarIconButton(
|
||||
tooltip: isEmoji ? '输入' : '表情',
|
||||
onPressed: () {
|
||||
if (isEmoji) {
|
||||
updatePanelType(PanelType.keyboard);
|
||||
} else {
|
||||
updatePanelType(PanelType.emoji);
|
||||
}
|
||||
},
|
||||
icon: isEmoji
|
||||
? const Icon(Icons.keyboard, size: 22)
|
||||
: const Icon(Icons.emoji_emotions, size: 22),
|
||||
selected: isEmoji,
|
||||
);
|
||||
() {
|
||||
final isEmoji = panelType.value == PanelType.emoji;
|
||||
return ToolbarIconButton(
|
||||
tooltip: isEmoji ? '输入' : '表情',
|
||||
onPressed: () {
|
||||
if (isEmoji) {
|
||||
updatePanelType(PanelType.keyboard);
|
||||
} else {
|
||||
updatePanelType(PanelType.emoji);
|
||||
}
|
||||
},
|
||||
icon: isEmoji
|
||||
? const Icon(Icons.keyboard, size: 22)
|
||||
: const Icon(Icons.emoji_emotions, size: 22),
|
||||
selected: isEmoji,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Widget get atBtn => ToolbarIconButton(
|
||||
onPressed: () => onMention(true),
|
||||
icon: const Icon(Icons.alternate_email, size: 22),
|
||||
tooltip: '@',
|
||||
selected: false,
|
||||
);
|
||||
onPressed: () => onMention(true),
|
||||
icon: const Icon(Icons.alternate_email, size: 22),
|
||||
tooltip: '@',
|
||||
selected: false,
|
||||
);
|
||||
|
||||
Widget get moreBtn => Obx(
|
||||
() {
|
||||
final isMore = panelType.value == PanelType.more;
|
||||
return ToolbarIconButton(
|
||||
tooltip: isMore ? '输入' : '更多',
|
||||
onPressed: () {
|
||||
if (isMore) {
|
||||
updatePanelType(PanelType.keyboard);
|
||||
} else {
|
||||
updatePanelType(PanelType.more);
|
||||
}
|
||||
},
|
||||
icon: isMore
|
||||
? const Icon(Icons.keyboard, size: 22)
|
||||
: const Icon(Icons.add_circle_outline, size: 22),
|
||||
selected: isMore,
|
||||
);
|
||||
() {
|
||||
final isMore = panelType.value == PanelType.more;
|
||||
return ToolbarIconButton(
|
||||
tooltip: isMore ? '输入' : '更多',
|
||||
onPressed: () {
|
||||
if (isMore) {
|
||||
updatePanelType(PanelType.keyboard);
|
||||
} else {
|
||||
updatePanelType(PanelType.more);
|
||||
}
|
||||
},
|
||||
icon: isMore
|
||||
? const Icon(Icons.keyboard, size: 22)
|
||||
: const Icon(Icons.add_circle_outline, size: 22),
|
||||
selected: isMore,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ abstract class CommonTextPubPage extends CommonPublishPage<String> {
|
||||
abstract class CommonTextPubPageState<T extends CommonTextPubPage>
|
||||
extends CommonPublishPageState<T> {
|
||||
@override
|
||||
late final TextEditingController editController =
|
||||
TextEditingController(text: widget.initialValue);
|
||||
late final TextEditingController editController = TextEditingController(
|
||||
text: widget.initialValue,
|
||||
);
|
||||
|
||||
@override
|
||||
void initPubState() {
|
||||
|
||||
@@ -129,73 +129,75 @@ abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
|
||||
dynamic key = oid ?? replyItem!.oid + replyItem.id;
|
||||
Navigator.of(context)
|
||||
.push(
|
||||
GetDialogRoute(
|
||||
pageBuilder: (buildContext, animation, secondaryAnimation) {
|
||||
return ReplyPage(
|
||||
oid: oid ?? replyItem!.oid.toInt(),
|
||||
root: oid != null ? 0 : replyItem!.id.toInt(),
|
||||
parent: oid != null ? 0 : replyItem!.id.toInt(),
|
||||
replyType: replyItem?.type.toInt() ?? replyType!,
|
||||
replyItem: replyItem,
|
||||
items: savedReplies[key],
|
||||
onSave: (reply) {
|
||||
if (reply.isEmpty) {
|
||||
savedReplies.remove(key);
|
||||
} else {
|
||||
savedReplies[key] = reply.toList();
|
||||
}
|
||||
GetDialogRoute(
|
||||
pageBuilder: (buildContext, animation, secondaryAnimation) {
|
||||
return ReplyPage(
|
||||
oid: oid ?? replyItem!.oid.toInt(),
|
||||
root: oid != null ? 0 : replyItem!.id.toInt(),
|
||||
parent: oid != null ? 0 : replyItem!.id.toInt(),
|
||||
replyType: replyItem?.type.toInt() ?? replyType!,
|
||||
replyItem: replyItem,
|
||||
items: savedReplies[key],
|
||||
onSave: (reply) {
|
||||
if (reply.isEmpty) {
|
||||
savedReplies.remove(key);
|
||||
} else {
|
||||
savedReplies[key] = reply.toList();
|
||||
}
|
||||
},
|
||||
hint: hint,
|
||||
);
|
||||
},
|
||||
hint: hint,
|
||||
);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.linear;
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.linear;
|
||||
|
||||
var tween =
|
||||
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
||||
var tween = Tween(
|
||||
begin: begin,
|
||||
end: end,
|
||||
).chain(CurveTween(curve: curve));
|
||||
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
settings: RouteSettings(arguments: Get.arguments),
|
||||
),
|
||||
)
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
settings: RouteSettings(arguments: Get.arguments),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
(res) {
|
||||
if (res != null) {
|
||||
savedReplies.remove(key);
|
||||
ReplyInfo replyInfo = RequestUtils.replyCast(res);
|
||||
if (loadingState.value.isSuccess) {
|
||||
List<ReplyInfo>? list = loadingState.value.data;
|
||||
if (list == null) {
|
||||
loadingState.value = Success([replyInfo]);
|
||||
} else {
|
||||
if (oid != null) {
|
||||
list.insert(hasUpTop ? 1 : 0, replyInfo);
|
||||
(res) {
|
||||
if (res != null) {
|
||||
savedReplies.remove(key);
|
||||
ReplyInfo replyInfo = RequestUtils.replyCast(res);
|
||||
if (loadingState.value.isSuccess) {
|
||||
List<ReplyInfo>? list = loadingState.value.data;
|
||||
if (list == null) {
|
||||
loadingState.value = Success([replyInfo]);
|
||||
} else {
|
||||
if (oid != null) {
|
||||
list.insert(hasUpTop ? 1 : 0, replyInfo);
|
||||
} else {
|
||||
replyItem!
|
||||
..count += 1
|
||||
..replies.add(replyInfo);
|
||||
}
|
||||
loadingState.refresh();
|
||||
}
|
||||
} else {
|
||||
replyItem!
|
||||
..count += 1
|
||||
..replies.add(replyInfo);
|
||||
loadingState.value = Success([replyInfo]);
|
||||
}
|
||||
loadingState.refresh();
|
||||
}
|
||||
} else {
|
||||
loadingState.value = Success([replyInfo]);
|
||||
}
|
||||
count.value += 1;
|
||||
count.value += 1;
|
||||
|
||||
// check reply
|
||||
if (enableCommAntifraud) {
|
||||
onCheckReply(replyInfo, isManual: false);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
// check reply
|
||||
if (enableCommAntifraud) {
|
||||
onCheckReply(replyInfo, isManual: false);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void onRemove(int index, ReplyInfo item, int? subIndex) {
|
||||
|
||||
Reference in New Issue
Block a user