Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-07-23 16:47:11 +08:00
parent 148e0872b4
commit 418a1e8d39
821 changed files with 29467 additions and 25520 deletions

View File

@@ -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();
}

View File

@@ -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,
),
};
}

View File

@@ -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),
);
},
);
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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,
);
},
);
}

View File

@@ -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() {

View File

@@ -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) {