mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPlus/http/follow.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/view.dart';
|
||||
import 'package:PiliPlus/models/follow/result.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_tab/view.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -12,9 +14,6 @@ import 'package:PiliPlus/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/up.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
import '../../models/follow/result.dart';
|
||||
import '../common/common_controller.dart';
|
||||
|
||||
class DynamicsController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin, ScrollOrRefreshMixin {
|
||||
@override
|
||||
|
||||
@@ -1,498 +0,0 @@
|
||||
import 'package:PiliPlus/http/msg.dart';
|
||||
import 'package:PiliPlus/pages/common/common_publish_page.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/view.dart';
|
||||
import 'package:PiliPlus/pages/emote/controller.dart';
|
||||
import 'package:PiliPlus/pages/emote/view.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply_new/toolbar_icon_button.dart';
|
||||
import 'package:PiliPlus/utils/request_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class CreateDynPanel extends CommonPublishPage {
|
||||
const CreateDynPanel({
|
||||
super.key,
|
||||
super.imageLengthLimit = 18,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CreateDynPanel> createState() => _CreateDynPanelState();
|
||||
}
|
||||
|
||||
class _CreateDynPanelState extends CommonPublishPageState<CreateDynPanel> {
|
||||
bool _isPrivate = false;
|
||||
DateTime? _publishTime;
|
||||
ReplyOption _replyOption = ReplyOption.allow;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
try {
|
||||
Get.delete<EmotePanelController>();
|
||||
} catch (_) {}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: _buildAppBar(theme),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: _buildEditWidget,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildPubtimeWidget,
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildReplyOptionWidget(theme),
|
||||
const SizedBox(height: 5),
|
||||
_buildPrivateWidget(theme),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildImageList(theme),
|
||||
const SizedBox(height: 2),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildToolbar,
|
||||
buildPanelContainer(Colors.transparent),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImageList(ThemeData theme) => 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: () {
|
||||
onPickImage(() {
|
||||
if (pathList.isNotEmpty && !enablePublish.value) {
|
||||
enablePublish.value = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Ink(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
),
|
||||
child: Center(child: Icon(Icons.add, size: 35)),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return buildImage(index, 100);
|
||||
}
|
||||
},
|
||||
separatorBuilder: (context, index) => const SizedBox(width: 10),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
PreferredSizeWidget _buildAppBar(ThemeData theme) => PreferredSize(
|
||||
preferredSize: Size.fromHeight(66),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: '返回',
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor: WidgetStateProperty.resolveWith(
|
||||
(states) {
|
||||
return theme.colorScheme.secondaryContainer;
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: Get.back,
|
||||
icon: Icon(
|
||||
Icons.arrow_back_outlined,
|
||||
size: 18,
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: const Text(
|
||||
'发布动态',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Obx(
|
||||
() => FilledButton.tonal(
|
||||
onPressed: enablePublish.value ? onPublish : null,
|
||||
style: FilledButton.styleFrom(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 10,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
child: Text(_publishTime == null ? '发布' : '定时发布'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildPrivateWidget(ThemeData theme) {
|
||||
final color =
|
||||
_isPrivate ? theme.colorScheme.error : theme.colorScheme.secondary;
|
||||
return PopupMenuButton(
|
||||
initialValue: _isPrivate,
|
||||
onOpened: controller.keepChatPanel,
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
_isPrivate = value;
|
||||
});
|
||||
},
|
||||
itemBuilder: (context) => List.generate(
|
||||
2,
|
||||
(index) => PopupMenuItem<bool>(
|
||||
enabled: _publishTime != null && index == 1 ? false : true,
|
||||
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: color,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_isPrivate ? '仅自己可见' : '所有人可见',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: color,
|
||||
),
|
||||
strutStyle: StrutStyle(leading: 0, height: 1),
|
||||
),
|
||||
Icon(
|
||||
size: 20,
|
||||
Icons.keyboard_arrow_right,
|
||||
color: color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildReplyOptionWidget(ThemeData theme) {
|
||||
final color = _replyOption == ReplyOption.close
|
||||
? theme.colorScheme.error
|
||||
: theme.colorScheme.secondary;
|
||||
return PopupMenuButton(
|
||||
initialValue: _replyOption,
|
||||
onOpened: controller.keepChatPanel,
|
||||
onSelected: (item) {
|
||||
setState(() {
|
||||
_replyOption = item;
|
||||
});
|
||||
},
|
||||
itemBuilder: (context) => ReplyOption.values
|
||||
.map(
|
||||
(item) => PopupMenuItem<ReplyOption>(
|
||||
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: color,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_replyOption.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: color,
|
||||
),
|
||||
strutStyle: StrutStyle(leading: 0, height: 1),
|
||||
),
|
||||
Icon(
|
||||
size: 20,
|
||||
Icons.keyboard_arrow_right,
|
||||
color: color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _buildPubtimeWidget => _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 && 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,
|
||||
);
|
||||
|
||||
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 _buildEditWidget => Form(
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Listener(
|
||||
onPointerUp: (event) {
|
||||
if (readOnly.value) {
|
||||
updatePanelType(PanelType.keyboard);
|
||||
selectKeyboard.value = true;
|
||||
}
|
||||
},
|
||||
child: Obx(
|
||||
() => TextField(
|
||||
controller: editController,
|
||||
minLines: 4,
|
||||
maxLines: null,
|
||||
focusNode: focusNode,
|
||||
readOnly: readOnly.value,
|
||||
onChanged: (value) {
|
||||
bool isEmpty = value.trim().isEmpty && pathList.isEmpty;
|
||||
if (!isEmpty && !enablePublish.value) {
|
||||
enablePublish.value = true;
|
||||
} else if (isEmpty && enablePublish.value) {
|
||||
enablePublish.value = false;
|
||||
}
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
hintText: '说点什么吧',
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
gapPadding: 0,
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget? customPanel(double height) => SizedBox(
|
||||
height: height,
|
||||
child: EmotePanel(onChoose: onChooseEmote),
|
||||
);
|
||||
|
||||
@override
|
||||
Future onCustomPublish({required String message, List? pictures}) async {
|
||||
SmartDialog.showLoading(msg: '正在发布');
|
||||
dynamic result = await MsgHttp.createDynamic(
|
||||
mid: Accounts.main.mid,
|
||||
rawText: editController.text,
|
||||
pics: pictures,
|
||||
publishTime: _publishTime != null
|
||||
? _publishTime!.millisecondsSinceEpoch ~/ 1000
|
||||
: null,
|
||||
replyOption: _replyOption,
|
||||
privatePub: _isPrivate ? 1 : null,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
if (result['status']) {
|
||||
Get.back();
|
||||
SmartDialog.showToast('发布成功');
|
||||
RequestUtils.insertCreatedDyn(result);
|
||||
RequestUtils.checkCreatedDyn(
|
||||
id: result['data']?['dyn_id'],
|
||||
dynText: editController.text,
|
||||
);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
debugPrint('failed to publish: ${result['msg']}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/reply_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/http/reply.dart';
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
|
||||
class DynamicDetailController extends ReplyController<MainListReply> {
|
||||
DynamicDetailController(this.oid, this.type);
|
||||
int oid;
|
||||
int type;
|
||||
late DynamicItemModel item;
|
||||
int? floor;
|
||||
|
||||
late final horizontalPreview = GStorage.horizontalPreview;
|
||||
late final showDynActionBar = GStorage.showDynActionBar;
|
||||
|
||||
@override
|
||||
dynamic get sourceId =>
|
||||
type == ReplyType.video.index ? IdUtils.av2bv(oid) : oid;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
item = Get.arguments['item'];
|
||||
floor = Get.arguments['floor'];
|
||||
if (floor == 1) {
|
||||
count.value = item.modules.moduleStat?.comment?.count ?? 0;
|
||||
}
|
||||
|
||||
if (oid != 0) {
|
||||
queryData();
|
||||
}
|
||||
}
|
||||
|
||||
getCommentParams(int id) async {
|
||||
var res = await DynamicsHttp.opusDetail(opusId: id);
|
||||
if (res is Success) {
|
||||
final data = (res as Success<DynamicItemModel>).response;
|
||||
type = data.basic!.commentType!;
|
||||
oid = int.parse(data.basic!.commentIdStr!);
|
||||
queryData();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReplyInfo>? getDataList(MainListReply response) {
|
||||
return response.replies;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState<MainListReply>> customGetData() =>
|
||||
ReplyHttp.replyListGrpc(
|
||||
type: type,
|
||||
oid: oid,
|
||||
cursor: CursorReq(
|
||||
next: cursor?.next ?? $fixnum.Int64(0),
|
||||
mode: mode.value,
|
||||
),
|
||||
antiGoodsReply: antiGoodsReply,
|
||||
);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
library dynamic_detail;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
@@ -1,847 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/request_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/common/skeleton/video_reply.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/models/common/reply_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/detail/index.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply_reply/index.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
|
||||
import '../../../utils/grid.dart';
|
||||
import '../widgets/dynamic_panel.dart';
|
||||
|
||||
class DynamicDetailPage extends StatefulWidget {
|
||||
const DynamicDetailPage({super.key});
|
||||
|
||||
@override
|
||||
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
||||
}
|
||||
|
||||
class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
with TickerProviderStateMixin {
|
||||
late DynamicDetailController _dynamicDetailController;
|
||||
AnimationController? _fabAnimationCtr;
|
||||
final RxBool _visibleTitle = false.obs;
|
||||
// 回复类型
|
||||
late int replyType;
|
||||
bool _isFabVisible = true;
|
||||
bool? _imageStatus;
|
||||
int oid = 0;
|
||||
int? opusId;
|
||||
bool isOpusId = false;
|
||||
|
||||
late final List<double> _ratio = GStorage.dynamicDetailRatio;
|
||||
|
||||
bool get _horizontalPreview =>
|
||||
context.orientation == Orientation.landscape &&
|
||||
_dynamicDetailController.horizontalPreview;
|
||||
|
||||
late final _key = GlobalKey<ScaffoldState>();
|
||||
|
||||
get _getImageCallback => _horizontalPreview
|
||||
? (imgList, index) {
|
||||
_imageStatus = true;
|
||||
bool isFabVisible = _isFabVisible;
|
||||
if (isFabVisible) {
|
||||
_hideFab();
|
||||
}
|
||||
final ctr = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
)..forward();
|
||||
PageUtils.onHorizontalPreview(
|
||||
_key,
|
||||
AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration.zero,
|
||||
),
|
||||
ctr,
|
||||
imgList,
|
||||
index,
|
||||
(value) async {
|
||||
_imageStatus = null;
|
||||
if (isFabVisible) {
|
||||
isFabVisible = false;
|
||||
_showFab();
|
||||
}
|
||||
if (value == false) {
|
||||
await ctr.reverse();
|
||||
}
|
||||
try {
|
||||
ctr.dispose();
|
||||
} catch (_) {}
|
||||
if (value == false) {
|
||||
Get.back();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
: null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// floor 1原创 2转发
|
||||
init();
|
||||
|
||||
_fabAnimationCtr = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
_fabAnimationCtr?.forward();
|
||||
_dynamicDetailController.scrollController.addListener(listener);
|
||||
}
|
||||
|
||||
// 页面初始化
|
||||
void init() async {
|
||||
Map args = Get.arguments;
|
||||
// 楼层
|
||||
int floor = args['floor'];
|
||||
// 评论类型
|
||||
final item = args['item'] as DynamicItemModel;
|
||||
int commentType = item.basic?.commentType ?? 11;
|
||||
replyType = (commentType == 0) ? 11 : commentType;
|
||||
|
||||
if (floor == 1) {
|
||||
oid = int.parse(item.basic!.commentIdStr!);
|
||||
} else {
|
||||
try {
|
||||
final moduleDynamic = item.modules.moduleDynamic!;
|
||||
String majorType = moduleDynamic.major!.type!;
|
||||
|
||||
if (majorType == 'MAJOR_TYPE_OPUS') {
|
||||
// 转发的动态
|
||||
String jumpUrl = moduleDynamic.major!.opus!.jumpUrl!;
|
||||
opusId = int.parse(jumpUrl.split('/').last);
|
||||
if (opusId != null) {
|
||||
isOpusId = true;
|
||||
_dynamicDetailController = Get.put(
|
||||
DynamicDetailController(oid, replyType),
|
||||
tag: Utils.makeHeroTag(opusId),
|
||||
);
|
||||
await _dynamicDetailController.getCommentParams(opusId!);
|
||||
setState(() {});
|
||||
}
|
||||
} else {
|
||||
oid = moduleDynamic.major!.draw!.id!;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
if (!isOpusId) {
|
||||
_dynamicDetailController = Get.put(
|
||||
DynamicDetailController(oid, replyType),
|
||||
tag: Utils.makeHeroTag(oid),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 查看二级评论
|
||||
void replyReply(BuildContext context, ReplyInfo replyItem, int? id) {
|
||||
EasyThrottle.throttle('replyReply', const Duration(milliseconds: 500), () {
|
||||
int oid = replyItem.oid.toInt();
|
||||
int rpid = replyItem.id.toInt();
|
||||
Widget replyReplyPage(
|
||||
[bool automaticallyImplyLeading = true,
|
||||
VoidCallback? onDispose]) =>
|
||||
Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
title: const Text('评论详情'),
|
||||
titleSpacing: automaticallyImplyLeading ? null : 12,
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: VideoReplyReplyPanel(
|
||||
id: id,
|
||||
oid: oid,
|
||||
rpid: rpid,
|
||||
source: 'dynamic',
|
||||
replyType: ReplyType.values[replyType],
|
||||
firstFloor: replyItem,
|
||||
onDispose: onDispose,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (this.context.orientation == Orientation.portrait) {
|
||||
Get.to(
|
||||
replyReplyPage,
|
||||
routeName: 'dynamicDetail-Copy',
|
||||
arguments: {
|
||||
'item': _dynamicDetailController.item,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
ScaffoldState? scaffoldState = Scaffold.maybeOf(context);
|
||||
if (scaffoldState != null) {
|
||||
bool isFabVisible = _isFabVisible;
|
||||
if (isFabVisible) {
|
||||
_hideFab();
|
||||
}
|
||||
scaffoldState.showBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
(context) => MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeLeft: true,
|
||||
child: replyReplyPage(
|
||||
false,
|
||||
() {
|
||||
if (isFabVisible && _imageStatus != true) {
|
||||
_showFab();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Get.to(
|
||||
replyReplyPage,
|
||||
routeName: 'dynamicDetail-Copy',
|
||||
arguments: {
|
||||
'item': _dynamicDetailController.item,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_dynamicDetailController.scrollController.hasClients) {
|
||||
_visibleTitle.value =
|
||||
_dynamicDetailController.scrollController.positions.first.pixels >
|
||||
55;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void listener() {
|
||||
// 标题
|
||||
_visibleTitle.value =
|
||||
_dynamicDetailController.scrollController.positions.first.pixels > 55;
|
||||
|
||||
// fab按钮
|
||||
final ScrollDirection direction1 = _dynamicDetailController
|
||||
.scrollController.positions.first.userScrollDirection;
|
||||
late final ScrollDirection direction2 = _dynamicDetailController
|
||||
.scrollController.positions.last.userScrollDirection;
|
||||
if (direction1 == ScrollDirection.forward ||
|
||||
direction2 == ScrollDirection.forward) {
|
||||
_showFab();
|
||||
} else if (direction1 == ScrollDirection.reverse ||
|
||||
direction2 == ScrollDirection.reverse) {
|
||||
_hideFab();
|
||||
}
|
||||
}
|
||||
|
||||
void _showFab() {
|
||||
if (!_isFabVisible) {
|
||||
_isFabVisible = true;
|
||||
_fabAnimationCtr?.forward();
|
||||
}
|
||||
}
|
||||
|
||||
void _hideFab() {
|
||||
if (_isFabVisible) {
|
||||
_isFabVisible = false;
|
||||
_fabAnimationCtr?.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fabAnimationCtr?.dispose();
|
||||
_fabAnimationCtr = null;
|
||||
_dynamicDetailController.scrollController.removeListener(listener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: Obx(
|
||||
() {
|
||||
return AnimatedOpacity(
|
||||
opacity: _visibleTitle.value ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: AuthorPanel(
|
||||
item: _dynamicDetailController.item,
|
||||
source: 'detail', //to remove tag
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: context.orientation == Orientation.landscape
|
||||
? [
|
||||
IconButton(
|
||||
tooltip: '页面比例调节',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(
|
||||
top: 56,
|
||||
right: 16,
|
||||
),
|
||||
width: context.width / 4,
|
||||
height: 32,
|
||||
child: Builder(
|
||||
builder: (context) => Slider(
|
||||
min: 1,
|
||||
max: 100,
|
||||
value: _ratio.first,
|
||||
onChanged: (value) async {
|
||||
if (value >= 10 && value <= 90) {
|
||||
_ratio[0] = value;
|
||||
_ratio[1] = 100 - value;
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.dynamicDetailRatio,
|
||||
_ratio,
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Transform.rotate(
|
||||
angle: pi / 2,
|
||||
child: Icon(Icons.splitscreen, size: 19),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: context.orientation == Orientation.portrait
|
||||
? refreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _dynamicDetailController.onRefresh();
|
||||
},
|
||||
child: _buildBody(context.orientation, theme),
|
||||
)
|
||||
: _buildBody(context.orientation, theme),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(Orientation orientation, ThemeData theme) => Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
double padding = max(context.width / 2 - Grid.smallCardWidth, 0);
|
||||
if (orientation == Orientation.portrait) {
|
||||
return CustomScrollView(
|
||||
controller: _dynamicDetailController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: DynamicPanel(
|
||||
item: _dynamicDetailController.item,
|
||||
source: 'detail',
|
||||
callback: _getImageCallback,
|
||||
),
|
||||
),
|
||||
replyPersistentHeader(theme),
|
||||
Obx(
|
||||
() => replyList(
|
||||
theme, _dynamicDetailController.loadingState.value),
|
||||
),
|
||||
]
|
||||
.map<Widget>((e) => SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
sliver: e))
|
||||
.toList(),
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: _ratio[0].toInt(),
|
||||
child: CustomScrollView(
|
||||
controller: _dynamicDetailController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: padding / 4,
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: DynamicPanel(
|
||||
item: _dynamicDetailController.item,
|
||||
source: 'detail',
|
||||
callback: _getImageCallback,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: _ratio[1].toInt(),
|
||||
child: Scaffold(
|
||||
key: _key,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: refreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _dynamicDetailController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller:
|
||||
_dynamicDetailController.scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(right: padding / 4),
|
||||
sliver: replyPersistentHeader(theme),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(right: padding / 4),
|
||||
sliver: Obx(
|
||||
() => replyList(
|
||||
theme,
|
||||
_dynamicDetailController
|
||||
.loadingState.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (_fabAnimationCtr != null)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 1),
|
||||
end: const Offset(0, 0),
|
||||
).animate(CurvedAnimation(
|
||||
parent: _fabAnimationCtr!,
|
||||
curve: Curves.easeInOut,
|
||||
)),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
Widget button() => FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: () {
|
||||
feedBack();
|
||||
_dynamicDetailController.onReply(
|
||||
context,
|
||||
oid: _dynamicDetailController.oid,
|
||||
replyType: ReplyType.values[replyType],
|
||||
);
|
||||
},
|
||||
tooltip: '评论动态',
|
||||
child: const Icon(Icons.reply),
|
||||
);
|
||||
return _dynamicDetailController.showDynActionBar.not
|
||||
? Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: 14,
|
||||
bottom:
|
||||
MediaQuery.paddingOf(context).bottom + 14,
|
||||
),
|
||||
child: button(),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 14, bottom: 14),
|
||||
child: button(),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: theme.colorScheme.outline
|
||||
.withOpacity(0.08),
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
bottom:
|
||||
MediaQuery.paddingOf(context).bottom),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (btnContext) =>
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
builder: (context) => RepostPanel(
|
||||
item: _dynamicDetailController
|
||||
.item,
|
||||
callback: () {
|
||||
int count =
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.forward
|
||||
?.count ??
|
||||
0;
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat ??=
|
||||
ModuleStatModel();
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.forward ??=
|
||||
DynamicStat();
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat!
|
||||
.forward!
|
||||
.count = count + 1;
|
||||
if (btnContext.mounted) {
|
||||
(btnContext as Element?)
|
||||
?.markNeedsBuild();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.shareFromSquare,
|
||||
size: 16,
|
||||
color: theme.colorScheme.outline,
|
||||
semanticLabel: "转发",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
15, 0, 15, 0),
|
||||
foregroundColor:
|
||||
theme.colorScheme.outline,
|
||||
),
|
||||
label: Text(
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.forward
|
||||
?.count !=
|
||||
null
|
||||
? Utils.numFormat(
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat!
|
||||
.forward!
|
||||
.count)
|
||||
: '转发',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
Utils.shareText(
|
||||
'${HttpString.dynamicShareBaseUrl}/${_dynamicDetailController.item.idStr}');
|
||||
},
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.shareNodes,
|
||||
size: 16,
|
||||
color: theme.colorScheme.outline,
|
||||
semanticLabel: "分享",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
15, 0, 15, 0),
|
||||
foregroundColor:
|
||||
theme.colorScheme.outline,
|
||||
),
|
||||
label: const Text('分享'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (context) => TextButton.icon(
|
||||
onPressed: () =>
|
||||
RequestUtils.onLikeDynamic(
|
||||
_dynamicDetailController.item,
|
||||
() {
|
||||
if (context.mounted) {
|
||||
(context as Element?)
|
||||
?.markNeedsBuild();
|
||||
}
|
||||
},
|
||||
),
|
||||
icon: Icon(
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? FontAwesomeIcons.solidThumbsUp
|
||||
: FontAwesomeIcons.thumbsUp,
|
||||
size: 16,
|
||||
color: _dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.outline,
|
||||
semanticLabel:
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? "已赞"
|
||||
: "点赞",
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
15, 0, 15, 0),
|
||||
foregroundColor:
|
||||
theme.colorScheme.outline,
|
||||
),
|
||||
label: AnimatedSwitcher(
|
||||
duration: const Duration(
|
||||
milliseconds: 400),
|
||||
transitionBuilder: (Widget child,
|
||||
Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation,
|
||||
child: child);
|
||||
},
|
||||
child: Text(
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.like
|
||||
?.count !=
|
||||
null
|
||||
? Utils.numFormat(
|
||||
_dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat!
|
||||
.like!
|
||||
.count)
|
||||
: '点赞',
|
||||
style: TextStyle(
|
||||
color: _dynamicDetailController
|
||||
.item
|
||||
.modules
|
||||
.moduleStat
|
||||
?.like
|
||||
?.status ==
|
||||
true
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
SliverPersistentHeader replyPersistentHeader(ThemeData theme) {
|
||||
return SliverPersistentHeader(
|
||||
delegate: CustomSliverPersistentHeaderDelegate(
|
||||
bgColor: theme.colorScheme.surface,
|
||||
child: Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 12, right: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Obx(
|
||||
() => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
'${_dynamicDetailController.count.value != -1 ? _dynamicDetailController.count.value : 0}条回复',
|
||||
key: ValueKey<int>(_dynamicDetailController.count.value),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => _dynamicDetailController.queryBySort(),
|
||||
icon: Icon(
|
||||
Icons.sort,
|
||||
size: 16,
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
label: Obx(() => Text(
|
||||
_dynamicDetailController.sortType.value.label,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
pinned: true,
|
||||
);
|
||||
}
|
||||
|
||||
Widget replyList(
|
||||
ThemeData theme, LoadingState<List<ReplyInfo>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => SliverList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return const VideoReplySkeleton();
|
||||
},
|
||||
itemCount: 8,
|
||||
),
|
||||
Success() => loadingState.response?.isNotEmpty == true
|
||||
? SliverList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
if (index == loadingState.response!.length) {
|
||||
_dynamicDetailController.onLoadMore();
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom),
|
||||
height: 125,
|
||||
child: Text(
|
||||
_dynamicDetailController.isEnd.not
|
||||
? '加载中...'
|
||||
: loadingState.response!.isEmpty
|
||||
? '还没有评论'
|
||||
: '没有更多了',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return ReplyItemGrpc(
|
||||
replyItem: loadingState.response![index],
|
||||
replyLevel: '1',
|
||||
replyReply: (replyItem, id) =>
|
||||
replyReply(context, replyItem, id),
|
||||
onReply: () {
|
||||
_dynamicDetailController.onReply(
|
||||
context,
|
||||
replyItem: loadingState.response![index],
|
||||
index: index,
|
||||
);
|
||||
},
|
||||
onDelete: (subIndex) =>
|
||||
_dynamicDetailController.onRemove(index, subIndex),
|
||||
upMid: _dynamicDetailController.upMid,
|
||||
callback: _getImageCallback,
|
||||
onCheckReply: (item) =>
|
||||
_dynamicDetailController.onCheckReply(context, item),
|
||||
onToggleTop: (isUpTop, rpid) =>
|
||||
_dynamicDetailController.onToggleTop(
|
||||
index,
|
||||
_dynamicDetailController.oid,
|
||||
_dynamicDetailController.type,
|
||||
isUpTop,
|
||||
rpid,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
itemCount: loadingState.response!.length + 1,
|
||||
)
|
||||
: HttpError(
|
||||
onReload: _dynamicDetailController.onReload,
|
||||
),
|
||||
Error() => HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
onReload: _dynamicDetailController.onReload,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
library dynamics;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
@@ -1,373 +0,0 @@
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/http/msg.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/common/common_publish_page.dart';
|
||||
import 'package:PiliPlus/pages/emote/controller.dart';
|
||||
import 'package:PiliPlus/pages/emote/view.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply_new/toolbar_icon_button.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/request_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class RepostPanel extends CommonPublishPage {
|
||||
const RepostPanel({
|
||||
super.key,
|
||||
this.item,
|
||||
this.dynIdStr,
|
||||
this.callback,
|
||||
// video
|
||||
this.rid,
|
||||
this.dynType,
|
||||
this.pic,
|
||||
this.title,
|
||||
this.uname,
|
||||
this.isMax,
|
||||
});
|
||||
|
||||
// video
|
||||
final int? rid;
|
||||
final int? dynType;
|
||||
final String? pic;
|
||||
final String? title;
|
||||
final String? uname;
|
||||
final bool? isMax;
|
||||
|
||||
final DynamicItemModel? item;
|
||||
final String? dynIdStr;
|
||||
final VoidCallback? callback;
|
||||
|
||||
@override
|
||||
State<RepostPanel> createState() => _RepostPanelState();
|
||||
}
|
||||
|
||||
class _RepostPanelState extends CommonPublishPageState<RepostPanel> {
|
||||
late bool _isMax = widget.isMax ?? false;
|
||||
|
||||
late final _pic = widget.pic ??
|
||||
widget.item?.modules.moduleDynamic?.major?.archive?.cover ??
|
||||
widget.item?.modules.moduleDynamic?.major?.pgc?.cover ??
|
||||
widget.item?.modules.moduleDynamic?.major?.opus?.pics?.firstOrNull?.url;
|
||||
|
||||
late final _text = widget.title ??
|
||||
widget.item?.modules.moduleDynamic?.major?.opus?.summary?.text ??
|
||||
widget.item?.modules.moduleDynamic?.desc?.text ??
|
||||
widget.item?.modules.moduleDynamic?.major?.archive?.title ??
|
||||
widget.item?.modules.moduleDynamic?.major?.pgc?.title ??
|
||||
'';
|
||||
|
||||
late final _uname = widget.uname ?? widget.item?.modules.moduleAuthor?.name;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
try {
|
||||
Get.delete<EmotePanelController>();
|
||||
} catch (_) {}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return AnimatedSize(
|
||||
alignment: Alignment.topCenter,
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Column(
|
||||
mainAxisSize: _isMax ? MainAxisSize.max : MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: _isMax ? 16 : 10),
|
||||
_buildAppBar(theme),
|
||||
if (_isMax)
|
||||
Expanded(child: _buildEditPanel(theme))
|
||||
else
|
||||
_buildEditPanel(theme),
|
||||
if (_isMax.not)
|
||||
..._biuldDismiss(theme)
|
||||
else ...[
|
||||
_buildToolbar,
|
||||
buildPanelContainer(Colors.transparent),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEditPanel(ThemeData theme) => Column(
|
||||
mainAxisSize: _isMax ? MainAxisSize.max : MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: _isMax.not
|
||||
? BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 2,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: _isMax.not
|
||||
? _buildEditPlaceHolder(theme)
|
||||
: _buildEditWidget,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildRefWidget(theme),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildRefWidget(ThemeData theme) => Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHigh ==
|
||||
theme.colorScheme.surface
|
||||
? theme.colorScheme.onInverseSurface
|
||||
: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (_pic != null) ...[
|
||||
NetworkImgLayer(
|
||||
radius: 8,
|
||||
width: 40,
|
||||
height: 40,
|
||||
src: _pic,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_uname?.isNotEmpty == true)
|
||||
Text(
|
||||
'@$_uname',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_text,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildEditPlaceHolder(ThemeData theme) => GestureDetector(
|
||||
onTap: () async {
|
||||
setState(() => _isMax = true);
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
if (mounted) {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'说点什么吧',
|
||||
style: TextStyle(
|
||||
height: 1.75,
|
||||
fontSize: 15,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _buildEditWidget => Form(
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: Listener(
|
||||
onPointerUp: (event) {
|
||||
if (readOnly.value) {
|
||||
updatePanelType(PanelType.keyboard);
|
||||
selectKeyboard.value = true;
|
||||
}
|
||||
},
|
||||
child: Obx(
|
||||
() => TextField(
|
||||
controller: editController,
|
||||
minLines: 4,
|
||||
maxLines: null,
|
||||
focusNode: focusNode,
|
||||
readOnly: readOnly.value,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '说点什么吧',
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
gapPadding: 0,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 10),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildAppBar(ThemeData theme) => _isMax.not
|
||||
? Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
widget.rid != null ? '分享至动态' : '转发动态',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: onPublish,
|
||||
style: TextButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
child: Text(widget.rid != null ? '立即发布' : '立即转发'),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
)
|
||||
: Container(
|
||||
height: 34,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: '返回',
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
WidgetStateProperty.resolveWith((states) {
|
||||
return theme.colorScheme.secondaryContainer;
|
||||
}),
|
||||
),
|
||||
onPressed: Get.back,
|
||||
icon: Icon(
|
||||
Icons.arrow_back_outlined,
|
||||
size: 18,
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
widget.rid != null ? '分享至动态' : '转发动态',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FilledButton.tonal(
|
||||
onPressed: onPublish,
|
||||
style: FilledButton.styleFrom(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 10,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
child: Text(widget.rid != null ? '发布' : '转发'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
List<Widget> _biuldDismiss(ThemeData theme) => [
|
||||
const SizedBox(height: 10),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: theme.colorScheme.outline.withOpacity(0.1),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: Get.back,
|
||||
title: Center(
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: theme.colorScheme.outline),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10 + MediaQuery.of(context).padding.bottom),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget? customPanel(double height) => SizedBox(
|
||||
height: height,
|
||||
child: EmotePanel(onChoose: onChooseEmote),
|
||||
);
|
||||
|
||||
@override
|
||||
Future onCustomPublish({required String message, List? pictures}) async {
|
||||
dynamic result = await MsgHttp.createDynamic(
|
||||
mid: Accounts.main.mid,
|
||||
dynIdStr: widget.item?.idStr ?? widget.dynIdStr,
|
||||
rid: widget.rid,
|
||||
dynType: widget.dynType,
|
||||
rawText: editController.text,
|
||||
);
|
||||
if (result['status']) {
|
||||
Get.back();
|
||||
SmartDialog.showToast('转发成功');
|
||||
widget.callback?.call();
|
||||
RequestUtils.insertCreatedDyn(result);
|
||||
RequestUtils.checkCreatedDyn(
|
||||
id: result['data']?['dyn_id'],
|
||||
dynText: editController.text,
|
||||
);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/msg.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||
import 'package:PiliPlus/pages/main/controller.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../http/dynamics.dart';
|
||||
|
||||
class DynamicsTabController
|
||||
extends CommonListController<DynamicsDataModel, DynamicItemModel> {
|
||||
DynamicsTabController({required this.dynamicsType});
|
||||
final String dynamicsType;
|
||||
String offset = '';
|
||||
int mid = -1;
|
||||
late final MainController mainController = Get.find<MainController>();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future onRefresh() {
|
||||
if (dynamicsType == 'all') {
|
||||
mainController.setCount();
|
||||
}
|
||||
offset = '';
|
||||
return super.onRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
List<DynamicItemModel>? getDataList(DynamicsDataModel response) {
|
||||
return response.items;
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(
|
||||
bool isRefresh, Success<DynamicsDataModel> response) {
|
||||
offset = response.response.offset ?? '';
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState<DynamicsDataModel>> customGetData() =>
|
||||
DynamicsHttp.followDynamic(
|
||||
type: dynamicsType == "up" ? "all" : dynamicsType,
|
||||
offset: offset,
|
||||
mid: dynamicsType == "up" ? mid : -1,
|
||||
);
|
||||
|
||||
Future onRemove(dynamic dynamicId) async {
|
||||
var res = await MsgHttp.removeDynamic(dynIdStr: dynamicId);
|
||||
if (res['status']) {
|
||||
List<DynamicItemModel> list = (loadingState.value as Success).response;
|
||||
list.removeWhere((item) => item.idStr == dynamicId);
|
||||
loadingState.refresh();
|
||||
SmartDialog.showToast('删除成功');
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
Future onSetTop(bool isTop, dynamic dynamicId) async {
|
||||
var res = await DynamicsHttp.setTop(dynamicId: dynamicId);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('${isTop ? '取消' : ''}置顶成功');
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future onReload() {
|
||||
scrollController.jumpToTop();
|
||||
return super.onReload();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
library dynamics.tab;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
@@ -1,212 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/common/common_page.dart';
|
||||
import 'package:PiliPlus/pages/main/controller.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:waterfall_flow/waterfall_flow.dart';
|
||||
|
||||
import '../../../common/skeleton/dynamic_card.dart';
|
||||
import '../../../utils/grid.dart';
|
||||
|
||||
import '../index.dart';
|
||||
import '../widgets/dynamic_panel.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class DynamicsTabPage extends CommonPage {
|
||||
const DynamicsTabPage({super.key, required this.dynamicsType});
|
||||
|
||||
final String dynamicsType;
|
||||
|
||||
@override
|
||||
State<DynamicsTabPage> createState() => _DynamicsTabPageState();
|
||||
}
|
||||
|
||||
class _DynamicsTabPageState
|
||||
extends CommonPageState<DynamicsTabPage, DynamicsTabController>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late bool dynamicsWaterfallFlow;
|
||||
StreamSubscription? _listener;
|
||||
late final MainController _mainController = Get.find<MainController>();
|
||||
|
||||
DynamicsController dynamicsController = Get.put(DynamicsController());
|
||||
@override
|
||||
late DynamicsTabController controller = Get.put(
|
||||
DynamicsTabController(dynamicsType: widget.dynamicsType)
|
||||
..mid = dynamicsController.mid.value,
|
||||
tag: widget.dynamicsType,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void listener() {
|
||||
if (_mainController.navigationBars[0]['id'] != 1 &&
|
||||
_mainController.selectedIndex.value == 0) {
|
||||
return;
|
||||
}
|
||||
super.listener();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.dynamicsType == 'up') {
|
||||
_listener = dynamicsController.mid.listen((mid) {
|
||||
if (mid != -1) {
|
||||
controller
|
||||
..mid = mid
|
||||
..onReload();
|
||||
}
|
||||
});
|
||||
}
|
||||
dynamicsWaterfallFlow = GStorage.setting
|
||||
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener?.cancel();
|
||||
dynamicsController.mid.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return refreshIndicator(
|
||||
onRefresh: () async {
|
||||
dynamicsController.queryFollowUp();
|
||||
await controller.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: controller.scrollController,
|
||||
slivers: [
|
||||
Obx(() => _buildBody(controller.loadingState.value)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget skeleton() {
|
||||
if (!dynamicsWaterfallFlow) {
|
||||
return SliverCrossAxisGroup(
|
||||
slivers: [
|
||||
const SliverFillRemaining(),
|
||||
SliverConstrainedCrossAxis(
|
||||
maxExtent: Grid.smallCardWidth * 2,
|
||||
sliver: SliverList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
},
|
||||
itemCount: 10,
|
||||
),
|
||||
),
|
||||
const SliverFillRemaining()
|
||||
],
|
||||
);
|
||||
}
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
mainAxisSpacing: StyleString.cardSpace / 2,
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent: 50,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
},
|
||||
childCount: 10,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(LoadingState<List<DynamicItemModel>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => skeleton(),
|
||||
Success() => loadingState.response?.isNotEmpty == true
|
||||
? SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
sliver: dynamicsWaterfallFlow
|
||||
? SliverWaterfallFlow.extent(
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
lastChildLayoutTypeBuilder: (index) {
|
||||
if (index == loadingState.response!.length - 1) {
|
||||
controller.onLoadMore();
|
||||
}
|
||||
return index == loadingState.response!.length
|
||||
? LastChildLayoutType.foot
|
||||
: LastChildLayoutType.none;
|
||||
},
|
||||
children: [
|
||||
if (dynamicsController.tabController.index == 4 &&
|
||||
dynamicsController.mid.value != -1) ...[
|
||||
for (var i in loadingState.response!)
|
||||
DynamicPanel(
|
||||
item: i,
|
||||
onRemove: controller.onRemove,
|
||||
),
|
||||
] else ...[
|
||||
for (var i in loadingState.response!)
|
||||
if (!dynamicsController.tempBannedList
|
||||
.contains(i.modules.moduleAuthor?.mid))
|
||||
DynamicPanel(
|
||||
item: i,
|
||||
onRemove: controller.onRemove,
|
||||
),
|
||||
]
|
||||
],
|
||||
)
|
||||
: SliverCrossAxisGroup(
|
||||
slivers: [
|
||||
const SliverFillRemaining(),
|
||||
SliverConstrainedCrossAxis(
|
||||
maxExtent: Grid.smallCardWidth * 2,
|
||||
sliver: SliverList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
if (index == loadingState.response!.length - 1) {
|
||||
controller.onLoadMore();
|
||||
}
|
||||
final item = loadingState.response![index];
|
||||
if ((dynamicsController.tabController.index ==
|
||||
4 &&
|
||||
dynamicsController.mid.value != -1) ||
|
||||
!dynamicsController.tempBannedList.contains(
|
||||
item.modules.moduleAuthor?.mid)) {
|
||||
return DynamicPanel(
|
||||
item: item,
|
||||
onRemove: controller.onRemove,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
itemCount: loadingState.response!.length,
|
||||
),
|
||||
),
|
||||
const SliverFillRemaining(),
|
||||
],
|
||||
),
|
||||
)
|
||||
: HttpError(
|
||||
onReload: controller.onReload,
|
||||
),
|
||||
Error() => HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
onReload: controller.onReload,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:PiliPlus/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPlus/models/common/up_panel_position.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/create_dyn_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_create/view.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/pic_panel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
import '../../../common/constants.dart';
|
||||
import 'pic_panel.dart';
|
||||
|
||||
Widget articlePanel(
|
||||
ThemeData theme,
|
||||
String? source,
|
||||
|
||||
@@ -2,9 +2,11 @@ import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/avatar.dart';
|
||||
import 'package:PiliPlus/common/widgets/report.dart';
|
||||
import 'package:PiliPlus/common/widgets/save_panel.dart';
|
||||
import 'package:PiliPlus/pages/save_panel/view.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/controller.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/request_utils.dart';
|
||||
@@ -17,9 +19,6 @@ import 'package:PiliPlus/http/user.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
import '../../../http/constants.dart';
|
||||
import '../controller.dart';
|
||||
|
||||
class AuthorPanel extends StatelessWidget {
|
||||
final DynamicItemModel item;
|
||||
final Function? addBannedList;
|
||||
|
||||
@@ -3,7 +3,15 @@ import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_view.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/pages/article/widgets/opus_content.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/additional_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/article_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/live_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/live_rcmd_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/pic_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/rich_node_panel.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/video_panel.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -11,15 +19,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
import '../../../models/dynamics/result.dart';
|
||||
import 'additional_panel.dart';
|
||||
import 'article_panel.dart';
|
||||
import 'live_panel.dart';
|
||||
import 'live_rcmd_panel.dart';
|
||||
import 'pic_panel.dart';
|
||||
import 'rich_node_panel.dart';
|
||||
import 'video_panel.dart';
|
||||
|
||||
InlineSpan picsNodes(
|
||||
List<OpusPicsModel> pics,
|
||||
Function(List<String>, int)? callback,
|
||||
|
||||
Reference in New Issue
Block a user