opt pub panel

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-05-26 17:11:52 +08:00
parent db3b74e33f
commit 3edac65ae8
49 changed files with 879 additions and 360 deletions

View File

@@ -1,13 +1,22 @@
import 'dart:math';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/button/toolbar_icon_button.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/common/widgets/custom_icon.dart';
import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/models/common/publish_panel_type.dart';
import 'package:PiliPlus/models/common/reply/reply_option_type.dart';
import 'package:PiliPlus/models/topic_pub_search/topic_item.dart';
import 'package:PiliPlus/pages/common/common_publish_page.dart';
import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart';
import 'package:PiliPlus/pages/dynamics_select_topic/view.dart';
import 'package:PiliPlus/pages/emote/controller.dart';
import 'package:PiliPlus/pages/emote/view.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
@@ -16,21 +25,29 @@ class CreateDynPanel extends CommonPublishPage {
const CreateDynPanel({
super.key,
super.imageLengthLimit = 18,
this.scrollController,
});
final ScrollController? scrollController;
@override
State<CreateDynPanel> createState() => _CreateDynPanelState();
}
class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
bool _isPrivate = false;
DateTime? _publishTime;
ReplyOptionType _replyOption = ReplyOptionType.allow;
final RxBool _isPrivate = false.obs;
final Rx<DateTime?> _publishTime = Rx<DateTime?>(null);
final Rx<ReplyOptionType> _replyOption = ReplyOptionType.allow.obs;
final _titleEditCtr = TextEditingController();
Rx<Pair<int, String>?> topic = Rx<Pair<int, String>?>(null);
@override
void dispose() {
_titleEditCtr.dispose();
try {
Get.delete<EmotePanelController>();
Get
..delete<EmotePanelController>()
..delete<SelectTopicController>();
} catch (_) {}
super.dispose();
}
@@ -46,30 +63,130 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
child: ListView(
padding: EdgeInsets.zero,
controller: widget.scrollController,
physics: const ClampingScrollPhysics(),
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _buildEditWidget,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Obx(
() {
final hasTopic = topic.value != null;
return Row(
spacing: 10,
children: [
TextButton(
style: TextButton.styleFrom(
overlayColor:
hasTopic ? Colors.transparent : null,
splashFactory:
hasTopic ? NoSplash.splashFactory : null,
shape: hasTopic
? null
: RoundedRectangleBorder(
side: BorderSide(
color: hasTopic
? Colors.transparent
: theme.colorScheme.outline
.withValues(alpha: 0.2),
),
borderRadius: const BorderRadius.all(
Radius.circular(25),
),
),
minimumSize: Size.zero,
padding: hasTopic
? const EdgeInsets.symmetric(vertical: 12)
: const EdgeInsets.all(12),
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: _onSelectTopic,
child: Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(right: 5),
child: Icon(
CustomIcon.topic_tag,
size: 18,
color: hasTopic
? null
: theme.colorScheme.outline,
),
),
),
TextSpan(
text:
hasTopic ? topic.value!.second : '选择话题',
style: TextStyle(
color: hasTopic
? null
: theme.colorScheme.outline,
),
),
],
),
),
),
if (hasTopic)
iconButton(
size: 22,
iconSize: 16,
context: context,
icon: Icons.clear,
bgColor: theme.colorScheme.onInverseSurface,
iconColor: theme.colorScheme.onSurfaceVariant,
onPressed: () => topic.value = null,
),
],
);
},
),
),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextField(
controller: _titleEditCtr,
style: const TextStyle(fontWeight: FontWeight.bold),
decoration: InputDecoration(
hintText: '标题选填20字',
isDense: true,
contentPadding: EdgeInsets.zero,
border: const OutlineInputBorder(
gapPadding: 0,
borderSide: BorderSide.none,
),
hintStyle: TextStyle(
fontWeight: FontWeight.bold,
color: theme.colorScheme.outline.withValues(alpha: 0.7),
),
),
inputFormatters: [LengthLimitingTextInputFormatter(20)],
),
),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _buildEditWidget(theme),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildPubtimeWidget,
Obx(() => _buildPubtimeWidget),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildReplyOptionWidget(theme),
Obx(() => _buildReplyOptionWidget(theme)),
const SizedBox(height: 5),
_buildPrivateWidget(theme),
Obx(() => _buildPrivateWidget(theme)),
],
),
],
@@ -77,7 +194,6 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
),
const SizedBox(height: 10),
_buildImageList(theme),
const SizedBox(height: 2),
],
),
),
@@ -186,7 +302,7 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
),
visualDensity: VisualDensity.compact,
),
child: Text(_publishTime == null ? '发布' : '定时发布'),
child: Text(_publishTime.value == null ? '发布' : '定时发布'),
),
),
),
@@ -196,20 +312,17 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
);
Widget _buildPrivateWidget(ThemeData theme) {
final color =
_isPrivate ? theme.colorScheme.error : theme.colorScheme.secondary;
return PopupMenuButton(
initialValue: _isPrivate,
final color = _isPrivate.value
? theme.colorScheme.error
: theme.colorScheme.secondary;
return PopupMenuButton<bool>(
initialValue: _isPrivate.value,
onOpened: controller.keepChatPanel,
onSelected: (value) {
setState(() {
_isPrivate = value;
});
},
onSelected: (value) => _isPrivate.value = value,
itemBuilder: (context) => List.generate(
2,
(index) => PopupMenuItem<bool>(
enabled: _publishTime != null && index == 1 ? false : true,
enabled: _publishTime.value != null && index == 1 ? false : true,
value: index == 0 ? false : true,
child: Row(
mainAxisSize: MainAxisSize.min,
@@ -231,12 +344,12 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
children: [
Icon(
size: 19,
_isPrivate ? Icons.visibility_off : Icons.visibility,
_isPrivate.value ? Icons.visibility_off : Icons.visibility,
color: color,
),
const SizedBox(width: 4),
Text(
_isPrivate ? '仅自己可见' : '所有人可见',
_isPrivate.value ? '仅自己可见' : '所有人可见',
style: TextStyle(
height: 1,
color: color,
@@ -255,17 +368,13 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
}
Widget _buildReplyOptionWidget(ThemeData theme) {
final color = _replyOption == ReplyOptionType.close
final color = _replyOption.value == ReplyOptionType.close
? theme.colorScheme.error
: theme.colorScheme.secondary;
return PopupMenuButton(
initialValue: _replyOption,
return PopupMenuButton<ReplyOptionType>(
initialValue: _replyOption.value,
onOpened: controller.keepChatPanel,
onSelected: (item) {
setState(() {
_replyOption = item;
});
},
onSelected: (item) => _replyOption.value = item,
itemBuilder: (context) => ReplyOptionType.values
.map(
(item) => PopupMenuItem<ReplyOptionType>(
@@ -291,12 +400,12 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
children: [
Icon(
size: 19,
_replyOption.iconData,
_replyOption.value.iconData,
color: color,
),
const SizedBox(width: 4),
Text(
_replyOption.title,
_replyOption.value.title,
style: TextStyle(
height: 1,
color: color,
@@ -314,7 +423,7 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
);
}
Widget get _buildPubtimeWidget => _publishTime == null
Widget get _buildPubtimeWidget => _publishTime.value == null
? FilledButton.tonal(
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(
@@ -323,7 +432,7 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
),
visualDensity: VisualDensity.compact,
),
onPressed: _isPrivate
onPressed: _isPrivate.value
? null
: () {
DateTime nowDate = DateTime.now();
@@ -363,15 +472,13 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
}
}
}
setState(() {
_publishTime = DateTime(
selectedDate.year,
selectedDate.month,
selectedDate.day,
selectedTime.hour,
selectedTime.minute,
);
});
_publishTime.value = DateTime(
selectedDate.year,
selectedDate.month,
selectedDate.day,
selectedTime.hour,
selectedTime.minute,
);
}
});
}
@@ -388,42 +495,41 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
),
visualDensity: VisualDensity.compact,
),
onPressed: () => setState(() => _publishTime = null),
label: Text(DateFormat('yyyy-MM-dd HH:mm').format(_publishTime!)),
onPressed: () => _publishTime.value = null,
label:
Text(DateFormat('yyyy-MM-dd HH:mm').format(_publishTime.value!)),
icon: const Icon(Icons.clear, size: 20),
iconAlignment: IconAlignment.end,
);
Widget get _buildToolbar => Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Obx(
() => ToolbarIconButton(
onPressed: () {
selectKeyboard.value = PanelType.emoji == currentPanelType;
updatePanelType(
PanelType.emoji == currentPanelType
? PanelType.keyboard
: PanelType.emoji,
);
},
icon: const Icon(Icons.emoji_emotions, size: 22),
tooltip: '表情',
selected: !selectKeyboard.value,
),
Widget get _buildToolbar => GestureDetector(
onTap: hidePanel,
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Obx(
() => ToolbarIconButton(
onPressed: () {
updatePanelType(
panelType.value == PanelType.emoji
? PanelType.keyboard
: PanelType.emoji,
);
},
icon: const Icon(Icons.emoji_emotions, size: 22),
tooltip: '表情',
selected: panelType.value == PanelType.emoji,
),
],
),
),
);
Widget get _buildEditWidget => Form(
Widget _buildEditWidget(ThemeData theme) => Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Listener(
onPointerUp: (event) {
if (readOnly.value) {
updatePanelType(PanelType.keyboard);
selectKeyboard.value = true;
}
},
child: Obx(
@@ -441,14 +547,16 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
enablePublish.value = false;
}
},
decoration: const InputDecoration(
decoration: InputDecoration(
hintText: '说点什么吧',
border: OutlineInputBorder(
hintStyle: TextStyle(color: theme.colorScheme.outline),
border: const OutlineInputBorder(
borderSide: BorderSide.none,
gapPadding: 0,
),
contentPadding: EdgeInsets.zero,
),
inputFormatters: [LengthLimitingTextInputFormatter(1000)],
),
),
),
@@ -461,15 +569,17 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
Future<void> onCustomPublish(
{required String message, List? pictures}) async {
SmartDialog.showLoading(msg: '正在发布');
dynamic result = await MsgHttp.createDynamic(
var result = await DynamicsHttp.createDynamic(
mid: Accounts.main.mid,
rawText: editController.text,
pics: pictures,
publishTime: _publishTime != null
? _publishTime!.millisecondsSinceEpoch ~/ 1000
publishTime: _publishTime.value != null
? _publishTime.value!.millisecondsSinceEpoch ~/ 1000
: null,
replyOption: _replyOption,
privatePub: _isPrivate ? 1 : null,
replyOption: _replyOption.value,
privatePub: _isPrivate.value ? 1 : null,
title: _titleEditCtr.text,
topic: topic.value,
);
SmartDialog.dismiss();
if (result['status']) {
@@ -485,4 +595,28 @@ class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
debugPrint('failed to publish: ${result['msg']}');
}
}
Future<void> _onSelectTopic() async {
TopicPubSearchItem? res = await showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
constraints: BoxConstraints(
maxWidth: min(600, context.mediaQueryShortestSide),
),
builder: (context) => DraggableScrollableSheet(
expand: false,
snap: true,
minChildSize: 0,
maxChildSize: 1,
initialChildSize: 0.65,
snapSizes: [0.65],
builder: (context, scrollController) =>
SelectTopicPanel(scrollController: scrollController),
),
);
if (res != null) {
topic.value = Pair(first: res.id!, second: res.name!);
}
}
}