初始化
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_rich_text/easy_rich_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hgdj/assets_tool/app_colors.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/hj_model/video_model.dart';
|
||||
import 'package:hgdj/tools_base/widget/add_media_source_button.dart';
|
||||
import 'package:hgdj/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
import '../../../home/widget/support_up_alert.dart';
|
||||
import '../chose_tag_page.dart';
|
||||
import '../publish_logic.dart';
|
||||
import 'video_cover_widget.dart';
|
||||
|
||||
/// 发布页各输入区统一的卡片底色
|
||||
const publishDecoration = BoxDecoration(
|
||||
color: Color(0x0DFFFFFF),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
);
|
||||
|
||||
enum LinkType { topic, video, link }
|
||||
|
||||
/// 添加视频
|
||||
class ChoseVideoWidget extends StatelessWidget {
|
||||
ChoseVideoWidget({super.key});
|
||||
|
||||
final controller = Get.find<PublishLogic>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text('添加视频',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w500)),
|
||||
10.sizeBoxW,
|
||||
Text('最大300M以内',
|
||||
style: TextStyle(color: Color(0xff666666), fontSize: 12.sp)),
|
||||
],
|
||||
),
|
||||
14.h.sizeBoxH,
|
||||
Obx(() {
|
||||
final cellW = (Get.width - 52) / 3;
|
||||
return Row(children: [
|
||||
6.h.sizeBoxW,
|
||||
if (controller.video.value.localPath == null)
|
||||
AddMediaSourceButton(
|
||||
width: cellW,
|
||||
height: cellW,
|
||||
onTap: controller.choseVideo,
|
||||
isVideo: true)
|
||||
else
|
||||
_cover(cellW),
|
||||
]);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 已选视频:封面 + 右上角删除
|
||||
Widget _cover(double size) {
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxWidth: 230, maxHeight: 219),
|
||||
child: Stack(
|
||||
children: [
|
||||
VideoCoverWidget(controller.video.value.localCover ?? '',
|
||||
width: size, height: size, isLocal: true),
|
||||
Positioned(
|
||||
right: 6,
|
||||
top: 6,
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.deleteSelectVideo(),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
child:
|
||||
const Icon(Icons.close, size: 12, color: Color(0xff333333)),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 选择标签
|
||||
class TopicWidget extends StatelessWidget {
|
||||
TopicWidget({super.key});
|
||||
|
||||
final controller = Get.find<PublishLogic>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(
|
||||
() => GestureDetector(
|
||||
onTap: () async {
|
||||
final model = await Get.to(ChoseTopicPage(
|
||||
entry: controller.type.tag, selectTags: controller.topics));
|
||||
if (model is TagsBean) {
|
||||
controller.topics
|
||||
..clear()
|
||||
..add(model);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
padding: EdgeInsets.symmetric(horizontal: 14.w),
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 60.h,
|
||||
decoration: publishDecoration,
|
||||
child: controller.topics.isEmpty ? _emptyTip() : _topicInfo(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 未选标签时的占位提示
|
||||
Widget _emptyTip() {
|
||||
return Row(
|
||||
children: [
|
||||
Text("#",
|
||||
style: TextStyle(
|
||||
color: Color(0xff989898),
|
||||
fontSize: 24.sp,
|
||||
fontWeight: FontWeight.bold)),
|
||||
10.sizeBoxW,
|
||||
Text('选择标签',
|
||||
style: TextStyle(
|
||||
color: Color(0xff989898),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w500)),
|
||||
const Spacer(),
|
||||
Icon(Icons.arrow_forward_ios, size: 16.sp, color: Color(0xffDCDCDC)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 已选标签:封面 + 名称(点击交给外层跳转,别再套手势,否则会截走外层的 onTap)
|
||||
Widget _topicInfo() {
|
||||
return Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: NetworkImageLoader(
|
||||
imageUrl: controller.topics.first.coverImg ?? '',
|
||||
width: 38,
|
||||
height: 38),
|
||||
),
|
||||
5.sizeBoxW,
|
||||
Text(
|
||||
controller.topics.firstOrNull?.name ?? '',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(Icons.arrow_forward_ios, size: 16.sp, color: Color(0xffDCDCDC)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 输入标题
|
||||
class InputTitleWidget extends StatelessWidget {
|
||||
InputTitleWidget({super.key});
|
||||
|
||||
final controller = Get.find<PublishLogic>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 60.h,
|
||||
margin: EdgeInsets.only(top: 14.h),
|
||||
padding: EdgeInsets.symmetric(horizontal: 14.w),
|
||||
alignment: Alignment.centerLeft,
|
||||
decoration: publishDecoration,
|
||||
child: TextField(
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.left,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
style: TextStyle(color: Colors.white, fontSize: 16.sp, height: 22 / 15),
|
||||
focusNode: controller.titleFouceN,
|
||||
controller: controller.titleController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: '请填写标题',
|
||||
hintStyle: TextStyle(
|
||||
color: Color(0xff525252), fontSize: 12.sp, height: 22 / 15),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 输入正文(右下角实时字数)
|
||||
class InputDescribeWidget extends StatelessWidget {
|
||||
InputDescribeWidget({super.key});
|
||||
|
||||
final controller = Get.find<PublishLogic>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: () => controller.contentFouceN.requestFocus(),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(minHeight: 171.h),
|
||||
margin: const EdgeInsets.only(top: 14),
|
||||
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 11.h),
|
||||
decoration: publishDecoration,
|
||||
child: Stack(
|
||||
children: [
|
||||
TextField(
|
||||
style: TextStyle(color: Colors.white, fontSize: 16.sp),
|
||||
maxLength: 300,
|
||||
maxLines: null,
|
||||
focusNode: controller.contentFouceN,
|
||||
controller: controller.describeController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(color: Color(0xff525252), fontSize: 12.sp),
|
||||
hintMaxLines: 10,
|
||||
hintText: '有趣的介绍能让你的逼格提高N个档次!...',
|
||||
counter: SizedBox(),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
// 字数计数:只监听不接管所有权(释放归 PublishLogic.onClose)
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.describeController,
|
||||
builder: (_, value, __) => Text(
|
||||
'${value.text.length}/300',
|
||||
style: TextStyle(color: Color(0xff757575), fontSize: 12),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加图片(最多 9 张,列表末位固定是「添加」占位)
|
||||
class ChoseImageWidget extends StatelessWidget {
|
||||
final String? hint;
|
||||
final String? highlightText;
|
||||
final Color? highlightTextColor;
|
||||
final double? highlightTextSize;
|
||||
|
||||
ChoseImageWidget(
|
||||
{super.key,
|
||||
this.hint,
|
||||
this.highlightText,
|
||||
this.highlightTextColor,
|
||||
this.highlightTextSize});
|
||||
|
||||
final controller = Get.find<PublishLogic>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 14),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('添加图片',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w500)),
|
||||
EasyRichText(
|
||||
hint ?? '添加图集 图片数量最多上传9张哦~',
|
||||
defaultStyle:
|
||||
TextStyle(color: Color(0xff666666), fontSize: 12.sp),
|
||||
patternList: [
|
||||
EasyRichTextPattern(
|
||||
targetString: highlightText ?? '',
|
||||
style: TextStyle(
|
||||
color: highlightTextColor ?? Colors.white,
|
||||
fontSize: highlightTextSize ?? 16.sp,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
14.h.sizeBoxH,
|
||||
LayoutBuilder(builder: (__, constrains) {
|
||||
final cellW = (constrains.maxWidth - 16) / 3;
|
||||
final images = controller.imgs;
|
||||
return Obx(
|
||||
() => Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
children: List.generate(images.length, (i) {
|
||||
// 末位是占位项:满 10 项(9 张图)就不再给「添加」按钮
|
||||
if (i < images.length - 1) return _imgCell(images[i], cellW);
|
||||
if (i == 9) return const SizedBox.shrink();
|
||||
return AddMediaSourceButton(
|
||||
isVideo: false,
|
||||
onTap: controller.choseImg,
|
||||
height: cellW,
|
||||
width: cellW,
|
||||
title: "添加图片",
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/// 单张已选图片 + 右上角删除
|
||||
Widget _imgCell(String path, double size) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.file(File(path),
|
||||
width: size, height: size, fit: BoxFit.cover),
|
||||
Positioned(
|
||||
right: 6,
|
||||
top: 6,
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.imgs.remove(path),
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .8),
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(Icons.close, size: 14),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置价格(金币)
|
||||
class SettingGoldWidget extends StatelessWidget {
|
||||
SettingGoldWidget({super.key});
|
||||
|
||||
final controller = Get.find<PublishLogic>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
final res = await Get.bottomSheet(
|
||||
SupportUPAlert(
|
||||
isPublish: true,
|
||||
selectCoin: int.tryParse(controller.gold.value ?? '')),
|
||||
);
|
||||
controller.gold.value = res?.toString();
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 18),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
decoration: publishDecoration,
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset('community_coin.webp'.communityPath,
|
||||
width: 20, height: 20),
|
||||
10.sizeBoxW,
|
||||
const Text('设置价格',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF989898),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600)),
|
||||
const Spacer(),
|
||||
Obx(
|
||||
() => Text(
|
||||
controller.gold.value == null
|
||||
? '免费'
|
||||
: '${controller.gold.value} 金币',
|
||||
style: TextStyle(
|
||||
color: controller.gold.value == null
|
||||
? Color(0xFF989898)
|
||||
: AppColors.actionRed,
|
||||
// color: Color(0xffF68216),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
5.sizeBoxW,
|
||||
const Icon(Icons.arrow_forward_ios,
|
||||
color: Color(0xffDCDCDC), size: 12)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/assets_tool/images.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
/// 视频封面:本地/网络图 + 可选暗色遮罩 + 居中播放按钮
|
||||
/// [ratio] 传了就按比例撑开,不传则由外部约束决定尺寸
|
||||
class VideoCoverWidget extends StatelessWidget {
|
||||
final String cover;
|
||||
final double? ratio;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final bool isLocal;
|
||||
final bool showMask;
|
||||
final double borderRadius;
|
||||
|
||||
const VideoCoverWidget(
|
||||
this.cover, {
|
||||
super.key,
|
||||
this.ratio,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isLocal = false,
|
||||
this.showMask = false,
|
||||
this.borderRadius = 8,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final maskW = width ?? double.infinity;
|
||||
final maskH = height ?? double.infinity;
|
||||
final child = Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (isLocal)
|
||||
Image.file(File(cover),
|
||||
width: width, height: height, fit: BoxFit.cover)
|
||||
else
|
||||
NetworkImageLoader(
|
||||
imageUrl: cover, width: maskW, height: maskH, borderRadius: 0),
|
||||
if (showMask)
|
||||
Container(
|
||||
width: maskW,
|
||||
height: maskH,
|
||||
color: Colors.black.withValues(alpha: 0.4)),
|
||||
Image.asset('circle_play.webp'.videoPath, width: 30, height: 30),
|
||||
],
|
||||
);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
child: ratio == null
|
||||
? child
|
||||
: AspectRatio(aspectRatio: ratio! > 0 ? ratio! : 1, child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user