初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
@@ -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,
),
),
),
),
],
),
),
),
);
}
}