diff --git a/lib/http/msg.dart b/lib/http/msg.dart index a3309b7a5..de46173fa 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -149,6 +149,7 @@ class MsgHttp { List? pics, int? publishTime, ReplyOption replyOption = ReplyOption.allow, + int? privatePub, }) async { String csrf = await Request.getCsrf(); var res = await Request().post( @@ -181,6 +182,10 @@ class MsgHttp { : pics != null ? 2 : 1, + if (privatePub != null) + 'create_option': { + 'private_pub': privatePub, + }, if (pics != null) 'pics': pics, "attach_card": null, "upload_id": diff --git a/lib/http/search.dart b/lib/http/search.dart index 9f527df75..945f9e344 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -97,7 +97,7 @@ class SearchHttp { if (pubEnd != null) 'pubtime_end_s': pubEnd, }; var res = await Request().get(Api.searchByType, queryParameters: reqData); - if (res.data['code'] == 0) { + if (res.data['code'] is int && res.data['code'] == 0) { dynamic data; try { switch (searchType) { diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 7a5f19bec..93a0d29b1 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -14,6 +14,7 @@ import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/storage.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:nil/nil.dart'; import 'controller.dart'; @@ -23,6 +24,12 @@ enum ReplyOption { allow, close, choose } extension ReplyOptionExtension on ReplyOption { String get title => ['允许评论', '关闭评论', '精选评论'][index]; + + IconData get iconData => [ + MdiIcons.commentTextOutline, + MdiIcons.commentOffOutline, + MdiIcons.commentProcessingOutline, + ][index]; } class DynamicsPage extends StatefulWidget { @@ -241,21 +248,18 @@ class CreatePanel extends StatefulWidget { class _CreatePanelState extends State { final _ctr = TextEditingController(); - bool _isEnable = false; - final _isEnableStream = StreamController(); late final _imagePicker = ImagePicker(); - late final _pathList = []; - late final _pathStream = StreamController>(); + late final int _limit = 18; + final RxBool _isEnablePub = false.obs; + late final RxList _pathList = [].obs; + + bool _isPrivate = false; DateTime? _publishTime; ReplyOption _replyOption = ReplyOption.allow; - late final int _limit = 18; - @override void dispose() { - _isEnableStream.close(); - _pathStream.close(); _ctr.dispose(); super.dispose(); } @@ -298,6 +302,7 @@ class _CreatePanelState extends State { } } } + SmartDialog.showLoading(msg: '正在发布'); dynamic result = await MsgHttp.createDynamic( mid: GStorage.userInfo.get('userInfoCache')?.mid, rawText: _ctr.text, @@ -306,11 +311,14 @@ class _CreatePanelState extends State { ? _publishTime!.millisecondsSinceEpoch ~/ 1000 : null, replyOption: _replyOption, + privatePub: _isPrivate ? 1 : null, ); if (result['status']) { Get.back(); + SmartDialog.dismiss(); SmartDialog.showToast('发布成功'); } else { + SmartDialog.dismiss(); SmartDialog.showToast(result['msg']); } // } @@ -318,312 +326,425 @@ class _CreatePanelState extends State { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 16), - Row( - children: [ - const SizedBox(width: 16), - SizedBox( - width: 34, - height: 34, - child: IconButton( - tooltip: '返回', - style: ButtonStyle( - padding: WidgetStateProperty.all(EdgeInsets.zero), - backgroundColor: WidgetStateProperty.resolveWith( - (states) { - return Theme.of(context).colorScheme.secondaryContainer; - }, - ), - ), - onPressed: Get.back, - icon: Icon( - Icons.arrow_back_outlined, - size: 18, - color: Theme.of(context).colorScheme.onSecondaryContainer, - ), - ), - ), - const Spacer(), - const Text( - '发布动态', - style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), - ), - const Spacer(), - StreamBuilder( - initialData: false, - stream: _isEnableStream.stream, - builder: (context, snapshot) => FilledButton.tonal( - onPressed: snapshot.data == true ? _onCreate : null, - style: FilledButton.styleFrom( - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - visualDensity: const VisualDensity( - horizontal: -2, - vertical: -2, - ), - ), - child: const Text('发布'), - ), - ), - const SizedBox(width: 16), - ], - ), - const SizedBox(width: 10), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: TextField( - controller: _ctr, - minLines: 4, - maxLines: 8, - autofocus: true, - onChanged: (value) { - bool isEmpty = - value.replaceAll('\n', '').isEmpty && _pathList.isEmpty; - if (!isEmpty && !_isEnable) { - _isEnable = true; - _isEnableStream.add(true); - } else if (isEmpty && _isEnable) { - _isEnable = false; - _isEnableStream.add(false); - } - }, - decoration: const InputDecoration( - hintText: '说点什么吧', - border: OutlineInputBorder( - borderSide: BorderSide.none, - gapPadding: 0, - ), - contentPadding: EdgeInsets.symmetric(vertical: 10), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: true, + appBar: PreferredSize( + preferredSize: Size.fromHeight(66), + child: Padding( + padding: const EdgeInsets.only(top: 16, bottom: 16), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _publishTime == null - ? FilledButton.tonal( - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 10, - ), - visualDensity: const VisualDensity( - horizontal: -2, - vertical: -2, - ), - ), - onPressed: () { - DateTime nowDate = DateTime.now(); - showDatePicker( - context: context, - initialDate: nowDate, - firstDate: nowDate, - lastDate: DateTime( - nowDate.year, - nowDate.month, - nowDate.day + 7, - ), - ).then( - (selectedDate) { - if (selectedDate != null && context.mounted) { - if (selectedDate.day == nowDate.day) { - SmartDialog.showToast('至少选择10分钟之后'); - } - TimeOfDay nowTime = TimeOfDay.now(); - showTimePicker( - context: context, - initialTime: nowTime.replacing( - hour: nowTime.minute + 10 >= 60 - ? (nowTime.hour + 1) % 24 - : nowTime.hour, - minute: (nowTime.minute + 10) % 60, - ), - ).then((selectedTime) { - if (selectedTime != null) { - if (selectedDate.day == nowDate.day) { - if (selectedTime.hour < nowTime.hour) { - SmartDialog.showToast('时间设置错误'); - return; - } else if (selectedTime.hour == - nowTime.hour) { - if (selectedTime.minute < - nowTime.minute + 10) { - SmartDialog.showToast('时间设置错误'); - return; - } - } - } - setState(() { - _publishTime = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - selectedTime.hour, - selectedTime.minute, - ); - }); - } - }); - } - }, - ); + const SizedBox(width: 16), + SizedBox( + width: 34, + height: 34, + child: IconButton( + tooltip: '返回', + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + backgroundColor: WidgetStateProperty.resolveWith( + (states) { + return Theme.of(context).colorScheme.secondaryContainer; }, - child: const Text('定时发布'), - ) - : OutlinedButton.icon( - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 10, - ), - visualDensity: const VisualDensity( - horizontal: -2, - vertical: -2, - ), - ), - onPressed: () { - setState(() { - _publishTime = null; - }); - }, - label: Text( - DateFormat('yyyy-MM-dd HH:mm').format(_publishTime!)), - icon: Icon(Icons.clear, size: 20), - iconAlignment: IconAlignment.end, ), - PopupMenuButton( - initialValue: _replyOption, - onSelected: (item) { - setState(() { - _replyOption = item; - }); - }, - itemBuilder: (context) => ReplyOption.values - .map((item) => PopupMenuItem( - value: item, - child: Text(item.title), - )) - .toList(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - _replyOption.title, - style: TextStyle( - height: 1, - color: Theme.of(context).colorScheme.primary, - ), - strutStyle: StrutStyle(leading: 0, height: 1), - ), - Icon( - size: 20, - Icons.keyboard_arrow_right, - color: Theme.of(context).colorScheme.primary, - ) - ], + ), + onPressed: Get.back, + icon: Icon( + Icons.arrow_back_outlined, + size: 18, + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), ), - ) + ), + const Spacer(), + const Text( + '发布动态', + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + ), + const Spacer(), + Obx( + () => FilledButton.tonal( + onPressed: _isEnablePub.value ? _onCreate : null, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + visualDensity: const VisualDensity( + horizontal: -2, + vertical: -2, + ), + ), + child: Text(_publishTime == null ? '发布' : '定时发布'), + ), + ), + const SizedBox(width: 16), ], ), ), - const SizedBox(height: 10), - StreamBuilder( - initialData: const [], - stream: _pathStream.stream, - builder: (context, snapshot) => SizedBox( - height: 75, - child: ListView.separated( - scrollDirection: Axis.horizontal, - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), - ), + ), + body: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - itemCount: - _pathList.length == _limit ? _limit : _pathList.length + 1, - itemBuilder: (context, index) { - if (_pathList.length != _limit && index == _pathList.length) { - return Material( - borderRadius: BorderRadius.circular(12), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { - EasyThrottle.throttle( - 'imagePicker', const Duration(milliseconds: 500), - () async { - try { - List 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张图片'); - if (i != 0) { - _pathStream.add(_pathList); + child: TextField( + controller: _ctr, + minLines: 4, + maxLines: 8, + autofocus: true, + onChanged: (value) { + bool isEmpty = value.trim().isEmpty && _pathList.isEmpty; + if (!isEmpty && !_isEnablePub.value) { + _isEnablePub.value = true; + } else if (isEmpty && _isEnablePub.value) { + _isEnablePub.value = false; + } + }, + decoration: const InputDecoration( + hintText: '说点什么吧', + border: OutlineInputBorder( + borderSide: BorderSide.none, + gapPadding: 0, + ), + contentPadding: EdgeInsets.zero, + ), + ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _publishTime == null + ? FilledButton.tonal( + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + visualDensity: const VisualDensity( + horizontal: -2, + vertical: -2, + ), + ), + onPressed: _isPrivate + ? null + : () { + DateTime nowDate = DateTime.now(); + showDatePicker( + context: context, + initialDate: nowDate, + firstDate: nowDate, + lastDate: DateTime( + nowDate.year, + nowDate.month, + nowDate.day + 7, + ), + ).then( + (selectedDate) { + if (selectedDate != null && + context.mounted) { + TimeOfDay nowTime = TimeOfDay.now(); + showTimePicker( + context: context, + initialTime: nowTime.replacing( + hour: nowTime.minute + 6 >= 60 + ? (nowTime.hour + 1) % 24 + : nowTime.hour, + minute: (nowTime.minute + 6) % 60, + ), + ).then((selectedTime) { + if (selectedTime != null) { + if (selectedDate.day == + nowDate.day) { + if (selectedTime.hour < + nowTime.hour) { + SmartDialog.showToast( + '时间设置错误,至少选择6分钟之后'); + return; + } else if (selectedTime.hour == + nowTime.hour) { + if (selectedTime.minute < + nowTime.minute + 6) { + if (selectedDate.day == + nowDate.day) { + SmartDialog.showToast( + '时间设置错误,至少选择6分钟之后'); + } + return; + } + } + } + setState(() { + _publishTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + }); + } + }); + } + }, + ); + }, + child: const Text('定时发布'), + ) + : OutlinedButton.icon( + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + visualDensity: const VisualDensity( + horizontal: -2, + vertical: -2, + ), + ), + onPressed: () { + setState(() { + _publishTime = null; + }); + }, + label: Text(DateFormat('yyyy-MM-dd HH:mm') + .format(_publishTime!)), + icon: Icon(Icons.clear, size: 20), + iconAlignment: IconAlignment.end, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PopupMenuButton( + enabled: _publishTime == null, + initialValue: _isPrivate, + onSelected: (value) { + setState(() { + _isPrivate = value; + }); + }, + itemBuilder: (context) => List.generate( + 2, + (index) => PopupMenuItem( + value: index == 0 ? false : true, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + size: 19, + index == 0 + ? Icons.visibility + : Icons.visibility_off, + ), + const SizedBox(width: 4), + Text(index == 0 ? '所有人可见' : '仅自己可见'), + ], + ), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + size: 19, + _isPrivate + ? Icons.visibility_off + : Icons.visibility, + color: _publishTime == null + ? _isPrivate + ? Theme.of(context).colorScheme.error + : Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), + const SizedBox(width: 4), + Text( + _isPrivate ? '仅自己可见' : '所有人可见', + style: TextStyle( + height: 1, + color: _publishTime == null + ? _isPrivate + ? Theme.of(context).colorScheme.error + : Theme.of(context) + .colorScheme + .primary + : Theme.of(context).colorScheme.outline, + ), + strutStyle: StrutStyle(leading: 0, height: 1), + ), + Icon( + size: 20, + Icons.keyboard_arrow_right, + color: _publishTime == null + ? _isPrivate + ? Theme.of(context).colorScheme.error + : Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), + ], + ), + ), + ), + const SizedBox(height: 5), + PopupMenuButton( + initialValue: _replyOption, + onSelected: (item) { + setState(() { + _replyOption = item; + }); + }, + itemBuilder: (context) => ReplyOption.values + .map( + (item) => PopupMenuItem( + value: item, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + size: 19, + item.iconData, + ), + const SizedBox(width: 4), + Text(item.title), + ], + ), + ), + ) + .toList(), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + size: 19, + _replyOption.iconData, + color: _replyOption == ReplyOption.close + ? Theme.of(context).colorScheme.error + : Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 4), + Text( + _replyOption.title, + style: TextStyle( + height: 1, + color: _replyOption == ReplyOption.close + ? Theme.of(context).colorScheme.error + : Theme.of(context).colorScheme.primary, + ), + strutStyle: StrutStyle(leading: 0, height: 1), + ), + Icon( + size: 20, + Icons.keyboard_arrow_right, + color: _replyOption == ReplyOption.close + ? Theme.of(context).colorScheme.error + : Theme.of(context).colorScheme.primary, + ), + ], + ), + ), + ), + ], + ), + ], + ), + ), + const SizedBox(height: 10), + Obx( + () => SizedBox( + height: 100, + child: ListView.separated( + scrollDirection: Axis.horizontal, + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: _pathList.length == _limit + ? _limit + : _pathList.length + 1, + itemBuilder: (context, index) { + if (_pathList.length != _limit && + index == _pathList.length) { + return Material( + borderRadius: BorderRadius.circular(12), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + EasyThrottle.throttle('imagePicker', + const Duration(milliseconds: 500), () async { + try { + List 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); + } } - break; - } else { - _pathList.add(pickedFiles[i].path); - if (i == pickedFiles.length - 1) { - _pathStream.add(_pathList); + if (_pathList.isNotEmpty && + !_isEnablePub.value) { + _isEnablePub.value = true; } } + } catch (e) { + SmartDialog.showToast(e.toString()); } - if (_pathList.isNotEmpty && !_isEnable) { - _isEnable = true; - _isEnableStream.add(true); - } - } - } catch (e) { - SmartDialog.showToast(e.toString()); - } - }); - }, - child: Ink( - width: 75, - height: 75, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: - Theme.of(context).colorScheme.secondaryContainer, + }); + }, + child: Ink( + width: 100, + height: 100, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Theme.of(context) + .colorScheme + .secondaryContainer, + ), + child: Center(child: Icon(Icons.add, size: 35)), + ), ), - child: Center(child: Icon(Icons.add)), - ), - ), - ); - } else { - return GestureDetector( - onTap: () { - _pathList.removeAt(index); - _pathStream.add(_pathList); - if (_pathList.isEmpty && - _ctr.text.replaceAll('\n', '').isEmpty) { - _isEnable = false; - _isEnableStream.add(false); - } - }, - child: Image( - height: 75, - fit: BoxFit.fitHeight, - filterQuality: FilterQuality.low, - image: FileImage(File(_pathList[index])), - ), - ); - } - }, - separatorBuilder: (context, index) => const SizedBox(width: 10), + ); + } else { + return GestureDetector( + onTap: () { + _pathList.removeAt(index); + if (_pathList.isEmpty && _ctr.text.trim().isEmpty) { + _isEnablePub.value = false; + } + }, + child: Image( + height: 100, + fit: BoxFit.fitHeight, + filterQuality: FilterQuality.low, + image: FileImage(File(_pathList[index])), + ), + ); + } + }, + separatorBuilder: (context, index) => + const SizedBox(width: 10), + ), + ), ), - ), + SizedBox( + height: MediaQuery.paddingOf(context).bottom + 25, + ), + ], ), - ], + ), ); } } diff --git a/lib/pages/video/detail/reply_new/reply_page.dart b/lib/pages/video/detail/reply_new/reply_page.dart index 2a7a18908..9195e789a 100644 --- a/lib/pages/video/detail/reply_new/reply_page.dart +++ b/lib/pages/video/detail/reply_new/reply_page.dart @@ -283,7 +283,7 @@ class _ReplyPageState extends State autofocus: false, readOnly: snapshot.data ?? false, onChanged: (value) { - bool isEmpty = value.replaceAll('\n', '').isEmpty; + bool isEmpty = value.trim().isEmpty; if (!isEmpty && !_enablePublish) { _enablePublish = true; _publishStream.add(true); diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index 665f65cad..2b1476499 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -239,8 +239,14 @@ class _WhisperDetailPageState extends State { Widget _buildInputView() { return Container( - padding: const EdgeInsets.symmetric(vertical: 6), - color: Theme.of(context).colorScheme.onInverseSurface, + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onInverseSurface, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -273,7 +279,7 @@ class _WhisperDetailPageState extends State { minLines: 1, maxLines: 4, onChanged: (value) { - bool isNotEmpty = value.replaceAll('\n', '').isNotEmpty; + bool isNotEmpty = value.trim().isNotEmpty; if (isNotEmpty && !_visibleSend) { _visibleSend = true; _enableSend.add(true); @@ -289,7 +295,7 @@ class _WhisperDetailPageState extends State { fillColor: Theme.of(context).colorScheme.surface, border: OutlineInputBorder( borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(6), gapPadding: 0, ), contentPadding: const EdgeInsets.all(10), @@ -364,8 +370,7 @@ class _WhisperDetailPageState extends State { height = max(200, keyboardHeight); } - return Container( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), + return SizedBox( height: height, child: EmotePanel( onChoose: onChooseEmote,