初始化
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.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/hj_utils/screen.dart';
|
||||
import 'package:hgdj/tools_base/toast.dart';
|
||||
import 'package:hgdj/tools_base/widget/group_text_filed.dart';
|
||||
import 'package:image_pickers/image_pickers.dart';
|
||||
|
||||
import '../../../hj_utils/api_service/mine_service.dart';
|
||||
import '../../mine/collec_history_buy/col_his_buy_sub_page.dart';
|
||||
|
||||
/// 底部评论条:假输入框 + 选图 + 收藏 + 发送
|
||||
class CommentFootView extends StatefulWidget {
|
||||
final VideoModel? video;
|
||||
final Function(bool isSend) onComment; //false 点的输入框(唤起输入弹层),true 点的发送
|
||||
final bool isSending;
|
||||
final TextEditingController textCtr;
|
||||
final String? imgPath;
|
||||
final Function(String?)? onImgChange;
|
||||
final bool showCollect;
|
||||
const CommentFootView({
|
||||
super.key,
|
||||
this.video,
|
||||
required this.onComment,
|
||||
this.isSending = false,
|
||||
required this.textCtr,
|
||||
this.imgPath,
|
||||
this.onImgChange,
|
||||
this.showCollect = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CommentFootView> createState() => _CommentFootViewState();
|
||||
}
|
||||
|
||||
class _CommentFootViewState extends State<CommentFootView> {
|
||||
bool _isCollectLoading = false;
|
||||
|
||||
bool get isCollect => widget.video?.vidStatus?.hasCollected ?? false;
|
||||
|
||||
void _pickImage() async {
|
||||
final listMedia = await ImagePickers.pickerPaths(
|
||||
uiConfig: UIConfig(uiThemeColor: Colors.white),
|
||||
galleryMode: GalleryMode.image,
|
||||
selectCount: 1,
|
||||
showCamera: false,
|
||||
);
|
||||
if (listMedia.isNotEmpty)
|
||||
widget.onImgChange?.call(listMedia.first.path ?? '');
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void _onCollect() async {
|
||||
if (_isCollectLoading) return;
|
||||
_isCollectLoading = true;
|
||||
try {
|
||||
final preCollect = isCollect;
|
||||
|
||||
await MineService.postCollect(
|
||||
widget.video?.id, LoadDataType.post.apiType, !isCollect);
|
||||
|
||||
if (!preCollect) {
|
||||
widget.video?.collectCount = (widget.video?.collectCount ?? 0) + 1;
|
||||
} else {
|
||||
widget.video?.collectCount = (widget.video?.collectCount ?? 1) - 1;
|
||||
}
|
||||
_isCollectLoading = false;
|
||||
widget.video?.vidStatus?.hasCollected = !isCollect;
|
||||
if (!preCollect) {
|
||||
showToast("收藏成功");
|
||||
}
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
_isCollectLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
padding: EdgeInsets.only(
|
||||
left: 18.w, right: 18.w, bottom: screen.paddingBottom),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
height: 58,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => widget.onComment(false),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 2),
|
||||
child: GroupTextFiled(
|
||||
controller: widget.textCtr,
|
||||
height: 32,
|
||||
enabled: false,
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
textStyle: TextStyle(color: Colors.white, fontSize: 14),
|
||||
placeholder: '请在此输入评论...',
|
||||
placeholderTextStyle:
|
||||
TextStyle(color: Color(0xff525252), fontSize: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .1),
|
||||
borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
12.sizeBoxW,
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.fromLTRB(0, 4, 4, 4),
|
||||
width: 30,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
child: GestureDetector(
|
||||
onTap: _pickImage,
|
||||
child: widget.imgPath?.isNotEmpty == true
|
||||
? Image.file(
|
||||
File(widget.imgPath!),
|
||||
width: 30,
|
||||
height: 30,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.asset(
|
||||
'icon_pic.webp'.commentPath,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.imgPath?.isNotEmpty == true)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
widget.onImgChange?.call(null);
|
||||
setState(() {});
|
||||
},
|
||||
child: Image.asset(
|
||||
"close_grey.png".commonImgPath,
|
||||
width: 12,
|
||||
height: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.showCollect)
|
||||
Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
child: InkWell(
|
||||
enableFeedback: false,
|
||||
onTap: _onCollect,
|
||||
child: Image.asset(
|
||||
isCollect
|
||||
? "collect_red.png".commonImgPath
|
||||
: "collect_path.png".commonImgPath,
|
||||
width: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: GestureDetector(
|
||||
onTap: () => widget.onComment(true),
|
||||
child: widget.isSending
|
||||
? CupertinoActivityIndicator(
|
||||
color: AppColors.actionRed,
|
||||
radius: 12,
|
||||
)
|
||||
: Image.asset('ic_send.webp'.commentPath),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/hj_model/comment/comment_model.dart';
|
||||
import 'package:hgdj/routers/jump_router.dart';
|
||||
|
||||
/// 评论正文:命中搜索词时把该词渲染成可点的橙色链接,其余按纯文本
|
||||
class CommentRichText extends StatelessWidget {
|
||||
final CommentModel? model;
|
||||
|
||||
const CommentRichText({super.key, this.model});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const style = TextStyle(color: Color(0xE5FFFFFF), fontSize: 14);
|
||||
final keyword = model?.searchKeyword ?? "";
|
||||
//按关键词切开,段与段之间插链接;切不出两段就当纯文本
|
||||
final parts =
|
||||
keyword.isEmpty ? <String>[] : (model?.content?.split(keyword) ?? []);
|
||||
if (model?.linkStr?.isNotEmpty != true ||
|
||||
keyword.isEmpty ||
|
||||
parts.length <= 1) {
|
||||
return Text(model?.content ?? '', style: style);
|
||||
}
|
||||
return Text.rich(
|
||||
TextSpan(
|
||||
style: style,
|
||||
children: [
|
||||
for (int i = 0; i < parts.length; i++) ...[
|
||||
TextSpan(text: parts[i]),
|
||||
if (i != parts.length - 1)
|
||||
WidgetSpan(
|
||||
child: GestureDetector(
|
||||
onTap: () => pushToPageByLink(model?.linkStr),
|
||||
child: Text(
|
||||
keyword,
|
||||
style: const TextStyle(
|
||||
color: Color(0xffF68804), fontSize: 14, height: 1.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hgdj/hj_model/banner/comment_top_banner_model.dart';
|
||||
import 'package:hgdj/routers/jump_router.dart';
|
||||
import 'package:hgdj/tools_base/banner/ads_banner_widget.dart';
|
||||
import 'package:hgdj/tools_base/widget/card_swiper/src/swiper.dart';
|
||||
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
||||
|
||||
/// 评论区置顶评论下方 Banner:支持多图轮播(含 GIF),按 linkType 跳转内外链
|
||||
class CommentTopBanner extends StatefulWidget {
|
||||
final List<CommentTopBannerModel> banners;
|
||||
final double borderRadius;
|
||||
|
||||
/// 宽高比,默认对齐站内 Banner 素材 720×150;勿用固定高度,窄/宽屏 cover 会裁切
|
||||
final double aspectRatio;
|
||||
|
||||
const CommentTopBanner({
|
||||
super.key,
|
||||
required this.banners,
|
||||
this.borderRadius = 12,
|
||||
this.aspectRatio = 720 / 150,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CommentTopBanner> createState() => _CommentTopBannerState();
|
||||
}
|
||||
|
||||
class _CommentTopBannerState extends State<CommentTopBanner> {
|
||||
final ValueNotifier<int> _selectIndex = ValueNotifier(0);
|
||||
|
||||
int get _count => widget.banners.length;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_selectIndex.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onTap(CommentTopBannerModel item) async {
|
||||
final type = (item.linkType ?? '').toUpperCase();
|
||||
final value = item.linkValue?.trim() ?? '';
|
||||
if (type == 'NONE' || value.isEmpty) return;
|
||||
|
||||
if (type == 'EXTERNAL') {
|
||||
if (value.startsWith('http')) {
|
||||
await launchUrlToWeb(value);
|
||||
} else {
|
||||
await pushToPageByLink(value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == 'INTERNAL') {
|
||||
// 兼容 video://detail?id=xxx
|
||||
final uri = Uri.tryParse(value);
|
||||
if (uri != null && uri.scheme == 'video') {
|
||||
final id = uri.queryParameters['id'];
|
||||
if (id?.isNotEmpty == true) {
|
||||
await pushToVideoPage(videoId: id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await pushToPageByLink(value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_count == 0) return const SizedBox.shrink();
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
width: double.infinity,
|
||||
child: AspectRatio(
|
||||
aspectRatio: widget.aspectRatio,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Swiper(
|
||||
autoplay: _count > 1,
|
||||
autoplayDelay: 3000,
|
||||
loop: _count > 1,
|
||||
itemCount: _count,
|
||||
onIndexChanged: (index) => _selectIndex.value = index,
|
||||
itemBuilder: (context, index) {
|
||||
final item = widget.banners[index];
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _onTap(item),
|
||||
child: NetworkImageLoader(
|
||||
imageUrl: item.imageUrl ?? '',
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
borderRadius: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_count > 1)
|
||||
Positioned(
|
||||
bottom: 8,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: ValueListenableBuilder<int>(
|
||||
valueListenable: _selectIndex,
|
||||
builder: (_, index, __) => CIndicator(
|
||||
itemCount: _count,
|
||||
selectIndex: index,
|
||||
space: 3,
|
||||
dotSize: 4,
|
||||
selectWidth: 10,
|
||||
isBarStyle: true,
|
||||
color: Colors.white.withValues(alpha: 0.4),
|
||||
selectColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user